/**
 * Fusion.SingleFile
 *
 * $Id: Widget.js 1377 2008-04-16 19:27:32Z madair $
 *
 * Copyright (c) 2008, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /* ***************************************************************************
 * This file gets included in the single file build process to adjust Fusion
 * properties and methods.
 *
 * ***************************************************************************/
 

var Fusion = {
    singleFile: true
};


/**
 * Fusion
 *
 * $Id: fusion.js 2865 2014-07-10 03:34:54Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * declare global namespace object for Jx library to use
 */
var Jx = {};

/**
 * reverse inheritance logic to allow for delayed loading of dependencies.
 * Under normal circumstances, Object.extend from Prototype would be used,
 * but in Fusion, widget code is loaded before base class code and the
 * extend function won't work until all the base class code is available.
 * DEPRECATED
 */
Object.inheritFrom = function(destination, source, args) {
    var parent;
    if(typeof source == "function") {
        // get the prototype of the superclass
        parent = source.prototype;
    } else {
        // in this case we're extending with the prototype
        parent = source;
    }
    for (property in parent) {
        if (typeof destination[property] == 'undefined') {
            destination[property] = parent[property];
        }
    }
    if (parent.initialize) {
        parent.initialize.apply(destination, args);
    }
};

(function() {
    /**
     * Before creating the OpenLayers namespace, check to see if
     * OpenLayers.singleFile is true.  This occurs if the
     * OpenLayers/SingleFile.js script is included before this one - as is the
     * case with single file builds.
     */
    var singleFile = (typeof Fusion == "object" && Fusion.singleFile);

    /**
     * Namespace: Fusion
     * This is the top-level namespace of the Fusion library
     */
    window.Fusion = {
        
        /***************************************************************************
         * A set of utility classes that provide common tools for applications
         ***************************************************************************/
        Tool: {},

        /***************************************************************************
         * A set of classes that implement the functionlity required for applications
         ***************************************************************************/
        Widget: {},

        /***************************************************************************
         * A class to provide for event handling at application runtime
         ***************************************************************************/
        Event: {lastEventId: 1},

        /***************************************************************************
         * A set of classes to provide core funtionality
         ***************************************************************************/
        Lib: {},

        /***************************************************************************
         * A set of classes that implement layer functionality for various server 
         * technologies.
         ***************************************************************************/
        Layers: {},

        /***************************************************************************
         * Application scope constants
         ***************************************************************************/
        Constant: {},
    
        /**
         * Pointer to the Application Definition, parsed into all the Fusion 
         * objects: MapGroups, WidgetSets
         */
        applicationDefinition: null,
    
        /**
         * URL of a proxy script if the map server is on a different domain/port
         * that the application template to get around the same-origin policy
         */
        sRedirectScript: "",  
        
        /**
         * A flag to force XHR calls through the proxy, defaults to false
         */
        bForceRedirect: false,
        
        /**
         * the script programming language for server-side programming.  
         * Typically set to 'php' but other server languages may be used. 
         */
        sScriptLang: "php",
        
        /**
         * the locale setting for the browser and for localization of Fusion.
         * This is the same as the locale setting in OpenLayers and is used as 
         * the file name for localization files in the /text subdirectory 
         */
        locale: 'en',
        
        /**
         * the hash of strings used for localization of Fusion.  Strings are
         * indexed by a key which is used in calls to OpenLayers.i18n()
         */
        Strings: {},    //locale specific strings
    
        /** URL to the directory from which fusion.js was loaded */
        fusionURL: null,
    
        /** 
         * configuration object that holds the server configuration as
         * loaded from fusion/config.json
         */
        configuration: null,
    
        /**
        * broker instance for communicating with the mapagent, typically only
        * used with MapGuide
        */
        oBroker: null,
    
        /** An array of scripts that are queued to be loaded */
        aScripts: [],
        /** An array of scripts that are currently being loaded */
        aLoadingScripts: [],
        
        /** 
        * The current state during initialization of the application.  Valid 
        * states are enumerated below 
        */
        loadState: null,
 
        /** API loading has not begun */
        UNLOADED: 0,
        /** Load the configuration file for the application */
        LOAD_CONFIG: 1,
        /** Load the widget files required by the application */
        LOAD_WIDGETS: 2,
        /** Loading is complete */
        LOAD_COMPLETE: 3,
    
        /** 
        * unit related stuff - the following values are the keys into the 
        * unit related arrays below: aUnitPerMeter, aMeterPerUnit, aUnitNames
        * and aUnitAbbr
        */

        /**
         * Constant: UNKNOWN
         * 
         * An unknown unit
         */
        UNKNOWN: 0,
        /**
         * Constant: INCHES
         * 
         * Inch unit
         */
        INCHES: 1,
        /**
         * Constant: FEET
         * 
         * Feet unit
         */
        FEET: 2,
        /**
         * Constant: YARDS
         * 
         * Yard unit
         */
        YARDS: 3,
        /**
         * Constant: MILES
         * 
         * Mile unit
         */
        MILES: 4,
        /**
         * Constant: NAUTICALMILES
         * 
         * Nautical Mile unit
         */
        NAUTICALMILES: 5,
        /**
         * Constant: MILLIMETERS
         * 
         * Millimeter unit
         */
        MILLIMETERS: 6,
        /**
         * Constant: CENTIMETERS
         * 
         * Centimeter unit
         */
        CENTIMETERS: 7,
        /**
         * Constant: METERS
         * 
         * Meter unit
         */
        METERS: 8,
        /**
         * Constant: KILOMETERS
         * 
         * Kilometer unit
         */
        KILOMETERS: 9,
        /**
         * Constant: DEGREES
         * 
         * Degree unit
         */
        DEGREES: 10,
        /**
         * Constant: DECIMALDEGREES
         * 
         * Decimal Degree unit
         */
        DECIMALDEGREES: 11,
        /**
         * Constant: DMS
         * 
         * DMS unit
         */
        DMS: 12,
        /**
         * Constant: PIXELS
         * 
         * Pixel unit
         */
        PIXELS: 13,
        /** 
        * conversion factors to go from the specified units to meters
        */
        aUnitPerMeter: [1.0, /* 0 - UNKNOWN */
                        39.37, /* 1 - INCHES */
                        3.2808, /* 2 - FEET */
                        1.0936133, /* 3 - YARDS */
                        0.00062137, /* 4 - MILES */
                        0.000539956803, /* 5 - NAUTICAL MILES */
                        1000.0, /* 6 - MILLIMETERS */
                        100.0, /* 7 - CENTIMETERS */
                        1.0, /* 8- METERS */
                        0.001, /* 9 - KILOMETERS */
                        0.000009044, /* 10 - DEGREES */
                        0.000009044, /* 11 - DECIMALDEGREES */
                        0.000009044, /* 12 - DMS */
                        1.0 /* 13 - PIXELS */
                        ],
        /** 
        * conversion factors to go from meters to the specified units
        */
        aMeterPerUnit: [1.0, /* 0 - UNKNOWN */
                        0.0254, /* 1 - INCHES */
                        0.3048, /* 2 - FEET */
                        0.9144, /* 3 - YARDS */
                        1609.344, /* 4 - MILES */
                        1852, /* 5 - NAUTICAL MILES */
                        0.001, /* 6 - MILLIMETERS */
                        0.01, /* 7 - CENTIMETERS */
                        1.0, /* 8- METERS */
                        1000.0, /* 9 - KILOMETERS */
                        111061.75033, /* 10 - DEGREES */
                        111061.75033, /* 11 - DECIMALDEGREES */
                        111061.75033, /* 12 - DMS */
                        1.0 /* 13 - PIXELS */],
                        
        /** 
        * the units names spelled out fully
        */
        aUnitNames: ['Unknown','Inches', 'Feet', 'Yards', 'Miles', 'Nautical Miles',
                     'Millimeters', 'Centimeters', 'Meters', 'Kilometers', 
                     'Degrees', 'Decimal Degrees', 'Degrees Minutes Seconds', 'Pixels'],
                     
        /** 
        * unit names abbreviations
        */
        aUnitAbbr: ['unk', 'in', 'ft', 'yd', 'mi', 'nm', 
                    'mm', 'cm', 'm', 'km', 
                    '&deg;', '&deg;', '&deg;', 'px'],
        
        /**
         * Function: initialize
         *
         * Initialize the Fusion application.  This method must be called from
         * the template's onload method once all script files have been loaded.
         * Applications can register for the event {<Fusion.Event.FUSION_INITIALIZED>}
         * to carry out further application initialization when all the Fusion
         * objects have been created and are available.
         *
         * Valid optional parameters that can be passed include:
         * 
         * {String} applicationDefinitionURL: a URL or resource ID 
         *              for an ApplicationDefinition file
         *          
         * {String} sessionId: a session id to start the application
         *              with.  Normally, applications are started without
         *              a session id and create one.  However, sometimes
         *              it is desirable to use an existing session.
         *
         * Parameters:
         * options - {Object} Optional parameters
         *
         *           
         */
        initialize: function(options) {
            TraceKit.remoteFetching = !singleFile;
            TraceKit.report.subscribe(OpenLayers.Function.bind(this.onTraceKitError, this));
        
            options = options || {};
            var sessionIdParam = this.getQueryParam('Session');
            // Override the pre-created sessionId by user specified sessionId(if exist)
            this.sessionId = sessionIdParam || options.sessionId || this.sessionId;
        
            if (options.applicationDefinitionURL) {
                this.applicationDefinitionURL = options.applicationDefinitionURL;            
            } else {
                var queryAppDef = this.getQueryParam('ApplicationDefinition');
                if (queryAppDef) {
                    this.applicationDefinitionURL = queryAppDef.split('+').join(' ');
                    this.appDefJson = null;   //wipe out any preloaded AppDef in a single file build
                } else {
                    this.applicationDefinitionURL = 'ApplicationDefinition.xml';
                }
            }
            if (Fusion._singleFile) {
                //override this method since OL is loaded in the fusion file
                OpenLayers._getScriptLocation = function() {
                    return Fusion.fusionURL + 'lib/OpenLayers/';
                };
            }
            this.initializeLocale();

            /*
             * if the application has been loaded from the same host as
             * fusion is installed in, then technically we don't need to
             * use the redirect script because we conform to the 
             * Same Origin Policy for XmlHttpRequest to work.
             */
            var test = window.location.protocol+'//'+window.location.host;
            var configUrl = 'config.json';
            //if (this.fusionURL.indexOf(test,0) == 0) {
            if ( ((this.fusionURL.indexOf("http://") < 0) || (this.fusionURL.indexOf(test,0) == 0)) && !(this.bForceRedirect)) {
                this.sRedirectScript = '';
            } else {
                this.sRedirectScript = 'redirect.php';
                configUrl += '&method=get';
            }
        
            if (Fusion.configuration) {
                //config.json loaded via single file build
                this.serverSet();
            } else {
                var options = {
                    onSuccess: OpenLayers.Function.bind(this.getConfigCB, this),
                    onFailure: OpenLayers.Function.bind(this.serverFailed, this),
                    method: 'get'
                };
                this.ajaxRequest(configUrl, options);
            }
        },
    
        /**
         * Function: initializeLocale
         *
         * Initialize the Strings hash with the requested locale.  This method
         * simply extends the OpenLayers strings hash with strings specific
         * to Fusion.  Add strings to the files in fusion/text/[locale].json.
         * This method can be called before <Fusion.initialize> if localized
         * strings are required in the template initialization.
         *
         * Parameters:
         * locale - {String} the locale value e.g. 'en-CA' or simply 'en' for English
         *
         */
        initializeLocale: function(locale) {
          OpenLayers.Lang.setCode(locale ? locale : window._FusionLocale);
          this.locale = OpenLayers.Lang.code;
      
          //check if strings are defined for specified locale, if not, set them to the default locale
          if (!OpenLayers.Lang[this.locale]) {
            OpenLayers.Lang[this.locale] = OpenLayers.Lang[OpenLayers.Lang.defaultCode];
          }
          if (!Fusion.Strings[this.locale]) {
            Fusion.Strings[this.locale] = Fusion.Strings[OpenLayers.Lang.defaultCode];
          }
          OpenLayers.Lang[this.locale] = OpenLayers.Util.extend(OpenLayers.Lang[this.locale],Fusion.Strings[this.locale]);
        },
    
        onTraceKitError: function(err) {
            Fusion.reportError(new Fusion.DetailedError(err));
        },
    
        /**
         * Function: setLoadState
         *
         * set the current initialization state of the application.
         * Depending on the state, this will cause various scripts
         * to be loaded.
         *
         * Parameters:
         * state - {Integer} the new loading state
         */
        setLoadState: function(state) {
            //console.log('setLoadState: ' + state);
            this.loadState = state;
            switch(state) {
                case this.LOAD_CONFIG:
                    //console.log('load config');
                    this.loadConfig();
                    break;
                case this.LOAD_WIDGETS:
                    //console.log('load widgets');
                    this.loadQueuedScripts();
                    break;
                case this.LOAD_COMPLETE:
                    //console.log('load complete');
                    if (this.applicationDefinition) {
                        this.applicationDefinition.create();
                        this.triggerEvent(Fusion.Event.FUSION_INITIALIZED);
                    } else {
                        Fusion.reportFatalError('failed to create AppDef object');
                    }
                    break;
            }
        },
        
        /**
         * Function: loadQueuedScripts
         *
         * load any scripts that have been queued for loading.  As the
         * scripts load, they are removed.  When all queued scripts have
         * been loaded, the load state is advanced.  If any of the loaded
         * scripts require other scripts (for inheritance), they will be
         * queued and loaded before the load state is advanced.
         */
        loadQueuedScripts: function() {
            //with the compressed version, all scripts are already laoded so just 
            //increment the load state
            if (this.aScripts.length == 0) {
                this.setLoadState(this.LOAD_COMPLETE);
                return;
            }

            this.aLoadingScripts = [];
            //make a separate array of what is actually being loaded to keep track
            //of them (require adds to aScripts so we can't use that array
            //directly).
            for (var i=0; i<this.aScripts.length; i++) {
                this.aLoadingScripts[i] = this.aScripts[i];
            }
            this.aScripts = [];
            
            //Ted Yang fix: this is because script loaded will change aLoadingScripts. So we have to create a temp collection to store the loading script.
            //See function scriptLoaded
            //Now IE9beta, Safari, Firefox and Chrome all support onload event,
            //but the IE9beta will triggered the event synchronously, so the aLoadingScripts will be changed before the "for sentence" is finished,
            //this caused the needed scripts will not be loaded (nearly half of them) 
            var loadingScriptsTemp = [];
            for (var i = 0; i < this.aLoadingScripts.length; i++) {
                loadingScriptsTemp[i] = this.aLoadingScripts[i];
            }
            //add the script elements to the DOM to cause them to load.
            for (var i = 0; i < loadingScriptsTemp.length; i++) {
                document.getElementsByTagName('head')[0].appendChild(loadingScriptsTemp[i]);
            }

            //if IE or Safari
            //Safari has fixed this problem already, and IE9beta also seems to support the onload event, so this code is only for the old IEs.
            this.checkLoadInterval = window.setInterval(
                    OpenLayers.Function.bind(this.checkLoadingScripts, this), 500);
        },
    
        /**
         * Function: queueScript
         *
         * Insert a new script into the loading queue.  The URL should be relative
         * to the Fusion base url.  The script will not actually be loaded until
         * <Fusion.loadQueuedScripts> is called.
         *
         * Parameters: 
         * url - {String} The url of the script.
         */
        queueScript: function(url) {
            if(!document.getElementById(url) && !this.aScripts[url]) {
                var script = document.createElement('script');
                script.defer = false;
                script.type = "text/javascript";
                //TODO: test url to see if it needs to come from fusion
                script.id = url;
                script.src = this.getFusionURL() + url;
                script.onload = OpenLayers.Function.bind(this.scriptLoaded, this, url);
                script.onerror = OpenLayers.Function.bind(this.scriptFailed, this, script.src);
                this.aScripts[url] = script;
                this.aScripts.push(script);
            }
        },
        
        /**
         * Function: scriptFailed
         *
         * Called when a script fails to load for some reason.
         *
         * Parameters: 
         * url - {String} the url that failed to load
         *
         * TODO: the application probably won't work if a script fails to load
         * so we need to decide how to inform the user and fail gracefully.
         */
        scriptFailed: function(url) {
            Fusion.reportFatalError(OpenLayers.i18n('scriptFailed',{'script':url}));
        },
    
        /**
         * Function: scriptLoaded
         *
         * a script has loaded.  It is removed from the various tracking
         * arrays.  When all requested scripts have been loaded, we check
         * to see if any scripts where required by the ones just loaded.
         * If yes, then we <Fusion.loadQueuedScripts> again, otherwise we advance
         * the load state.
         *
         * Parameters: 
         * url - {String} the url of the script that was loaded.
         */
        scriptLoaded: function(url) {
            for (var i=0; i<this.aLoadingScripts.length;i++) {
                if (this.aLoadingScripts[i].id == url) {
                    this.aLoadingScripts.splice(i,1);
                }
            }
            if (this.aLoadingScripts.length == 0) {
                window.clearInterval(this.checkLoadInterval);
                if (this.aScripts.length > 0) {
                    this.loadQueuedScripts();
                } else {
                    this.setLoadState(this.LOAD_COMPLETE);
                }
            }
        },
        
        /**
         * Function: checkLoadingScripts
         *
         * check if scripts have loaded.  In IE, scripts don't seem to fire the
         * onload event.  Safari also seems to have some problems.
         */
        checkLoadingScripts: function() {
            //In IE9beta, when one script is loaded and the this.aLoadingScripts.length is 0 (for example, there is only 1 item in the aLoadingScripts) , 
            //the onload event will be triggered synchronously,
            //but the "this.checkLoadInterval" is not created yet, so the clearInterval will failed to work in function scriptLoaded,
            //so I add this code to fix this problem
            //if IE9beta fix the problem, and we don't need to support the old IEs, maybe we can remove the checkLoadingScripts
            if (this.aLoadingScripts.length == 0) {
                window.clearInterval(this.checkLoadInterval);
                return;
            }

            var agt=navigator.userAgent.toLowerCase();
            for (var i=this.aLoadingScripts.length-1; i>=0; i--) {
                var s = this.aLoadingScripts[i];
                if (agt.indexOf('safari') != -1) {
                    var widgetName = s.id.substring(s.id.lastIndexOf('/')+1, s.id.indexOf('.js'));
                    var b;
                    eval ('b = typeof Fusion.Widget.'+widgetName+' == "function";');
                    if (b) {
                        this.scriptLoaded(s.id);
                    }
                } else if (s.readyState == 'loaded' ||
                    s.readyState == 'complete') {
                    this.scriptLoaded(s.id);
                }
            }
        },
        
        /**
         * Function: loadConfig
         *
         * asynchronously load the application definition.  For MapGuide, also
         * set up the broker object.  Once the AppDef is loaded/created advance
         * the load state.
         */
        loadConfig: function() {
            var mapAgentUrl = this.getConfigurationItem('mapguide', 'mapAgentUrl');
            if (mapAgentUrl) {
                this.oBroker = new Fusion.Lib.MGBroker();
                var url = mapAgentUrl;
                if (this.sRedirectScript) {
                    url = this.sRedirectScript + '?s=' + url;
                    this.oBroker.method = 'post';
                } else {
                    this.oBroker.method = 'get';
                }
                this.oBroker.setSiteURL(url, "Anonymous", "");
            }
            this.applicationDefinition = new Fusion.Lib.ApplicationDefinition(this.sessionId);
            if (Fusion.appDefJson) {
                Fusion.setLoadState(Fusion.LOAD_WIDGETS);
            }
        },
    
        /**
         * Function: getConfigCB
         *
         * the server has returned the application configuration file that
         * contains enough information to bootstrap the application.
         *
         * Parameters:
         * r - {XMLHttpRequest} an XMLHttpRequest object
         */
        getConfigCB: function(r) {
            if (r.responseText) {  
                eval("this.configuration="+r.responseText);
                this.serverSet();
            } else {
                //console.log('Error parsing configuration file, it is not valid somehow?');
                alert(OpenLayers.i18n('configParseError'));
            }
        },
    
        /**
         * Function: serverSet
         *
         * the server has returned the application configuration file that
         * contains enough information to bootstrap the application.
         *
         * Parameters:
         * r - {XMLHttpRequest} an XMLHttpRequest object
         */
        serverSet: function() {
            var s = this.configuration.mapguide.webTierUrl;
            /* if it is set, use it ... otherwise assume fusion is installed in
             * the default location and compute the web tier url from that
             */
            if (s) {
                var nLength = s.length;
                var slastChar =  s.charAt((nLength-1));
                if (slastChar != '/') {
                    s = s + "/";
                }
            } else {
                var idx = this.fusionURL.lastIndexOf('fusion');
                if (idx == -1) {
                  s = this.fusionURL + "../";   //loaded relatively from within fusion directory
                } else {
                  s = this.fusionURL.substring(0, idx);
                }
            }
            this.configuration.mapguide.webTierUrl = s;
            this.configuration.mapguide.mapAgentUrl = s + 'mapagent/mapagent.fcgi';
        
            //trigger loading stuff ...
            this.setLoadState(this.LOAD_CONFIG);
        },
    
        /**
         * Function: serverFailed
         *
         * the application failed to load the application configuration file.
         * Not much point in continuing, but we can inform the user why this
         * happened.
         *
         * Parameters: 
         * r - {XMLHttpRequest} the XMLHttpRequest object
         *
         * TODO: do something more useful in here?
         */
        serverFailed: function(r) {
            //console.log('error loading server configuration file');
            alert(OpenLayers.i18n('configLoadError')); 
        },
    
        /**
         * Function: ajaxRequest
         *
         * convenience function to issue an {XmlHttpRequest} to the server relative
         * to the Fusion installation.
         *
         * Parameters: 
         * scriptURL - {String} the URL (relative to Fusion) to request
         * options - {Object} optional parameters to send with the request, passed directly to the
         *            <OpenLayers Ajax.Request> function
         */
        ajaxRequest: function(scriptURL, options) {
            //console.log('options.parameters='+options.parameters);
            var r = this.getRedirectScript();
            if (r != '') {
                r = r + '?s=';
            }
            var url = r + this.getFusionURL() + scriptURL;
            if (!options.onException) {
                options.onException = OpenLayers.Function.bind(this.ajaxException, this);
            }
            if (!options.onFailure) {
                options.onFailure = OpenLayers.Function.bind(this.ajaxException, this);
            }
            if (!options.contentType) {
              options.contentType = 'application/x-www-form-urlencoded';
            }
            if (options.parameters && typeof options.parameters == 'string') {
              if (options.parameters.indexOf('?') < 0) {
                  options.parameters = '?' + options.parameters;
              }
            }
            var temp = new OpenLayers.Ajax.Request( url, options);
        },
    
        /**
         * Function: ajaxException
         *
         * trap exceptions produced in handling an ajax response.
         *
         * Parameters: 
         * r - {XmlHttpRequest}
         * e - {Exception}
         */
        ajaxException: function(r, e) {
            try {
                throw new Error(OpenLayers.i18n('ajaxError', {'exception':e.message, 
                                              'filename':e.fileName, 
                                              'line':e.lineNumber,
                                              'response': r.transport.responseText,
                                              'innerStack': e.stack || ''
                                              }));
            } catch (e) {
                TraceKit.report(e);
            }
        },
    
         /**
         * Function: getXmlAsJson
         *
         * Convert XML to JSON using a server-side script
         * for requests that aren't available in JSON.
         *
         * Parameters: 
         * url - {String} the URL of the XML object to be converted;
         *       this can be any URL and is not prepended with the Fusion URL
         * callback - {Function} a callback function to be called if 
         *            the request is successful
         *
         * boolean indicator if the content is JSON or not.
        */
        getXmlAsJson: function(url, callback) {
            var options = {
                method: 'get',
                onSuccess: OpenLayers.Function.bind(this.xml2json, this, callback),
                onFailure: OpenLayers.Function.bind(this.ajaxException, this)
            };
            var temp = new OpenLayers.Ajax.Request(url, options);
        },
    
        /**
         * Function: parseJSON
         *
         * Parses the given JSON string to a javascript object. This is safer than using eval() as there is no possibility of
         * arbitrary code execution
         *
         * Parameters: 
         * str - {String} The JSON string to parse
         *
        */
        parseJSON: function(str) {
            var o;
            if (typeof(JSON) != 'undefined' && JSON.parse != null) {
                o = JSON.parse(str);
            } else {
                eval('o='+str); //TODO: Still evil for now, but the evil is localized to this one spot. Replace with a JSON.parse shim
            }
            return o;
        },
        
        /**
         * Function: trim
         *
         * Utility function to trim a given string of any trailing whitespace
         */
        trim: function(str) {
            return (typeof String.prototype.trim == 'undefined') ? str.replace(/^\s+|\s+$/g, '') : str.trim();
        },
    
        /**
         * Function: xml2json
         *
         * Callback method to convert the XHR response into an actual json object
         * and triggers the call to the callback method
         *
         * Parameters: 
         * callback - {Function} callback method to be executed on success and will be passed a parsed json object
         * r - {XmlHttpRequest} the XmlHttpRequest object
         * json - {Boolean} boolean indicator if the content is JSON or not (set by OpenLayers)
         *
        */
        xml2json: function(callback, r, json) {
          if (json) {
            var o = Fusion.parseJSON(r.responseText);
            callback(o);
          } else {
            if (r.status >= 400) {
                var msg = r.transport.responseText;
                if (msg == "") {
                    msg = OpenLayers.i18n('serverNotAvailable');
                }
                Fusion.reportFatalError(OpenLayers.i18n('invalidXMLDocument', {'msg':msg, 'url':r.request.url}));
                return;
            }

            var options = {
              onSuccess: callback,
              method: 'post',
              parameters: {'xml': encodeURIComponent(r.responseText)}
            };
            var sl = Fusion.getScriptLanguage();
            Fusion.ajaxRequest('common/'+sl+'/Xml2JSON.'+sl, options);
          }
        },
    
        /**
         * Function: getMapByName
         *
         * return a map widget with the given name
         *
         * Parameters: 
         * name - {String} The map name to return
         *
         * Return: 
         * {<Fusion.Widget.Map>} a map object or null if not found.
         */
        getMapByName: function(name) {
            var map = null;
            if (this.applicationDefinition) {
                map = this.applicationDefinition.getMapByName(name);
            }
            return map;
        },
    
        /**
         * Function: getMapById
         *
         * return a map widget that is associated with the given DOM element
         * by id.
         *
         * Parameters: 
         * id - {String} The map id to return
         *
         * Return: 
         * {<Fusion.Widget.Map>} a map object or null if not found.
         */
        getMapById: function(id) {
            var map = null;
            if (this.applicationDefinition) {
                map = this.applicationDefinition.getMapById(id);
            }
            return map;
        },
    
        /**
         * Function: getMapByIndice
         *
         * return the map widget at the given index if there are more than one
         * map elements specified in the AppDef
         *
         * Parameters: 
         * indice - {String} The map indice to return
         *
         * Return: 
         * {<Fusion.Widget.Map>} a map object or null if not found.
         */
        getMapByIndice: function(indice) {
            var map = null;
            if (this.applicationDefinition) {
                map = this.applicationDefinition.getMapByIndice(indice);
            }
            return map;
        },
    
        /**
         * Function: getWidgetById
         *
         * return a reference to a widget by the id of the DOM
         * Element that it was created in.
         *
         * Parameters: 
         * id - {String} the DOM id to get the widget for
         *
         * Return: 
         * {<Fusion.Widget>} the widget or null
         */
        getWidgetById: function(id) {
            /*
            var widget = null;
            if (this.applicationDefinition) {
                widget = this.applicationDefinition.getWidgetById(id);
            }
            return widget;
            */
            var d = $(id);
            if (d && d.widget) {
                return d.widget;
            } else {
                return null;
            }
        },
    
        /**
         * Function getWidgetsByType
         *
         * returns an array of widgets by type.
         *
         * Parameters: 
         * type - {String} the type of widget to get references to
         *
         * Return: 
         * {Array} an array of widgets, which may be empty
         */
        getWidgetsByType: function(type) {
            var widgets = [];
            if (this.applicationDefinition) {
                widgets = this.applicationDefinition.getWidgetsByType(type);
            }
            return widgets;
        },
    
        /**
         * Function getSearchDefinitions
         *
         * returns the SearchDefinition objects as defined in the AppDef.
         *
         * Return: 
         * {Array} an array of searchDefinitions, which may be empty
         */
        getSearchDefinitions: function() {
            if (this.applicationDefinition) {
                return this.applicationDefinition.searchDefinitions;
            } else {
                return {};
            }
        },
    
        /**
         * Function getSearchCategories
         *
         * returns the SearchCategory objects as defined in the AppDef.
         *
         * Return:
         * {Array} an array of searchCategories, which may be empty
         */
        getSearchCategories: function() {
            if (this.applicationDefinition) {
                return this.applicationDefinition.searchCategories;
            } else {
                return {};
            }
        },
    
        /**
         * Function getApplicationDefinitionURL
         *
         * returns the URL of the Application Definition
         *
         * Return: 
         * {String} a URL
         */
        getApplicationDefinitionURL: function() { return this.applicationDefinitionURL; },

        /**
         * Function: getApplicationURL
         *
         * returns the applicaiton's absolute URL minus the filename.html part
         *
         * Return: 
         * {String} a URL
         */
        getApplicationURL: function() { 
          var path = window.location.pathname;
          var idx = path.lastIndexOf('/');
          if (idx>=0) {
            path = path.substr(0,idx+1);
          }
          return window.location.protocol + "//" + window.location.host + path;
        },
    
        /**
         * Function: getFusionURL
         *
         * returns the application's absolute URL minus the filename.html part
         *
         * Return: 
         * {String} a URL
         */
        getFusionURL: function() {return this.fusionURL;},
    
        /**
         * Function: getConfigurationItem
         *
         * returns individual elements out of the Fusion config.json file
         *
         * Parameters: 
         * arch - {String} the server architecture (mapguide or mapserver)
         * key - {String} the config item to be returned
         *
         * Return: 
         * {String} a the value for the key of null if not found
         */
        getConfigurationItem: function(arch, key) { 
            if (this.configuration[arch] && (this.configuration[arch][key]!='undefined')) { 
                return this.configuration[arch][key]; 
            } 
            return null; 
        },
    
        /**
         * Function: getScriptLanguage
         *
         * Accessor to return the server-side script language
         *
         * Return: 
         * {String} the script language e.g. 'php'
         */
        getScriptLanguage: function() { return this.configuration.general.scriptLanguage; },
    
        /**
         * Function: getRedirectScript
         *
         * Accessor to return the URL of the redirect script
         *
         * Return: 
         * {String} a URL
         */
        getRedirectScript: function() { return this.sRedirectScript; },
    
        /**
         * Function: getBroker
         *
         * Accessor to return the broker object (for MapGuide only).  The broker
         * is used to prepare various queries to the MapGuide server.
         *
         * Return: 
         * {<Fusion.Lib.MGBroker>} the broker object
         */
        getBroker: function() { return this.oBroker; },
    
        /**
         * Function: require
         *
         * Marks a JavaScript file as required which puts it into the queue for
         * loading during Fusion initialization.  Widget code is included
         * through this mecahnism
         *
         * Parameters: 
         * url - {String} the URL of the JS file to be loaded
         *
         */
        require: function(url) { this.queueScript(url); },
    
        /**
         * Function: reportFatalError
         *
         * Triggers the <Fusion.Event.FUSION_ERROR> and passes along a <Fusion.DetailedError>
         * object to the callback functions registered for this event.  Widgets
         * call <Fusion.reportError> to inform the system of errors.
         * Applications will typically register an event listener for the error
         * event and do something to report the error to the user.  
         * By default, errors are not reported since there is no listener
         *
         * Unlike reportError, errors reported through reportFatalError carry valuable information
         * such as stack traces, which errors sent via reportError will not contain
         *
         * Parameters:
         * msg - {String} The error message
         */
        reportFatalError: function(msg) {
            try {
                throw new Error(msg);
            } catch (e) {
                TraceKit.report(e);
            }
        },
    
        /**
         * Function: reportError
         *
         * Triggers the <Fusion.Event.FUSION_ERROR> and passes along the error
         * object to the callback functions registered for this event.  Widgets
         * call <Fusion.reportError> to inform the system of errors.
         * Applications will typically register an event listener for the error
         * event and do something to report the error to the user.  
         * By default, errors are not reported since there is no listener
         *
         * For fatal errors, it is recommended to use reportFatalError instead
         *
         * Parameters:
         * o - {Object} the error object which is typically a string or a <Fusion.Error> object
         */
        reportError: function(o) { this.triggerEvent(Fusion.Event.FUSION_ERROR, o); },
    
        /**
         * Function: unitFromName
         *
         * returns index into the units array for the given unit name or 
         * abbreviation
         *
         * Parameters: 
         * unit - {String} the units name to look up
         *
         * Return: 
         * {Integer} index into the units array
         */
        unitFromName: function(unit) {
            switch(unit.toLowerCase()) {
                case 'unknown':
                    return Fusion.UNKNOWN;
                case 'inches':
                case 'inch':
                case 'in':
                    return Fusion.INCHES;
                case 'feet':
                case 'ft':
                    return Fusion.FEET;
                case 'yards':
                case 'yard':
                case 'yd':
                    return Fusion.YARDS;
                case 'miles':
                case 'mile':
                case 'mi':
                    return Fusion.MILES;
                case 'nautical miles':
                case 'nautical mile':
                case 'nm':
                    return Fusion.NAUTICALMILES;
                case 'millimeters':
                case 'millimeter':
                case 'mm':
                    return Fusion.MILLIMETERS;
                case 'centimeters':
                case 'centimeter':
                case 'cm':
                    return Fusion.CENTIMETERS;
                case 'meters':
                case 'meter':
                case 'm':
                    return Fusion.METERS;
                case 'kilometers':
                case 'kilometer':
                case 'km':
                    return Fusion.KILOMETERS;
                case 'degrees':
                case 'degree':
                case 'deg':
                    return Fusion.DEGREES;
                case 'decimal degrees':
                case 'dd':
                    return Fusion.DECIMALDEGREES;
                case 'degrees minutes seconds':
                case 'dms':
                    return Fusion.DMS;
                case 'pixels':
                case 'pixel':
                case 'px':
                    return Fusion.PIXELS;
                default:
                    return Fusion.UNKNOWN;
            }
        },
        
        /**
         * Function: unitSystem
         *
         * Given a unit, this method returns if the units system is one of:
         * imperial, metric, degrees or device units
         *
         * Parameters: 
         * unit - {Integer} the units array index
         *
         * Return: 
         * {String} the units system
         */
        unitSystem: function(unit) {
            switch(unit) {

                case Fusion.INCHES:
                case Fusion.FEET:
                case Fusion.YARDS:
                case Fusion.MILES:
                case Fusion.NAUTICALMILES:
                    return 'imperial';
                case Fusion.MILLIMETERS:
                case Fusion.CENTIMETERS:
                case Fusion.METERS:
                case Fusion.KILOMETERS:
                    return 'metric';
                case Fusion.DEGREES:
                case Fusion.DECIMALDEGREES:
                case Fusion.DMS:
                    return 'deg';
                case Fusion.UNKNOWN:
                case Fusion.PIXELS:
                default:
                    return 'device';
            }
        },
    
        /**
         * Function: unitName
         *
         * Given a unit, this method returns the units name
         *
         * Parameters: 
         * unit - {Integer} the units array index
         *
         * Return: 
         * {String} the units name
         */
        unitName: function(unit) {
            if (unit >= Fusion.UNKNOWN && unit <= Fusion.PIXELS) {
              return (Fusion.aUnitNames[unit]);
            }
            return 'Unknown';
        },
        
        /**
         * Function: unitAbbr
         *
         * Given a unit, this method returns the units abbreviation
         *
         * Parameters:
         * unit - {Integer} the units array index
         *
         * Return: 
         * {String} the units abbreviation
         */
        unitAbbr: function(unit) {
            if (unit >= Fusion.UNKNOWN && unit <= Fusion.PIXELS) {
              return (Fusion.aUnitAbbr[unit]);
            }
            return 'Unk';
        },

        /**
         * Function: toMeter
         *
         * Converts a length value from native units into meters.  This is the
         * identity transform if the input units are meters
         *
         * Parameters:
         * unit - {Integer} the units array index
         * value - {Float} the value to be converted
         *
         * Return: 
         * {Float} the value in meters
         */
        toMeter: function(unit, value) {
            if (unit == Fusion.UNKNOWN) {
                return value;
            }
            if (unit > Fusion.UNKNOWN && unit < Fusion.PIXELS) {
              return (Fusion.aMeterPerUnit[unit] * value);
            }
            return false;
        },

        /**
         * Function: fromMeter
         *
         * Converts a length value from meters into native units.  This is the
         * identity transform if the native units are meters
         *
         * Parameters:
         * unit - {Integer} the units array index
         * value - {Float} the value to be converted
         *
         * Return: 
         * {Float} the value in native units
         */
        fromMeter: function(unit, value) {
            if (unit == Fusion.UNKNOWN) {
                return value;
            }
            if (unit > Fusion.UNKNOWN && unit < Fusion.PIXELS) {
                return (Fusion.aUnitPerMeter[unit] * value);
            }
            return false;
        },

        /**
         * Function: convert
         *
         * Converts a length value from one unit system into another.
         *
         * Parameters:
         * unitsIn - {Integer} the units array index of the input
         * unitsOut - {Integer} the units array index of the output
         * value - {Float} the value to be converted
         *
         * Return: 
         * {Float} the value in output units
         */
        convert: function(unitsIn, unitsOut, value) {
            if (unitsIn >= Fusion.UNKNOWN && unitsIn < Fusion.PIXELS && 
                unitsOut >= Fusion.UNKNOWN && unitsOut < Fusion.PIXELS) {
                return Fusion.fromMeter(unitsOut, Fusion.toMeter(unitsIn, value));
            }
            return false;
        },
    
      /**
         * Function: initUnits
         *
         * initializes the meters per unit values when a new map is loaded.  
         * Some systems make different assumptions for the conversion of degrees
         * to meters so this makes sure both Fusion and OpenLayers are using 
         * the same value.
         *
         * Parameters: 
         * metersPerUnit - {Float} the value returned by LoadMap.php
         *                 for meters per unit
         */
        initUnits: function(metersPerUnit) {
            var eps = 1000;
            if (Math.abs(metersPerUnit-Fusion.aMeterPerUnit[Fusion.DEGREES]) < eps){
                Fusion.aMeterPerUnit[Fusion.DEGREES] = metersPerUnit;
                Fusion.aMeterPerUnit[Fusion.DECIMALDEGREES] = metersPerUnit;
                Fusion.aMeterPerUnit[Fusion.DMX] = metersPerUnit;
                var inverse = 1.0/metersPerUnit;
                Fusion.aUnitPerMeter[Fusion.DEGREES] = inverse;
                Fusion.aUnitPerMeter[Fusion.DECIMALDEGREES] = inverse;
                Fusion.aUnitPerMeter[Fusion.DMX] = inverse;
            
                var inPerUnit = OpenLayers.INCHES_PER_UNIT.m * metersPerUnit;
                OpenLayers.INCHES_PER_UNIT["dd"] = inPerUnit;
                OpenLayers.INCHES_PER_UNIT["degrees"] = inPerUnit;
                OpenLayers.INCHES_PER_UNIT["Degree"] = inPerUnit;
            }
        },
    
        /**
        * Function: getClosestUnits
        *
        * find the OpenLayers units identifier given the Fusion metersPerUnit value
        *
        * Parameters:
        * metersPerUnit - {Float} the value returned by LoadMap.php for meters per unit
        */
        getClosestUnits: function(metersPerUnit) {
            var units = "degrees";
            var minDiff = 100000000;
            for (var key in OpenLayers.INCHES_PER_UNIT) {
                var newDiff = Math.abs((metersPerUnit * 39.3701) - OpenLayers.INCHES_PER_UNIT[key]);
                if(newDiff < minDiff)
                {
                    minDiff = newDiff;
                    units = key;
                }
            }
            return units;
        },

        /**
         * Function: addWidgetStyleSheet
         *
         * Dynamically load a CSS stylesheet.  The url will be prepended with
         * the Fusion URL.
         *
         * Parameters: 
         * url - {String} the URL of the CSS file to be loaded
         */
        addWidgetStyleSheet: function(url) {
            var lnk = document.createElement('link');
            var hd = document.getElementsByTagName('HEAD')[0];
            hd.insertBefore(lnk, Fusion._scriptObject);
            lnk.type = 'text/css';
            lnk.rel='stylesheet';
            lnk.href = Fusion.getFusionURL()+url;
        },

        /**
         * Function: parseQueryString
         *
         * An initialization time function to parse the application URL parameters
         * and stores them in an array.  They can be retrieved using 
         * {<Fusion.getQueryParam>}
         *
         * Return: 
         * {Array} an array of the query params from when the page was loaded
         */
        parseQueryString: function() {
            this.queryParams = [];
            var s=window.location.search;
            if (s!='') {
                s=s.substring( 1 );
                var p=s.split('&');
                for (var i=0;i<p.length;i++) {
                    var q=p[i].split('=');
                    this.queryParams[q[0].toLowerCase()]=decodeURIComponent(q[1]);
                }
            }
            return this.queryParams;
        },

        /**
         * Function: getQueryParam
         *
         * Returns the query parameter value for a given parameter name
         *
         * Parameters: 
         * p - {String} the parameter to lookup
         *
         * Return: 
         * parameter value or the empty string '' if not found
         */
        getQueryParam: function(p) {
            if (!this.queryParams) {
                this.parseQueryString();
            }
            p = p.toLowerCase();
            if (this.queryParams[p] && typeof this.queryParams[p] == 'string') {
                return this.queryParams[p];
            } else {
                return '';
            }
        },
    
        /**
         * Property: _scriptName
         * {String} Relative path of this script.
         */
        _scriptName: "lib/fusionSF",

        /**
         * Function: _getScriptLocation
         * Return the path to this script for bootstrapping Fusion.
         *
         * Return:
         * {String} Path to this script
         */
        _getScriptLocation: function () {
            Fusion.fusionURL = null;
            var scriptName = Fusion._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); 
                    if (index > -1) {
                        Fusion._scriptObject = scripts[i];
                        var img = document.createElement('img');
                        img.src = src.slice(0, index)+'lib/a_pixel.png';
                        var s = img.src;
                        var n = s.lastIndexOf('lib/a_pixel.png');
                        Fusion.fusionURL = s.slice(0, n);
                        Fusion.aPixel = img;
                        break;
                    }
                }
            }
            if (!this.fusionURL) {
                alert('failed to determine fusionURL using script name '+Fusion._scriptName+'.  Initialization aborted');
                return;
            }
            return Fusion.fusionURL;
        }
    };

    /*********************************************************************************/
    /* actual bootstrap execution code follows */
    
    Fusion._singleFile = singleFile;
    var host = Fusion._getScriptLocation();
    Jx.aPixel = Fusion.aPixel;

    var getXmlHttpRequest = function(){
        try { return new XMLHttpRequest(); }
        catch (e) { try { return new ActiveXObject('Msxml2.XMLHTTP'); } 
        catch (e) { try { return new ActiveXObject('Microsoft.XMLHTTP'); }
        catch (e) { return  null; }}}
    }
    
    //determine the language to use and add resource bundles to be loaded to the core scripts
    var locale = navigator.language ?
                  navigator.language.substring(0,2):    //e.g. en-CA becomes just en                  
                  navigator.userLanguage.substring(0,2);//only use the prefix part for now, 
    var s = window.location.search;
    var idx = s.indexOf('locale=');
    if (idx>0) {
      var temp = s.substring(idx+7);
      var idx2 = temp.indexOf('&');
      locale = temp.substring(0,idx2<0?temp.length:idx2);
    }
    else if(navigator.userAgent.indexOf("MSIE")>0) {
        //This is because IE will always returns OS language instead of browser language. So we need the server side's help.
        var xhr = getXmlHttpRequest();
        xhr.open("GET", Fusion.fusionURL+"common/php/GetLocale.php",false);
        xhr.send(null);
        if(xhr.status == 200) {
            var returnLocale = xhr.responseText;
            if(returnLocale.length >= 2) {
                locale = returnLocale.substring(0, 2);
            }
        }      
    }
    window._FusionLocale = locale;
    
    if (!Fusion._singleFile) {
        var coreScripts = [ 'lib/TraceKit/TraceKit.js',
                            'lib/jxLib/jxlib.uncompressed.js',
                            'lib/Proj4js/proj4js-combined.js',
                            'layers/MapGuide/php/csmap2proj4.js',
                            'lib/OpenLayers/OpenLayers.js',
                            'lib/EventMgr.js',
                            'lib/Error.js',
                            'lib/ApplicationDefinition.js',
                            'lib/MGBroker.js',
                            'lib/Widget.js',
                            'lib/Map.js',
                            'lib/MapMessage.js',
                            'layers/Layers.js',
                            'lib/Search.js',
                            'text/en.json'];
        if (locale != 'en') {
            coreScripts.push('lib/OpenLayers/Lang/'+locale+'.js');
            coreScripts.push('text/'+locale+'.json');
        }
        
        var allScriptTags = new Array(coreScripts.length);
        for (var i = 0; i < coreScripts.length; i++) {
            allScriptTags[i] = "<script src='" + host + coreScripts[i] +
                                   "'></script>"; 
        }
        document.write(allScriptTags.join(""));
    }
    
    /*********************************************************************************/
    /* Load commercial layer API scripts */
    
    /*This function is extracted from Fusion to get the MapAgentUrl */ 
    var getAgentUrl = function() { 
        /* 
        * if the application has been loaded from the same host as 
        * fusion is installed in, then technically we don't need to 
        * use the redirect script because we conform to the  
        * Same Origin Policy for XmlHttpRequest to work. 
        */ 
        var fusionURL = Fusion._getScriptLocation(); 
        var testUrl = window.location.protocol + '//' + window.location.host; 
        var configUrl = 'config.json'; 
    
        if (((Fusion.fusionURL.indexOf("http://") < 0) || (Fusion.fusionURL.indexOf(testUrl, 0) == 0)) && !(Fusion.bForceRedirect)) { 
            Fusion.sRedirectScript = ''; 
        } 
        else { 
            Fusion.sRedirectScript = 'redirect.php'; 
            configUrl += '&method=get'; 
        } 
    
        var r = Fusion.getRedirectScript(); 
        if (r != '') { 
            r = r + '?s='; 
        } 
    
        var fusionURL = r + Fusion.getFusionURL() + configUrl; 
    
        var xhr = getXmlHttpRequest(); 
        xhr.open("GET", fusionURL, false); 
        xhr.send(null); 
    
        eval("Fusion.configuration=" + xhr.responseText); 
        var s = Fusion.configuration.mapguide.webTierUrl; 
        /* if it is set, use it ... otherwise assume fusion is installed in 
        * the default location and compute the web tier url from that 
        */ 
        if (s) { 
            var nLength = s.length; 
            var slastChar = s.charAt((nLength - 1)); 
            if (slastChar != '/') { 
                s = s + "/"; 
            } 
        } 
        else { 
            var idx = Fusion.fusionURL.lastIndexOf('fusion'); 
            if (idx == -1) { 
                s = Fusion.fusionURL + "../";   //loaded relatively from within fusion directory 
            } 
            else { 
                s = Fusion.fusionURL.substring(0, idx); 
            } 
        } 
        Fusion.configuration.mapguide.mapAgentUrl = s + 'mapagent/mapagent.fcgi'; 
        return Fusion.configuration.mapguide.mapAgentUrl; 
    }
    
    var addElement = function(element) { 
        if (!element) { 
            return; 
        } 
        var src = element.textContent; 
    
        // For IE Browser 
        if (!src) { 
            src = element.text; 
        } 
    
        document.writeln('<script src="' + src + '"><\/script>'); 
    }

    /**
     * Function: createSessionId
     *
     * Pre-create a session to avoid the 401 Unauthorized dialog
     *
     */
    var createSessionId = function(user, pass){
        var xhr = getXmlHttpRequest();
        var url = Fusion.fusionURL+"layers/MapGuide/php/CreateSession.php";
        if (user != null && user != "")
        {
            url += "?username=" + user;
            if (pass != null && pass != "")
                url += "&password=" + pass;
        }
        xhr.open("GET", url, false);
        xhr.send(null); 
        var o = Fusion.parseJSON(xhr.responseText);
        Fusion.sessionId = o.sessionId;
        var version = o.siteVersion;
        var bits = version.split('.');
        Fusion.siteVersion = new Array(parseInt(bits[0]),
                                       parseInt(bits[1]),
                                       parseInt(bits[2]),
                                       parseInt(bits[3])
        );
    }

    var appDefUrl = Fusion.getQueryParam('ApplicationDefinition'); 
    if (appDefUrl && (
        appDefUrl.indexOf('Library') == 0 ||
        appDefUrl.indexOf('Session') == 0)) {
        
        var fetchAppDef = function(appDefUrl, sessionId) {
            var xhr = new getXmlHttpRequest(); 
            var mapAgentUrl = getAgentUrl(); 
            xhr.open("GET", mapAgentUrl + "?OPERATION=GETRESOURCECONTENT&VERSION=1.0.0&LOCALE=en&CLIENTAGENT=MapGuide+Developer&RESOURCEID=" + appDefUrl + "&FORMAT=text%2Fxml&SESSION="+ sessionId, false); 
            xhr.send(null); 
            var appDefXML = xhr.responseXML.documentElement; 

            if (appDefXML) {
                var googleElement = appDefXML.getElementsByTagName("GoogleScript")[0]; 
                var yahooElement = appDefXML.getElementsByTagName("YahooScript")[0]; 
                var veElement = appDefXML.getElementsByTagName("VirtualEarthScript")[0];
                var osmElement = appDefXML.getElementsByTagName("OpenStreetMapScript")[0];

                addElement(googleElement); 
                addElement(yahooElement); 
                addElement(veElement);
                addElement(osmElement);
            }
        };
        
        var getSiteVersion = function(sessionId) {
            var xhr = new getXmlHttpRequest(); 
            var mapAgentUrl = getAgentUrl(); 
            xhr.open("GET", mapAgentUrl + "?OPERATION=GETSITEVERSION&VERSION=1.0.0&LOCALE=en&CLIENTAGENT=MapGuide+Developer&SESSION="+ sessionId, false); 
            xhr.send(null); 
            var verXML = xhr.responseXML.documentElement; 
            var el = verXML.getElementsByTagName("Version")[0];
            var version = el.textContent || el.text;
            var bits = version.split('.');
            return new Array(parseInt(bits[0]),
                             parseInt(bits[1]),
                             parseInt(bits[2]),
                             parseInt(bits[3]));
        };
        
        var passedSessionId = Fusion.getQueryParam('Session'); 
        if (passedSessionId == null || passedSessionId == "" ){
            createSessionId(Fusion.getQueryParam("Username"), Fusion.getQueryParam("Password"));
        } else {
            Fusion.sessionId = passedSessionId;
            Fusion.siteVersion = getSiteVersion(passedSessionId);
        }
        fetchAppDef(appDefUrl, Fusion.sessionId);
    }
    
})();

/*
 TraceKit - Cross brower stack traces - github.com/occ/TraceKit
 MIT license
*/

(function(window, undefined) {


var TraceKit = {};
var _oldTraceKit = window.TraceKit;

// global reference to slice
var _slice = [].slice;
var UNKNOWN_FUNCTION = '?';


/**
 * _has, a better form of hasOwnProperty
 * Example: _has(MainHostObject, property) === true/false
 *
 * @param {Object} host object to check property
 * @param {string} key to check
 */
function _has(object, key) {
    return Object.prototype.hasOwnProperty.call(object, key);
}

function _isUndefined(what) {
    return typeof what === 'undefined';
}

/**
 * TraceKit.noConflict: Export TraceKit out to another variable
 * Example: var TK = TraceKit.noConflict()
 */
TraceKit.noConflict = function noConflict() {
    window.TraceKit = _oldTraceKit;
    return TraceKit;
};

/**
 * TraceKit.wrap: Wrap any function in a TraceKit reporter
 * Example: func = TraceKit.wrap(func);
 *
 * @param {Function} func Function to be wrapped
 * @return {Function} The wrapped func
 */
TraceKit.wrap = function traceKitWrapper(func) {
    function wrapped() {
        try {
            return func.apply(this, arguments);
        } catch (e) {
            TraceKit.report(e);
            throw e;
        }
    }
    return wrapped;
};

/**
 * TraceKit.report: cross-browser processing of unhandled exceptions
 *
 * Syntax:
 *   TraceKit.report.subscribe(function(stackInfo) { ... })
 *   TraceKit.report.unsubscribe(function(stackInfo) { ... })
 *   TraceKit.report(exception)
 *   try { ...code... } catch(ex) { TraceKit.report(ex); }
 *
 * Supports:
 *   - Firefox: full stack trace with line numbers, plus column number
 *              on top frame; column number is not guaranteed
 *   - Opera:   full stack trace with line and column numbers
 *   - Chrome:  full stack trace with line and column numbers
 *   - Safari:  line and column number for the top frame only; some frames
 *              may be missing, and column number is not guaranteed
 *   - IE:      line and column number for the top frame only; some frames
 *              may be missing, and column number is not guaranteed
 *
 * In theory, TraceKit should work on all of the following versions:
 *   - IE5.5+ (only 8.0 tested)
 *   - Firefox 0.9+ (only 3.5+ tested)
 *   - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
 *     Exceptions Have Stacktrace to be enabled in opera:config)
 *   - Safari 3+ (only 4+ tested)
 *   - Chrome 1+ (only 5+ tested)
 *   - Konqueror 3.5+ (untested)
 *
 * Requires TraceKit.computeStackTrace.
 *
 * Tries to catch all unhandled exceptions and report them to the
 * subscribed handlers. Please note that TraceKit.report will rethrow the
 * exception. This is REQUIRED in order to get a useful stack trace in IE.
 * If the exception does not reach the top of the browser, you will only
 * get a stack trace from the point where TraceKit.report was called.
 *
 * Handlers receive a stackInfo object as described in the
 * TraceKit.computeStackTrace docs.
 */
TraceKit.report = (function reportModuleWrapper() {
    var handlers = [],
        lastException = null,
        lastExceptionStack = null;

    /**
     * Add a crash handler.
     * @param {Function} handler
     */
    function subscribe(handler) {
        installGlobalHandler();
        handlers.push(handler);
    }

    /**
     * Remove a crash handler.
     * @param {Function} handler
     */
    function unsubscribe(handler) {
        for (var i = handlers.length - 1; i >= 0; --i) {
            if (handlers[i] === handler) {
                handlers.splice(i, 1);
            }
        }
    }

    /**
     * Dispatch stack information to all handlers.
     * @param {Object.<string, *>} stack
     */
    function notifyHandlers(stack, windowError) {
        var exception = null;
        if (windowError && !TraceKit.collectWindowErrors) {
          return;
        }
        for (var i in handlers) {
            if (_has(handlers, i)) {
                try {
                    handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
                } catch (inner) {
                    exception = inner;
                }
            }
        }

        if (exception) {
            throw exception;
        }
    }

    var _oldOnerrorHandler, _onErrorHandlerInstalled;

    /**
     * Ensures all global unhandled exceptions are recorded.
     * Supported by Gecko and IE.
     * @param {string} message Error message.
     * @param {string} url URL of script that generated the exception.
     * @param {(number|string)} lineNo The line number at which the error
     * occurred.
     */
    function traceKitWindowOnError(message, url, lineNo) {
        var stack = null;

        if (lastExceptionStack) {
            TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
            stack = lastExceptionStack;
            lastExceptionStack = null;
            lastException = null;
        } else {
            var location = {
                'url': url,
                'line': lineNo
            };
            location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
            location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
            stack = {
                'mode': 'onerror',
                'message': message,
                'url': document.location.href,
                'stack': [location],
                'useragent': navigator.userAgent
            };
        }

        notifyHandlers(stack, 'from window.onerror');

        if (_oldOnerrorHandler) {
            return _oldOnerrorHandler.apply(this, arguments);
        }

        return false;
    }

    function installGlobalHandler ()
    {
        if (_onErrorHandlerInstalled === true) {
            return;
        }
        _oldOnerrorHandler = window.onerror;
        window.onerror = traceKitWindowOnError;
        _onErrorHandlerInstalled = true;
    }

    /**
     * Reports an unhandled Error to TraceKit.
     * @param {Error} ex
     */
    function report(ex) {
        var args = _slice.call(arguments, 1);
        if (lastExceptionStack) {
            if (lastException === ex) {
                return; // already caught by an inner catch block, ignore
            } else {
                var s = lastExceptionStack;
                lastExceptionStack = null;
                lastException = null;
                notifyHandlers.apply(null, [s, null].concat(args));
            }
        }

        var stack = TraceKit.computeStackTrace(ex);
        lastExceptionStack = stack;
        lastException = ex;

        // If the stack trace is incomplete, wait for 2 seconds for
        // slow slow IE to see if onerror occurs or not before reporting
        // this exception; otherwise, we will end up with an incomplete
        // stack trace
        window.setTimeout(function () {
            if (lastException === ex) {
                lastExceptionStack = null;
                lastException = null;
                notifyHandlers.apply(null, [stack, null].concat(args));
            }
        }, (stack.incomplete ? 2000 : 0));

        throw ex; // re-throw to propagate to the top level (and cause window.onerror)
    }

    report.subscribe = subscribe;
    report.unsubscribe = unsubscribe;
    return report;
}());

/**
 * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
 *
 * Syntax:
 *   s = TraceKit.computeStackTrace.ofCaller([depth])
 *   s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
 * Returns:
 *   s.name              - exception name
 *   s.message           - exception message
 *   s.stack[i].url      - JavaScript or HTML file URL
 *   s.stack[i].func     - function name, or empty for anonymous functions (if guessing did not work)
 *   s.stack[i].args     - arguments passed to the function, if known
 *   s.stack[i].line     - line number, if known
 *   s.stack[i].column   - column number, if known
 *   s.stack[i].context  - an array of source code lines; the middle element corresponds to the correct line#
 *   s.mode              - 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace
 *
 * Supports:
 *   - Firefox:  full stack trace with line numbers and unreliable column
 *               number on top frame
 *   - Opera 10: full stack trace with line and column numbers
 *   - Opera 9-: full stack trace with line numbers
 *   - Chrome:   full stack trace with line and column numbers
 *   - Safari:   line and column number for the topmost stacktrace element
 *               only
 *   - IE:       no line numbers whatsoever
 *
 * Tries to guess names of anonymous functions by looking for assignments
 * in the source code. In IE and Safari, we have to guess source file names
 * by searching for function bodies inside all page scripts. This will not
 * work for scripts that are loaded cross-domain.
 * Here be dragons: some function names may be guessed incorrectly, and
 * duplicate functions may be mismatched.
 *
 * TraceKit.computeStackTrace should only be used for tracing purposes.
 * Logging of unhandled exceptions should be done with TraceKit.report,
 * which builds on top of TraceKit.computeStackTrace and provides better
 * IE support by utilizing the window.onerror event to retrieve information
 * about the top of the stack.
 *
 * Note: In IE and Safari, no stack trace is recorded on the Error object,
 * so computeStackTrace instead walks its *own* chain of callers.
 * This means that:
 *  * in Safari, some methods may be missing from the stack trace;
 *  * in IE, the topmost function in the stack trace will always be the
 *    caller of computeStackTrace.
 *
 * This is okay for tracing (because you are likely to be calling
 * computeStackTrace from the function you want to be the topmost element
 * of the stack trace anyway), but not okay for logging unhandled
 * exceptions (because your catch block will likely be far away from the
 * inner function that actually caused the exception).
 *
 * Tracing example:
 *     function trace(message) {
 *         var stackInfo = TraceKit.computeStackTrace.ofCaller();
 *         var data = message + "\n";
 *         for(var i in stackInfo.stack) {
 *             var item = stackInfo.stack[i];
 *             data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n";
 *         }
 *         if (window.console)
 *             console.info(data);
 *         else
 *             alert(data);
 *     }
 */
TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
    var debug = false,
        sourceCache = {};

    /**
     * Attempts to retrieve source code via XMLHttpRequest, which is used
     * to look up anonymous function names.
     * @param {string} url URL of source code.
     * @return {string} Source contents.
     */
    function loadSource(url) {
        if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.
            return '';
        }
        try {
            var getXHR = function() {
                try {
                    return new window.XMLHttpRequest();
                } catch (e) {
                    // explicitly bubble up the exception if not found
                    return new window.ActiveXObject('Microsoft.XMLHTTP');
                }
            };

            var request = getXHR();
            request.open('GET', url, false);
            request.send('');
            return request.responseText;
        } catch (e) {
            return '';
        }
    }

    /**
     * Retrieves source code from the source code cache.
     * @param {string} url URL of source code.
     * @return {Array.<string>} Source contents.
     */
    function getSource(url) {
        if (!_has(sourceCache, url)) {
            // URL needs to be able to fetched within the acceptable domain.  Otherwise,
            // cross-domain errors will be triggered.
            var source = '';
            if (url.indexOf(document.domain) !== -1) {
                source = loadSource(url);
            }
            sourceCache[url] = source ? source.split('\n') : [];
        }

        return sourceCache[url];
    }

    /**
     * Tries to use an externally loaded copy of source code to determine
     * the name of a function by looking at the name of the variable it was
     * assigned to, if any.
     * @param {string} url URL of source code.
     * @param {(string|number)} lineNo Line number in source code.
     * @return {string} The function name, if discoverable.
     */
    function guessFunctionName(url, lineNo) {
        var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
            reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
            line = '',
            maxLines = 10,
            source = getSource(url),
            m;

        if (!source.length) {
            return UNKNOWN_FUNCTION;
        }

        // Walk backwards from the first line in the function until we find the line which
        // matches the pattern above, which is the function definition
        for (var i = 0; i < maxLines; ++i) {
            line = source[lineNo - i] + line;

            if (!_isUndefined(line)) {
                if ((m = reGuessFunction.exec(line))) {
                    return m[1];
                } else if ((m = reFunctionArgNames.exec(line))) {
                    return m[1];
                }
            }
        }

        return UNKNOWN_FUNCTION;
    }

    /**
     * Retrieves the surrounding lines from where an exception occurred.
     * @param {string} url URL of source code.
     * @param {(string|number)} line Line number in source code to centre
     * around for context.
     * @return {?Array.<string>} Lines of source code.
     */
    function gatherContext(url, line) {
        var source = getSource(url);

        if (!source.length) {
            return null;
        }

        var context = [],
            // linesBefore & linesAfter are inclusive with the offending line.
            // if linesOfContext is even, there will be one extra line
            //   *before* the offending line.
            linesBefore = Math.floor(TraceKit.linesOfContext / 2),
            // Add one extra line if linesOfContext is odd
            linesAfter = linesBefore + (TraceKit.linesOfContext % 2),
            start = Math.max(0, line - linesBefore - 1),
            end = Math.min(source.length, line + linesAfter - 1);

        line -= 1; // convert to 0-based index

        for (var i = start; i < end; ++i) {
            if (!_isUndefined(source[i])) {
                context.push(source[i]);
            }
        }

        return context.length > 0 ? context : null;
    }

    /**
     * Escapes special characters, except for whitespace, in a string to be
     * used inside a regular expression as a string literal.
     * @param {string} text The string.
     * @return {string} The escaped string literal.
     */
    function escapeRegExp(text) {
        return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
    }

    /**
     * Escapes special characters in a string to be used inside a regular
     * expression as a string literal. Also ensures that HTML entities will
     * be matched the same as their literal friends.
     * @param {string} body The string.
     * @return {string} The escaped string.
     */
    function escapeCodeAsRegExpForMatchingInsideHTML(body) {
        return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
    }

    /**
     * Determines where a code fragment occurs in the source code.
     * @param {RegExp} re The function definition.
     * @param {Array.<string>} urls A list of URLs to search.
     * @return {?Object.<string, (string|number)>} An object containing
     * the url, line, and column number of the defined function.
     */
    function findSourceInUrls(re, urls) {
        var source, m;
        for (var i = 0, j = urls.length; i < j; ++i) {
            // console.log('searching', urls[i]);
            if ((source = getSource(urls[i])).length) {
                source = source.join('\n');
                if ((m = re.exec(source))) {
                    // console.log('Found function in ' + urls[i]);

                    return {
                        'url': urls[i],
                        'line': source.substring(0, m.index).split('\n').length,
                        'column': m.index - source.lastIndexOf('\n', m.index) - 1
                    };
                }
            }
        }

        // console.log('no match');

        return null;
    }

    /**
     * Determines at which column a code fragment occurs on a line of the
     * source code.
     * @param {string} fragment The code fragment.
     * @param {string} url The URL to search.
     * @param {(string|number)} line The line number to examine.
     * @return {?number} The column number.
     */
    function findSourceInLine(fragment, url, line) {
        var source = getSource(url),
            re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
            m;

        line -= 1;

        if (source && source.length > line && (m = re.exec(source[line]))) {
            return m.index;
        }

        return null;
    }

    /**
     * Determines where a function was defined within the source code.
     * @param {(Function|string)} func A function reference or serialized
     * function definition.
     * @return {?Object.<string, (string|number)>} An object containing
     * the url, line, and column number of the defined function.
     */
    function findSourceByFunctionBody(func) {
        var urls = [window.location.href],
            scripts = document.getElementsByTagName('script'),
            body,
            code = '' + func,
            codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
            eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
            re,
            parts,
            result;

        for (var i = 0; i < scripts.length; ++i) {
            var script = scripts[i];
            if (script.src) {
                urls.push(script.src);
            }
        }

        if (!(parts = codeRE.exec(code))) {
            re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
        }

        // not sure if this is really necessary, but I don�t have a test
        // corpus large enough to confirm that and it was in the original.
        else {
            var name = parts[1] ? '\\s+' + parts[1] : '',
                args = parts[2].split(',').join('\\s*,\\s*');

            body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
            re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');
        }

        // look for a normal function definition
        if ((result = findSourceInUrls(re, urls))) {
            return result;
        }

        // look for an old-school event handler function
        if ((parts = eventRE.exec(code))) {
            var event = parts[1];
            body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);

            // look for a function defined in HTML as an onXXX handler
            re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');

            if ((result = findSourceInUrls(re, urls[0]))) {
                return result;
            }

            // look for ???
            re = new RegExp(body);

            if ((result = findSourceInUrls(re, urls))) {
                return result;
            }
        }

        return null;
    }

    // Contents of Exception in various browsers.
    //
    // SAFARI:
    // ex.message = Can't find variable: qq
    // ex.line = 59
    // ex.sourceId = 580238192
    // ex.sourceURL = http://...
    // ex.expressionBeginOffset = 96
    // ex.expressionCaretOffset = 98
    // ex.expressionEndOffset = 98
    // ex.name = ReferenceError
    //
    // FIREFOX:
    // ex.message = qq is not defined
    // ex.fileName = http://...
    // ex.lineNumber = 59
    // ex.stack = ...stack trace... (see the example below)
    // ex.name = ReferenceError
    //
    // CHROME:
    // ex.message = qq is not defined
    // ex.name = ReferenceError
    // ex.type = not_defined
    // ex.arguments = ['aa']
    // ex.stack = ...stack trace...
    //
    // INTERNET EXPLORER:
    // ex.message = ...
    // ex.name = ReferenceError
    //
    // OPERA:
    // ex.message = ...message... (see the example below)
    // ex.name = ReferenceError
    // ex.opera#sourceloc = 11  (pretty much useless, duplicates the info in ex.message)
    // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'

    /**
     * Computes stack trace information from the stack property.
     * Chrome and Gecko use this property.
     * @param {Error} ex
     * @return {?Object.<string, *>} Stack trace information.
     */
    function computeStackTraceFromStackProp(ex) {
        if (!ex.stack) {
            return null;
        }

        var chrome = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
            gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\d+)(?::(\d+))?\s*$/i,
            lines = ex.stack.split('\n'),
            stack = [],
            parts,
            element,
            reference = /^(.*) is undefined$/.exec(ex.message);

        for (var i = 0, j = lines.length; i < j; ++i) {
            if ((parts = gecko.exec(lines[i]))) {
                element = {
                    'url': parts[3],
                    'func': parts[1] || UNKNOWN_FUNCTION,
                    'args': parts[2] ? parts[2].split(',') : '',
                    'line': +parts[4],
                    'column': parts[5] ? +parts[5] : null
                };
            } else if ((parts = chrome.exec(lines[i]))) {
                element = {
                    'url': parts[2],
                    'func': parts[1] || UNKNOWN_FUNCTION,
                    'line': +parts[3],
                    'column': parts[4] ? +parts[4] : null
                };
            } else {
                continue;
            }

            if (!element.func && element.line) {
                element.func = guessFunctionName(element.url, element.line);
            }

            if (element.line) {
                element.context = gatherContext(element.url, element.line);
            }

            stack.push(element);
        }

        if (stack[0] && stack[0].line && !stack[0].column && reference) {
            stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
        }

        if (!stack.length) {
            return null;
        }

        return {
            'mode': 'stack',
            'name': ex.name,
            'message': ex.message,
            'url': document.location.href,
            'stack': stack,
            'useragent': navigator.userAgent
        };
    }

    /**
     * Computes stack trace information from the stacktrace property.
     * Opera 10 uses this property.
     * @param {Error} ex
     * @return {?Object.<string, *>} Stack trace information.
     */
    function computeStackTraceFromStacktraceProp(ex) {
        // Access and store the stacktrace property before doing ANYTHING
        // else to it because Opera is not very good at providing it
        // reliably in other circumstances.
        var stacktrace = ex.stacktrace;

        var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,
            lines = stacktrace.split('\n'),
            stack = [],
            parts;

        for (var i = 0, j = lines.length; i < j; i += 2) {
            if ((parts = testRE.exec(lines[i]))) {
                var element = {
                    'line': +parts[1],
                    'column': +parts[2],
                    'func': parts[3] || parts[4],
                    'args': parts[5] ? parts[5].split(',') : [],
                    'url': parts[6]
                };

                if (!element.func && element.line) {
                    element.func = guessFunctionName(element.url, element.line);
                }
                if (element.line) {
                    try {
                        element.context = gatherContext(element.url, element.line);
                    } catch (exc) {}
                }

                if (!element.context) {
                    element.context = [lines[i + 1]];
                }

                stack.push(element);
            }
        }

        if (!stack.length) {
            return null;
        }

        return {
            'mode': 'stacktrace',
            'name': ex.name,
            'message': ex.message,
            'url': document.location.href,
            'stack': stack,
            'useragent': navigator.userAgent
        };
    }

    /**
     * NOT TESTED.
     * Computes stack trace information from an error message that includes
     * the stack trace.
     * Opera 9 and earlier use this method if the option to show stack
     * traces is turned on in opera:config.
     * @param {Error} ex
     * @return {?Object.<string, *>} Stack information.
     */
    function computeStackTraceFromOperaMultiLineMessage(ex) {
        // Opera includes a stack trace into the exception message. An example is:
        //
        // Statement on line 3: Undefined variable: undefinedFunc
        // Backtrace:
        //   Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
        //         undefinedFunc(a);
        //   Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
        //           zzz(x, y, z);
        //   Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
        //           yyy(a, a, a);
        //   Line 1 of function script
        //     try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
        //   ...

        var lines = ex.message.split('\n');
        if (lines.length < 4) {
            return null;
        }

        var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i,
            lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i,
            lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
            stack = [],
            scripts = document.getElementsByTagName('script'),
            inlineScriptBlocks = [],
            parts,
            i,
            len,
            source;

        for (i in scripts) {
            if (_has(scripts, i) && !scripts[i].src) {
                inlineScriptBlocks.push(scripts[i]);
            }
        }

        for (i = 2, len = lines.length; i < len; i += 2) {
            var item = null;
            if ((parts = lineRE1.exec(lines[i]))) {
                item = {
                    'url': parts[2],
                    'func': parts[3],
                    'line': +parts[1]
                };
            } else if ((parts = lineRE2.exec(lines[i]))) {
                item = {
                    'url': parts[3],
                    'func': parts[4]
                };
                var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
                var script = inlineScriptBlocks[parts[2] - 1];
                if (script) {
                    source = getSource(item.url);
                    if (source) {
                        source = source.join('\n');
                        var pos = source.indexOf(script.innerText);
                        if (pos >= 0) {
                            item.line = relativeLine + source.substring(0, pos).split('\n').length;
                        }
                    }
                }
            } else if ((parts = lineRE3.exec(lines[i]))) {
                var url = window.location.href.replace(/#.*$/, ''),
                    line = parts[1];
                var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1]));
                source = findSourceInUrls(re, [url]);
                item = {
                    'url': url,
                    'line': source ? source.line : line,
                    'func': ''
                };
            }

            if (item) {
                if (!item.func) {
                    item.func = guessFunctionName(item.url, item.line);
                }
                var context = gatherContext(item.url, item.line);
                var midline = (context ? context[Math.floor(context.length / 2)] : null);
                if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) {
                    item.context = context;
                } else {
                    // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
                    item.context = [lines[i + 1]];
                }
                stack.push(item);
            }
        }
        if (!stack.length) {
            return null; // could not parse multiline exception message as Opera stack trace
        }

        return {
            'mode': 'multiline',
            'name': ex.name,
            'message': lines[0],
            'url': document.location.href,
            'stack': stack,
            'useragent': navigator.userAgent
        };
    }

    /**
     * Adds information about the first frame to incomplete stack traces.
     * Safari and IE require this to get complete data on the first frame.
     * @param {Object.<string, *>} stackInfo Stack trace information from
     * one of the compute* methods.
     * @param {string} url The URL of the script that caused an error.
     * @param {(number|string)} lineNo The line number of the script that
     * caused an error.
     * @param {string=} message The error generated by the browser, which
     * hopefully contains the name of the object that caused the error.
     * @return {boolean} Whether or not the stack information was
     * augmented.
     */
    function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
        var initial = {
            'url': url,
            'line': lineNo
        };

        if (initial.url && initial.line) {
            stackInfo.incomplete = false;

            if (!initial.func) {
                initial.func = guessFunctionName(initial.url, initial.line);
            }

            if (!initial.context) {
                initial.context = gatherContext(initial.url, initial.line);
            }

            var reference = / '([^']+)' /.exec(message);
            if (reference) {
                initial.column = findSourceInLine(reference[1], initial.url, initial.line);
            }

            if (stackInfo.stack.length > 0) {
                if (stackInfo.stack[0].url === initial.url) {
                    if (stackInfo.stack[0].line === initial.line) {
                        return false; // already in stack trace
                    } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
                        stackInfo.stack[0].line = initial.line;
                        stackInfo.stack[0].context = initial.context;
                        return false;
                    }
                }
            }

            stackInfo.stack.unshift(initial);
            stackInfo.partial = true;
            return true;
        } else {
            stackInfo.incomplete = true;
        }

        return false;
    }

    /**
     * Computes stack trace information by walking the arguments.caller
     * chain at the time the exception occurred. This will cause earlier
     * frames to be missed but is the only way to get any stack trace in
     * Safari and IE. The top frame is restored by
     * {@link augmentStackTraceWithInitialElement}.
     * @param {Error} ex
     * @return {?Object.<string, *>} Stack trace information.
     */
    function computeStackTraceByWalkingCallerChain(ex, depth) {
        var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
            stack = [],
            funcs = {},
            recursion = false,
            parts,
            item,
            source;

        for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {
            if (curr === computeStackTrace || curr === TraceKit.report) {
                // console.log('skipping internal function');
                continue;
            }

            item = {
                'url': null,
                'func': UNKNOWN_FUNCTION,
                'line': null,
                'column': null
            };

            if (curr.name) {
                item.func = curr.name;
            } else if ((parts = functionName.exec(curr.toString()))) {
                item.func = parts[1];
            }

            if ((source = findSourceByFunctionBody(curr))) {
                item.url = source.url;
                item.line = source.line;

                if (item.func === UNKNOWN_FUNCTION) {
                    item.func = guessFunctionName(item.url, item.line);
                }

                var reference = / '([^']+)' /.exec(ex.message || ex.description);
                if (reference) {
                    item.column = findSourceInLine(reference[1], source.url, source.line);
                }
            }

            if (funcs['' + curr]) {
                recursion = true;
            }else{
                funcs['' + curr] = true;
            }

            stack.push(item);
        }

        if (depth) {
            // console.log('depth is ' + depth);
            // console.log('stack is ' + stack.length);
            stack.splice(0, depth);
        }

        var result = {
            'mode': 'callers',
            'name': ex.name,
            'message': ex.message,
            'url': document.location.href,
            'stack': stack,
            'useragent': navigator.userAgent
        };
        augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
        return result;
    }

    /**
     * Computes a stack trace for an exception.
     * @param {Error} ex
     * @param {(string|number)=} depth
     */
    function computeStackTrace(ex, depth) {
        var stack = null;
        depth = (depth == null ? 0 : +depth);

        try {
            // This must be tried first because Opera 10 *destroys*
            // its stacktrace property if you try to access the stack
            // property first!!
            stack = computeStackTraceFromStacktraceProp(ex);
            if (stack) {
                return stack;
            }
        } catch (e) {
            if (debug) {
                throw e;
            }
        }

        try {
            stack = computeStackTraceFromStackProp(ex);
            if (stack) {
                return stack;
            }
        } catch (e) {
            if (debug) {
                throw e;
            }
        }

        try {
            stack = computeStackTraceFromOperaMultiLineMessage(ex);
            if (stack) {
                return stack;
            }
        } catch (e) {
            if (debug) {
                throw e;
            }
        }

        try {
            stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
            if (stack) {
                return stack;
            }
        } catch (e) {
            if (debug) {
                throw e;
            }
        }

        return {
            'mode': 'failed'
        };
    }

    /**
     * Logs a stacktrace starting from the previous call and working down.
     * @param {(number|string)=} depth How many frames deep to trace.
     * @return {Object.<string, *>} Stack trace information.
     */
    function computeStackTraceOfCaller(depth) {
        depth = (depth == null ? 0 : +depth) + 1; // "+ 1" because "ofCaller" should drop one frame
        try {
            throw new Error();
        } catch (ex) {
            return computeStackTrace(ex, depth + 1);
        }
    }

    computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
    computeStackTrace.guessFunctionName = guessFunctionName;
    computeStackTrace.gatherContext = gatherContext;
    computeStackTrace.ofCaller = computeStackTraceOfCaller;

    return computeStackTrace;
}());

/**
 * Extends support for global error handling for asynchronous browser
 * functions. Adopted from Closure Library's errorhandler.js
 */
(function extendToAsynchronousCallbacks() {
    var _helper = function _helper(fnName) {
        var originalFn = window[fnName];
        window[fnName] = function traceKitAsyncExtension() {
            // Make a copy of the arguments
            var args = _slice.call(arguments);
            var originalCallback = args[0];
            if (typeof (originalCallback) === 'function') {
                args[0] = TraceKit.wrap(originalCallback);
            }
            // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
            // also only supports 2 argument and doesn't care what "this" is, so we
            // can just call the original function directly.
            if (originalFn.apply) {
                return originalFn.apply(this, args);
            } else {
                return originalFn(args[0], args[1]);
            }
        };
    };

    _helper('setTimeout');
    _helper('setInterval');
}());

//Default options:
if (!TraceKit.remoteFetching) {
  TraceKit.remoteFetching = true;
}
if (!TraceKit.collectWindowErrors) {
  TraceKit.collectWindowErrors = true;
}
if (!TraceKit.linesOfContext || TraceKit.linesOfContext < 1) {
  // 5 lines before, the offending line, 5 lines after
  TraceKit.linesOfContext = 11;
}



// Export to global object
window.TraceKit = TraceKit;

}(window));/******************************************************************************
 * MooTools 1.2.6
 * Copyright (c) 2006-2007 [Valerio Proietti](http://mad4milk.net/).
 * MooTools is distributed under an MIT-style license.
 ******************************************************************************
 * reset.css - Copyright (c) 2006, Yahoo! Inc. All rights reserved.
 * Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt
 ******************************************************************************
 * Jx UI Library, 3.0alpha
 * Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/
/*
---

name: Core

description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.

license: MIT-style license.

copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).

authors: The MooTools production team (http://mootools.net/developers/)

inspiration:
  - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
  - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)

provides: [MooTools, Native, Hash.base, Array.each, $util]

...
*/

var MooTools = {
	'version': '1.2.6',
	'build': 'a3be7a0ef5205eb655c4ee11e8cbedcb3f2ffa95'
};

var Native = function(options){
	options = options || {};
	var name = options.name;
	var legacy = options.legacy;
	var protect = options.protect;
	var methods = options.implement;
	var generics = options.generics;
	var initialize = options.initialize;
	var afterImplement = options.afterImplement || function(){};
	var object = initialize || legacy;
	generics = generics !== false;

	object.constructor = Native;
	object.$family = {name: 'native'};
	if (legacy && initialize) object.prototype = legacy.prototype;
	object.prototype.constructor = object;

	if (name){
		var family = name.toLowerCase();
		object.prototype.$family = {name: family};
		Native.typize(object, family);
	}

	var add = function(obj, name, method, force){
		if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
		if (generics) Native.genericize(obj, name, protect);
		afterImplement.call(obj, name, method);
		return obj;
	};

	object.alias = function(a1, a2, a3){
		if (typeof a1 == 'string'){
			var pa1 = this.prototype[a1];
			if ((a1 = pa1)) return add(this, a2, a1, a3);
		}
		for (var a in a1) this.alias(a, a1[a], a2);
		return this;
	};

	object.implement = function(a1, a2, a3){
		if (typeof a1 == 'string') return add(this, a1, a2, a3);
		for (var p in a1) add(this, p, a1[p], a2);
		return this;
	};

	if (methods) object.implement(methods);

	return object;
};

Native.genericize = function(object, property, check){
	if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
		var args = Array.prototype.slice.call(arguments);
		return object.prototype[property].apply(args.shift(), args);
	};
};

Native.implement = function(objects, properties){
	for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
};

Native.typize = function(object, family){
	if (!object.type) object.type = function(item){
		return ($type(item) === family);
	};
};

(function(){
	var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
	for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});

	var types = {'boolean': Boolean, 'native': Native, 'object': Object};
	for (var t in types) Native.typize(types[t], t);

	var generics = {
		'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
		'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
	};
	for (var g in generics){
		for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
	}
})();

var Hash = new Native({

	name: 'Hash',

	initialize: function(object){
		if ($type(object) == 'hash') object = $unlink(object.getClean());
		for (var key in object) this[key] = object[key];
		return this;
	}

});

Hash.implement({

	forEach: function(fn, bind){
		for (var key in this){
			if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
		}
	},

	getClean: function(){
		var clean = {};
		for (var key in this){
			if (this.hasOwnProperty(key)) clean[key] = this[key];
		}
		return clean;
	},

	getLength: function(){
		var length = 0;
		for (var key in this){
			if (this.hasOwnProperty(key)) length++;
		}
		return length;
	}

});

Hash.alias('forEach', 'each');

Array.implement({

	forEach: function(fn, bind){
		for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
	}

});

Array.alias('forEach', 'each');

function $A(iterable){
	if (iterable.item){
		var l = iterable.length, array = new Array(l);
		while (l--) array[l] = iterable[l];
		return array;
	}
	return Array.prototype.slice.call(iterable);
};

function $arguments(i){
	return function(){
		return arguments[i];
	};
};

function $chk(obj){
	return !!(obj || obj === 0);
};

function $clear(timer){
	clearTimeout(timer);
	clearInterval(timer);
	return null;
};

function $defined(obj){
	return (obj != undefined);
};

function $each(iterable, fn, bind){
	var type = $type(iterable);
	((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
};

function $empty(){};

function $extend(original, extended){
	for (var key in (extended || {})) original[key] = extended[key];
	return original;
};

function $H(object){
	return new Hash(object);
};

function $lambda(value){
	return ($type(value) == 'function') ? value : function(){
		return value;
	};
};

function $merge(){
	var args = Array.slice(arguments);
	args.unshift({});
	return $mixin.apply(null, args);
};

function $mixin(mix){
	for (var i = 1, l = arguments.length; i < l; i++){
		var object = arguments[i];
		if ($type(object) != 'object') continue;
		for (var key in object){
			var op = object[key], mp = mix[key];
			mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
		}
	}
	return mix;
};

function $pick(){
	for (var i = 0, l = arguments.length; i < l; i++){
		if (arguments[i] != undefined) return arguments[i];
	}
	return null;
};

function $random(min, max){
	return Math.floor(Math.random() * (max - min + 1) + min);
};

function $splat(obj){
	var type = $type(obj);
	return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
};

var $time = Date.now || function(){
	return +new Date;
};

function $try(){
	for (var i = 0, l = arguments.length; i < l; i++){
		try {
			return arguments[i]();
		} catch(e){}
	}
	return null;
};

function $type(obj){
	if (obj == undefined) return false;
	if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
	if (obj.nodeName){
		switch (obj.nodeType){
			case 1: return 'element';
			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
		}
	} else if (typeof obj.length == 'number'){
		if (obj.callee) return 'arguments';
		else if (obj.item) return 'collection';
	}
	return typeof obj;
};

function $unlink(object){
	var unlinked;
	switch ($type(object)){
		case 'object':
			unlinked = {};
			for (var p in object) unlinked[p] = $unlink(object[p]);
		break;
		case 'hash':
			unlinked = new Hash(object);
		break;
		case 'array':
			unlinked = [];
			for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
		break;
		default: return object;
	}
	return unlinked;
};


/*
---

name: Browser

description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.

license: MIT-style license.

requires: [Native, $util]

provides: [Browser, Window, Document, $exec]

...
*/

var Browser = $merge({

	Engine: {name: 'unknown', version: 0},

	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},

	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},

	Plugins: {},

	Engines: {

		presto: function(){
			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
		},

		trident: function(){
			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
		},

		webkit: function(){
			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
		},

		gecko: function(){
			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
		}

	}

}, Browser || {});

Browser.Platform[Browser.Platform.name] = true;

Browser.detect = function(){

	for (var engine in this.Engines){
		var version = this.Engines[engine]();
		if (version){
			this.Engine = {name: engine, version: version};
			this.Engine[engine] = this.Engine[engine + version] = true;
			break;
		}
	}

	return {name: engine, version: version};

};

Browser.detect();

Browser.Request = function(){
	return $try(function(){
		return new XMLHttpRequest();
	}, function(){
		return new ActiveXObject('MSXML2.XMLHTTP');
	}, function(){
		return new ActiveXObject('Microsoft.XMLHTTP');
	});
};

Browser.Features.xhr = !!(Browser.Request());

Browser.Plugins.Flash = (function(){
	var version = ($try(function(){
		return navigator.plugins['Shockwave Flash'].description;
	}, function(){
		return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
	}) || '0 r0').match(/\d+/g);
	return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
})();

function $exec(text){
	if (!text) return text;
	if (window.execScript){
		window.execScript(text);
	} else {
		var script = document.createElement('script');
		script.setAttribute('type', 'text/javascript');
		script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
		document.head.appendChild(script);
		document.head.removeChild(script);
	}
	return text;
};

Native.UID = 1;

var $uid = (Browser.Engine.trident) ? function(item){
	return (item.uid || (item.uid = [Native.UID++]))[0];
} : function(item){
	return item.uid || (item.uid = Native.UID++);
};

var Window = new Native({

	name: 'Window',

	legacy: (Browser.Engine.trident) ? null: window.Window,

	initialize: function(win){
		$uid(win);
		if (!win.Element){
			win.Element = $empty;
			if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
			win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
		}
		win.document.window = win;
		return $extend(win, Window.Prototype);
	},

	afterImplement: function(property, value){
		window[property] = Window.Prototype[property] = value;
	}

});

Window.Prototype = {$family: {name: 'window'}};

new Window(window);

var Document = new Native({

	name: 'Document',

	legacy: (Browser.Engine.trident) ? null: window.Document,

	initialize: function(doc){
		$uid(doc);
		doc.head = doc.getElementsByTagName('head')[0];
		doc.html = doc.getElementsByTagName('html')[0];
		if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
			doc.execCommand("BackgroundImageCache", false, true);
		});
		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){
			doc.window.detachEvent('onunload', arguments.callee);
			doc.head = doc.html = doc.window = null;
		});
		return $extend(doc, Document.Prototype);
	},

	afterImplement: function(property, value){
		document[property] = Document.Prototype[property] = value;
	}

});

Document.Prototype = {$family: {name: 'document'}};

new Document(document);


/*
---

name: Array

description: Contains Array Prototypes like each, contains, and erase.

license: MIT-style license.

requires: [$util, Array.each]

provides: Array

...
*/

Array.implement({

	every: function(fn, bind){
		for (var i = 0, l = this.length; i < l; i++){
			if (!fn.call(bind, this[i], i, this)) return false;
		}
		return true;
	},

	filter: function(fn, bind){
		var results = [];
		for (var i = 0, l = this.length; i < l; i++){
			if (fn.call(bind, this[i], i, this)) results.push(this[i]);
		}
		return results;
	},

	clean: function(){
		return this.filter($defined);
	},

	indexOf: function(item, from){
		var len = this.length;
		for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
			if (this[i] === item) return i;
		}
		return -1;
	},

	map: function(fn, bind){
		var results = [];
		for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
		return results;
	},

	some: function(fn, bind){
		for (var i = 0, l = this.length; i < l; i++){
			if (fn.call(bind, this[i], i, this)) return true;
		}
		return false;
	},

	associate: function(keys){
		var obj = {}, length = Math.min(this.length, keys.length);
		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
		return obj;
	},

	link: function(object){
		var result = {};
		for (var i = 0, l = this.length; i < l; i++){
			for (var key in object){
				if (object[key](this[i])){
					result[key] = this[i];
					delete object[key];
					break;
				}
			}
		}
		return result;
	},

	contains: function(item, from){
		return this.indexOf(item, from) != -1;
	},

	extend: function(array){
		for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
		return this;
	},

	getLast: function(){
		return (this.length) ? this[this.length - 1] : null;
	},

	getRandom: function(){
		return (this.length) ? this[$random(0, this.length - 1)] : null;
	},

	include: function(item){
		if (!this.contains(item)) this.push(item);
		return this;
	},

	combine: function(array){
		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
		return this;
	},

	erase: function(item){
		for (var i = this.length; i--; i){
			if (this[i] === item) this.splice(i, 1);
		}
		return this;
	},

	empty: function(){
		this.length = 0;
		return this;
	},

	flatten: function(){
		var array = [];
		for (var i = 0, l = this.length; i < l; i++){
			var type = $type(this[i]);
			if (!type) continue;
			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
		}
		return array;
	},

	hexToRgb: function(array){
		if (this.length != 3) return null;
		var rgb = this.map(function(value){
			if (value.length == 1) value += value;
			return value.toInt(16);
		});
		return (array) ? rgb : 'rgb(' + rgb + ')';
	},

	rgbToHex: function(array){
		if (this.length < 3) return null;
		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
		var hex = [];
		for (var i = 0; i < 3; i++){
			var bit = (this[i] - 0).toString(16);
			hex.push((bit.length == 1) ? '0' + bit : bit);
		}
		return (array) ? hex : '#' + hex.join('');
	}

});


/*
---

name: Function

description: Contains Function Prototypes like create, bind, pass, and delay.

license: MIT-style license.

requires: [Native, $util]

provides: Function

...
*/

try {
	delete Function.prototype.bind;
} catch(e){}

Function.implement({

	extend: function(properties){
		for (var property in properties) this[property] = properties[property];
		return this;
	},

	create: function(options){
		var self = this;
		options = options || {};
		return function(event){
			var args = options.arguments;
			args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
			if (options.event) args = [event || window.event].extend(args);
			var returns = function(){
				return self.apply(options.bind || null, args);
			};
			if (options.delay) return setTimeout(returns, options.delay);
			if (options.periodical) return setInterval(returns, options.periodical);
			if (options.attempt) return $try(returns);
			return returns();
		};
	},

	run: function(args, bind){
		return this.apply(bind, $splat(args));
	},

	pass: function(args, bind){
		return this.create({bind: bind, arguments: args});
	},

	bind: function(bind, args){
		return this.create({bind: bind, arguments: args});
	},

	bindWithEvent: function(bind, args){
		return this.create({bind: bind, arguments: args, event: true});
	},

	attempt: function(args, bind){
		return this.create({bind: bind, arguments: args, attempt: true})();
	},

	delay: function(delay, bind, args){
		return this.create({bind: bind, arguments: args, delay: delay})();
	},

	periodical: function(periodical, bind, args){
		return this.create({bind: bind, arguments: args, periodical: periodical})();
	}

});


/*
---

name: Number

description: Contains Number Prototypes like limit, round, times, and ceil.

license: MIT-style license.

requires: [Native, $util]

provides: Number

...
*/

Number.implement({

	limit: function(min, max){
		return Math.min(max, Math.max(min, this));
	},

	round: function(precision){
		precision = Math.pow(10, precision || 0);
		return Math.round(this * precision) / precision;
	},

	times: function(fn, bind){
		for (var i = 0; i < this; i++) fn.call(bind, i, this);
	},

	toFloat: function(){
		return parseFloat(this);
	},

	toInt: function(base){
		return parseInt(this, base || 10);
	}

});

Number.alias('times', 'each');

(function(math){
	var methods = {};
	math.each(function(name){
		if (!Number[name]) methods[name] = function(){
			return Math[name].apply(null, [this].concat($A(arguments)));
		};
	});
	Number.implement(methods);
})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);


/*
---

name: String

description: Contains String Prototypes like camelCase, capitalize, test, and toInt.

license: MIT-style license.

requires: Native

provides: String

...
*/

try {
	delete String.prototype.contains;
} catch(e){}

String.implement({

	test: function(regex, params){
		return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
	},

	contains: function(string, separator){
		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
	},

	trim: function(){
		return this.replace(/^\s+|\s+$/g, '');
	},

	clean: function(){
		return this.replace(/\s+/g, ' ').trim();
	},

	camelCase: function(){
		return this.replace(/-\D/g, function(match){
			return match.charAt(1).toUpperCase();
		});
	},

	hyphenate: function(){
		return this.replace(/[A-Z]/g, function(match){
			return ('-' + match.charAt(0).toLowerCase());
		});
	},

	capitalize: function(){
		return this.replace(/\b[a-z]/g, function(match){
			return match.toUpperCase();
		});
	},

	escapeRegExp: function(){
		return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
	},

	toInt: function(base){
		return parseInt(this, base || 10);
	},

	toFloat: function(){
		return parseFloat(this);
	},

	hexToRgb: function(array){
		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
		return (hex) ? hex.slice(1).hexToRgb(array) : null;
	},

	rgbToHex: function(array){
		var rgb = this.match(/\d{1,3}/g);
		return (rgb) ? rgb.rgbToHex(array) : null;
	},

	stripScripts: function(option){
		var scripts = '';
		var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
			scripts += arguments[1] + '\n';
			return '';
		});
		if (option === true) $exec(scripts);
		else if ($type(option) == 'function') option(scripts, text);
		return text;
	},

	substitute: function(object, regexp){
		return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
			if (match.charAt(0) == '\\') return match.slice(1);
			return (object[name] != undefined) ? object[name] : '';
		});
	}

});


/*
---

name: Hash

description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.

license: MIT-style license.

requires: Hash.base

provides: Hash

...
*/

Hash.implement({

	has: Object.prototype.hasOwnProperty,

	keyOf: function(value){
		for (var key in this){
			if (this.hasOwnProperty(key) && this[key] === value) return key;
		}
		return null;
	},

	hasValue: function(value){
		return (Hash.keyOf(this, value) !== null);
	},

	extend: function(properties){
		Hash.each(properties || {}, function(value, key){
			Hash.set(this, key, value);
		}, this);
		return this;
	},

	combine: function(properties){
		Hash.each(properties || {}, function(value, key){
			Hash.include(this, key, value);
		}, this);
		return this;
	},

	erase: function(key){
		if (this.hasOwnProperty(key)) delete this[key];
		return this;
	},

	get: function(key){
		return (this.hasOwnProperty(key)) ? this[key] : null;
	},

	set: function(key, value){
		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
		return this;
	},

	empty: function(){
		Hash.each(this, function(value, key){
			delete this[key];
		}, this);
		return this;
	},

	include: function(key, value){
		if (this[key] == undefined) this[key] = value;
		return this;
	},

	map: function(fn, bind){
		var results = new Hash;
		Hash.each(this, function(value, key){
			results.set(key, fn.call(bind, value, key, this));
		}, this);
		return results;
	},

	filter: function(fn, bind){
		var results = new Hash;
		Hash.each(this, function(value, key){
			if (fn.call(bind, value, key, this)) results.set(key, value);
		}, this);
		return results;
	},

	every: function(fn, bind){
		for (var key in this){
			if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
		}
		return true;
	},

	some: function(fn, bind){
		for (var key in this){
			if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
		}
		return false;
	},

	getKeys: function(){
		var keys = [];
		Hash.each(this, function(value, key){
			keys.push(key);
		});
		return keys;
	},

	getValues: function(){
		var values = [];
		Hash.each(this, function(value){
			values.push(value);
		});
		return values;
	},

	toQueryString: function(base){
		var queryString = [];
		Hash.each(this, function(value, key){
			if (base) key = base + '[' + key + ']';
			var result;
			switch ($type(value)){
				case 'object': result = Hash.toQueryString(value, key); break;
				case 'array':
					var qs = {};
					value.each(function(val, i){
						qs[i] = val;
					});
					result = Hash.toQueryString(qs, key);
				break;
				default: result = key + '=' + encodeURIComponent(value);
			}
			if (value != undefined) queryString.push(result);
		});

		return queryString.join('&');
	}

});

Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});


/*
---

name: Event

description: Contains the Event Class, to make the event object cross-browser.

license: MIT-style license.

requires: [Window, Document, Hash, Array, Function, String]

provides: Event

...
*/

var Event = new Native({

	name: 'Event',

	initialize: function(event, win){
		win = win || window;
		var doc = win.document;
		event = event || win.event;
		if (event.$extended) return event;
		this.$extended = true;
		var type = event.type;
		var target = event.target || event.srcElement;
		while (target && target.nodeType == 3) target = target.parentNode;

		if (type.test(/key/)){
			var code = event.which || event.keyCode;
			var key = Event.Keys.keyOf(code);
			if (type == 'keydown'){
				var fKey = code - 111;
				if (fKey > 0 && fKey < 13) key = 'f' + fKey;
			}
			key = key || String.fromCharCode(code).toLowerCase();
		} else if (type.match(/(click|mouse|menu)/i)){
			doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
			var page = {
				x: event.pageX || event.clientX + doc.scrollLeft,
				y: event.pageY || event.clientY + doc.scrollTop
			};
			var client = {
				x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
				y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
			};
			if (type.match(/DOMMouseScroll|mousewheel/)){
				var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
			}
			var rightClick = (event.which == 3) || (event.button == 2);
			var related = null;
			if (type.match(/over|out/)){
				switch (type){
					case 'mouseover': related = event.relatedTarget || event.fromElement; break;
					case 'mouseout': related = event.relatedTarget || event.toElement;
				}
				if (!(function(){
					while (related && related.nodeType == 3) related = related.parentNode;
					return true;
				}).create({attempt: Browser.Engine.gecko})()) related = false;
			}
		}

		return $extend(this, {
			event: event,
			type: type,

			page: page,
			client: client,
			rightClick: rightClick,

			wheel: wheel,

			relatedTarget: related,
			target: target,

			code: code,
			key: key,

			shift: event.shiftKey,
			control: event.ctrlKey,
			alt: event.altKey,
			meta: event.metaKey
		});
	}

});

Event.Keys = new Hash({
	'enter': 13,
	'up': 38,
	'down': 40,
	'left': 37,
	'right': 39,
	'esc': 27,
	'space': 32,
	'backspace': 8,
	'tab': 9,
	'delete': 46
});

Event.implement({

	stop: function(){
		return this.stopPropagation().preventDefault();
	},

	stopPropagation: function(){
		if (this.event.stopPropagation) this.event.stopPropagation();
		else this.event.cancelBubble = true;
		return this;
	},

	preventDefault: function(){
		if (this.event.preventDefault) this.event.preventDefault();
		else this.event.returnValue = false;
		return this;
	}

});


/*
---

name: Class

description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.

license: MIT-style license.

requires: [$util, Native, Array, String, Function, Number, Hash]

provides: Class

...
*/

function Class(params){

	if (params instanceof Function) params = {initialize: params};

	var newClass = function(){
		Object.reset(this);
		if (newClass._prototyping) return this;
		this._current = $empty;
		var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
		delete this._current; delete this.caller;
		return value;
	}.extend(this);

	newClass.implement(params);

	newClass.constructor = Class;
	newClass.prototype.constructor = newClass;

	return newClass;

};

Function.prototype.protect = function(){
	this._protected = true;
	return this;
};

Object.reset = function(object, key){

	if (key == null){
		for (var p in object) Object.reset(object, p);
		return object;
	}

	delete object[key];

	switch ($type(object[key])){
		case 'object':
			var F = function(){};
			F.prototype = object[key];
			var i = new F;
			object[key] = Object.reset(i);
		break;
		case 'array': object[key] = $unlink(object[key]); break;
	}

	return object;

};

new Native({name: 'Class', initialize: Class}).extend({

	instantiate: function(F){
		F._prototyping = true;
		var proto = new F;
		delete F._prototyping;
		return proto;
	},

	wrap: function(self, key, method){
		if (method._origin) method = method._origin;

		return function(){
			if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.');
			var caller = this.caller, current = this._current;
			this.caller = current; this._current = arguments.callee;
			var result = method.apply(this, arguments);
			this._current = current; this.caller = caller;
			return result;
		}.extend({_owner: self, _origin: method, _name: key});

	}

});

Class.implement({

	implement: function(key, value){

		if ($type(key) == 'object'){
			for (var p in key) this.implement(p, key[p]);
			return this;
		}

		var mutator = Class.Mutators[key];

		if (mutator){
			value = mutator.call(this, value);
			if (value == null) return this;
		}

		var proto = this.prototype;

		switch ($type(value)){

			case 'function':
				if (value._hidden) return this;
				proto[key] = Class.wrap(this, key, value);
			break;

			case 'object':
				var previous = proto[key];
				if ($type(previous) == 'object') $mixin(previous, value);
				else proto[key] = $unlink(value);
			break;

			case 'array':
				proto[key] = $unlink(value);
			break;

			default: proto[key] = value;

		}

		return this;

	}

});

Class.Mutators = {

	Extends: function(parent){

		this.parent = parent;
		this.prototype = Class.instantiate(parent);

		this.implement('parent', function(){
			var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
			if (!previous) throw new Error('The method "' + name + '" has no parent.');
			return previous.apply(this, arguments);
		}.protect());

	},

	Implements: function(items){
		$splat(items).each(function(item){
			if (item instanceof Function) item = Class.instantiate(item);
			this.implement(item);
		}, this);

	}

};


/*
---

name: Class.Extras

description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.

license: MIT-style license.

requires: Class

provides: [Chain, Events, Options, Class.Extras]

...
*/

var Chain = new Class({

	$chain: [],

	chain: function(){
		this.$chain.extend(Array.flatten(arguments));
		return this;
	},

	callChain: function(){
		return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
	},

	clearChain: function(){
		this.$chain.empty();
		return this;
	}

});

var Events = new Class({

	$events: {},

	addEvent: function(type, fn, internal){
		type = Events.removeOn(type);
		if (fn != $empty){
			this.$events[type] = this.$events[type] || [];
			this.$events[type].include(fn);
			if (internal) fn.internal = true;
		}
		return this;
	},

	addEvents: function(events){
		for (var type in events) this.addEvent(type, events[type]);
		return this;
	},

	fireEvent: function(type, args, delay){
		type = Events.removeOn(type);
		if (!this.$events || !this.$events[type]) return this;
		this.$events[type].each(function(fn){
			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
		}, this);
		return this;
	},

	removeEvent: function(type, fn){
		type = Events.removeOn(type);
		if (!this.$events[type]) return this;
		if (!fn.internal) this.$events[type].erase(fn);
		return this;
	},

	removeEvents: function(events){
		var type;
		if ($type(events) == 'object'){
			for (type in events) this.removeEvent(type, events[type]);
			return this;
		}
		if (events) events = Events.removeOn(events);
		for (type in this.$events){
			if (events && events != type) continue;
			var fns = this.$events[type];
			for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]);
		}
		return this;
	}

});

Events.removeOn = function(string){
	return string.replace(/^on([A-Z])/, function(full, first){
		return first.toLowerCase();
	});
};

var Options = new Class({

	setOptions: function(){
		this.options = $merge.run([this.options].extend(arguments));
		if (!this.addEvent) return this;
		for (var option in this.options){
			if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
			this.addEvent(option, this.options[option]);
			delete this.options[option];
		}
		return this;
	}

});


/*
---

name: Element

description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.

license: MIT-style license.

requires: [Window, Document, Array, String, Function, Number, Hash]

provides: [Element, Elements, $, $$, Iframe]

...
*/

var Element = new Native({

	name: 'Element',

	legacy: window.Element,

	initialize: function(tag, props){
		var konstructor = Element.Constructors.get(tag);
		if (konstructor) return konstructor(props);
		if (typeof tag == 'string') return document.newElement(tag, props);
		return document.id(tag).set(props);
	},

	afterImplement: function(key, value){
		Element.Prototype[key] = value;
		if (Array[key]) return;
		Elements.implement(key, function(){
			var items = [], elements = true;
			for (var i = 0, j = this.length; i < j; i++){
				var returns = this[i][key].apply(this[i], arguments);
				items.push(returns);
				if (elements) elements = ($type(returns) == 'element');
			}
			return (elements) ? new Elements(items) : items;
		});
	}

});

Element.Prototype = {$family: {name: 'element'}};

Element.Constructors = new Hash;

var IFrame = new Native({

	name: 'IFrame',

	generics: false,

	initialize: function(){
		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
		var props = params.properties || {};
		var iframe = document.id(params.iframe);
		var onload = props.onload || $empty;
		delete props.onload;
		props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
		iframe = new Element(iframe || 'iframe', props);
		var onFrameLoad = function(){
			var host = $try(function(){
				return iframe.contentWindow.location.host;
			});
			if (!host || host == window.location.host){
				var win = new Window(iframe.contentWindow);
				new Document(iframe.contentWindow.document);
				$extend(win.Element.prototype, Element.Prototype);
			}
			onload.call(iframe.contentWindow, iframe.contentWindow.document);
		};
		var contentWindow = $try(function(){
			return iframe.contentWindow;
		});
		((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
		return iframe;
	}

});

var Elements = new Native({

	initialize: function(elements, options){
		options = $extend({ddup: true, cash: true}, options);
		elements = elements || [];
		if (options.ddup || options.cash){
			var uniques = {}, returned = [];
			for (var i = 0, l = elements.length; i < l; i++){
				var el = document.id(elements[i], !options.cash);
				if (options.ddup){
					if (uniques[el.uid]) continue;
					uniques[el.uid] = true;
				}
				if (el) returned.push(el);
			}
			elements = returned;
		}
		return (options.cash) ? $extend(elements, this) : elements;
	}

});

Elements.implement({

	filter: function(filter, bind){
		if (!filter) return this;
		return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
			return item.match(filter);
		} : filter, bind));
	}

});

(function(){

/*<ltIE8>*/
var createElementAcceptsHTML;
try {
	var x = document.createElement('<input name=x>');
	createElementAcceptsHTML = (x.name == 'x');
} catch(e){}

var escapeQuotes = function(html){
	return ('' + html).replace(/&/g,'&amp;').replace(/"/g,'&quot;');
};
/*</ltIE8>*/

Document.implement({

	newElement: function(tag, props){
		if (props && props.checked != null) props.defaultChecked = props.checked;
		/*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
		if (createElementAcceptsHTML && props){
			tag = '<' + tag;
			if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
			if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
			tag += '>';
			delete props.name;
			delete props.type;
		}
		/*</ltIE8>*/
		return this.id(this.createElement(tag)).set(props);
	},

	newTextNode: function(text){
		return this.createTextNode(text);
	},

	getDocument: function(){
		return this;
	},

	getWindow: function(){
		return this.window;
	},

	id: (function(){

		var types = {

			string: function(id, nocash, doc){
				id = doc.getElementById(id);
				return (id) ? types.element(id, nocash) : null;
			},

			element: function(el, nocash){
				$uid(el);
				if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
					var proto = Element.Prototype;
					for (var p in proto) el[p] = proto[p];
				};
				return el;
			},

			object: function(obj, nocash, doc){
				if (obj.toElement) return types.element(obj.toElement(doc), nocash);
				return null;
			}

		};

		types.textnode = types.whitespace = types.window = types.document = $arguments(0);

		return function(el, nocash, doc){
			if (el && el.$family && el.uid) return el;
			var type = $type(el);
			return (types[type]) ? types[type](el, nocash, doc || document) : null;
		};

	})()

});

})();

if (window.$ == null) Window.implement({
	$: function(el, nc){
		return document.id(el, nc, this.document);
	}
});

Window.implement({

	$$: function(selector){
		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
		var elements = [];
		var args = Array.flatten(arguments);
		for (var i = 0, l = args.length; i < l; i++){
			var item = args[i];
			switch ($type(item)){
				case 'element': elements.push(item); break;
				case 'string': elements.extend(this.document.getElements(item, true));
			}
		}
		return new Elements(elements);
	},

	getDocument: function(){
		return this.document;
	},

	getWindow: function(){
		return this;
	}

});

Native.implement([Element, Document], {

	getElement: function(selector, nocash){
		return document.id(this.getElements(selector, true)[0] || null, nocash);
	},

	getElements: function(tags, nocash){
		tags = tags.split(',');
		var elements = [];
		var ddup = (tags.length > 1);
		tags.each(function(tag){
			var partial = this.getElementsByTagName(tag.trim());
			(ddup) ? elements.extend(partial) : elements = partial;
		}, this);
		return new Elements(elements, {ddup: ddup, cash: !nocash});
	}

});

(function(){

var collected = {}, storage = {};
var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};

var get = function(uid){
	return (storage[uid] || (storage[uid] = {}));
};

var clean = function(item, retain){
	if (!item) return;
	var uid = item.uid;
	if (retain !== true) retain = false;
	if (Browser.Engine.trident){
		if (item.clearAttributes){
			var clone = retain && item.cloneNode(false);
			item.clearAttributes();
			if (clone) item.mergeAttributes(clone);
		} else if (item.removeEvents){
			item.removeEvents();
		}
		if ((/object/i).test(item.tagName)){
			for (var p in item){
				if (typeof item[p] == 'function') item[p] = $empty;
			}
			Element.dispose(item);
		}
	}
	if (!uid) return;
	collected[uid] = storage[uid] = null;
};

var purge = function(){
	Hash.each(collected, clean);
	if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
	if (window.CollectGarbage) CollectGarbage();
	collected = storage = null;
};

var walk = function(element, walk, start, match, all, nocash){
	var el = element[start || walk];
	var elements = [];
	while (el){
		if (el.nodeType == 1 && (!match || Element.match(el, match))){
			if (!all) return document.id(el, nocash);
			elements.push(el);
		}
		el = el[walk];
	}
	return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
};

var attributes = {
	'html': 'innerHTML',
	'class': 'className',
	'for': 'htmlFor',
	'defaultValue': 'defaultValue',
	'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
};
var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];

bools = bools.associate(bools);

Hash.extend(attributes, bools);
Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));

var inserters = {

	before: function(context, element){
		if (element.parentNode) element.parentNode.insertBefore(context, element);
	},

	after: function(context, element){
		if (!element.parentNode) return;
		var next = element.nextSibling;
		(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
	},

	bottom: function(context, element){
		element.appendChild(context);
	},

	top: function(context, element){
		var first = element.firstChild;
		(first) ? element.insertBefore(context, first) : element.appendChild(context);
	}

};

inserters.inside = inserters.bottom;

Hash.each(inserters, function(inserter, where){

	where = where.capitalize();

	Element.implement('inject' + where, function(el){
		inserter(this, document.id(el, true));
		return this;
	});

	Element.implement('grab' + where, function(el){
		inserter(document.id(el, true), this);
		return this;
	});

});

Element.implement({

	set: function(prop, value){
		switch ($type(prop)){
			case 'object':
				for (var p in prop) this.set(p, prop[p]);
				break;
			case 'string':
				var property = Element.Properties.get(prop);
				(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
		}
		return this;
	},

	get: function(prop){
		var property = Element.Properties.get(prop);
		return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
	},

	erase: function(prop){
		var property = Element.Properties.get(prop);
		(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
		return this;
	},

	setProperty: function(attribute, value){
		var key = attributes[attribute];
		if (value == undefined) return this.removeProperty(attribute);
		if (key && bools[attribute]) value = !!value;
		(key) ? this[key] = value : this.setAttribute(attribute, '' + value);
		return this;
	},

	setProperties: function(attributes){
		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
		return this;
	},

	getProperty: function(attribute){
		var key = attributes[attribute];
		var value = (key) ? this[key] : this.getAttribute(attribute, 2);
		return (bools[attribute]) ? !!value : (key) ? value : value || null;
	},

	getProperties: function(){
		var args = $A(arguments);
		return args.map(this.getProperty, this).associate(args);
	},

	removeProperty: function(attribute){
		var key = attributes[attribute];
		(key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
		return this;
	},

	removeProperties: function(){
		Array.each(arguments, this.removeProperty, this);
		return this;
	},

	hasClass: function(className){
		return this.className.contains(className, ' ');
	},

	addClass: function(className){
		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
		return this;
	},

	removeClass: function(className){
		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
		return this;
	},

	toggleClass: function(className){
		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
	},

	adopt: function(){
		Array.flatten(arguments).each(function(element){
			element = document.id(element, true);
			if (element) this.appendChild(element);
		}, this);
		return this;
	},

	appendText: function(text, where){
		return this.grab(this.getDocument().newTextNode(text), where);
	},

	grab: function(el, where){
		inserters[where || 'bottom'](document.id(el, true), this);
		return this;
	},

	inject: function(el, where){
		inserters[where || 'bottom'](this, document.id(el, true));
		return this;
	},

	replaces: function(el){
		el = document.id(el, true);
		el.parentNode.replaceChild(this, el);
		return this;
	},

	wraps: function(el, where){
		el = document.id(el, true);
		return this.replaces(el).grab(el, where);
	},

	getPrevious: function(match, nocash){
		return walk(this, 'previousSibling', null, match, false, nocash);
	},

	getAllPrevious: function(match, nocash){
		return walk(this, 'previousSibling', null, match, true, nocash);
	},

	getNext: function(match, nocash){
		return walk(this, 'nextSibling', null, match, false, nocash);
	},

	getAllNext: function(match, nocash){
		return walk(this, 'nextSibling', null, match, true, nocash);
	},

	getFirst: function(match, nocash){
		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
	},

	getLast: function(match, nocash){
		return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
	},

	getParent: function(match, nocash){
		return walk(this, 'parentNode', null, match, false, nocash);
	},

	getParents: function(match, nocash){
		return walk(this, 'parentNode', null, match, true, nocash);
	},

	getSiblings: function(match, nocash){
		return this.getParent().getChildren(match, nocash).erase(this);
	},

	getChildren: function(match, nocash){
		return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
	},

	getWindow: function(){
		return this.ownerDocument.window;
	},

	getDocument: function(){
		return this.ownerDocument;
	},

	getElementById: function(id, nocash){
		var el = this.ownerDocument.getElementById(id);
		if (!el) return null;
		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
			if (!parent) return null;
		}
		return document.id(el, nocash);
	},

	getSelected: function(){
		return new Elements($A(this.options).filter(function(option){
			return option.selected;
		}));
	},

	getComputedStyle: function(property){
		if (this.currentStyle) return this.currentStyle[property.camelCase()];
		var computed = this.getDocument().defaultView.getComputedStyle(this, null);
		return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
	},

	toQueryString: function(){
		var queryString = [];
		this.getElements('input, select, textarea', true).each(function(el){
			if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
				return opt.value;
			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
			$splat(value).each(function(val){
				if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
			});
		});
		return queryString.join('&');
	},

	clone: function(contents, keepid){
		contents = contents !== false;
		var clone = this.cloneNode(contents);
		var clean = function(node, element){
			if (!keepid) node.removeAttribute('id');
			if (Browser.Engine.trident){
				node.clearAttributes();
				node.mergeAttributes(element);
				node.removeAttribute('uid');
				if (node.options){
					var no = node.options, eo = element.options;
					for (var j = no.length; j--;) no[j].selected = eo[j].selected;
				}
			}
			var prop = props[element.tagName.toLowerCase()];
			if (prop && element[prop]) node[prop] = element[prop];
		};

		if (contents){
			var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
			for (var i = ce.length; i--;) clean(ce[i], te[i]);
		}

		clean(clone, this);
		return document.id(clone);
	},

	destroy: function(){
		Element.empty(this);
		Element.dispose(this);
		clean(this, true);
		return null;
	},

	empty: function(){
		$A(this.childNodes).each(function(node){
			Element.destroy(node);
		});
		return this;
	},

	dispose: function(){
		return (this.parentNode) ? this.parentNode.removeChild(this) : this;
	},

	hasChild: function(el){
		el = document.id(el, true);
		if (!el) return false;
		if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
		return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
	},

	match: function(tag){
		return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
	}

});

Native.implement([Element, Window, Document], {

	addListener: function(type, fn){
		if (type == 'unload'){
			var old = fn, self = this;
			fn = function(){
				self.removeListener('unload', fn);
				old();
			};
		} else {
			collected[this.uid] = this;
		}
		if (this.addEventListener) this.addEventListener(type, fn, false);
		else this.attachEvent('on' + type, fn);
		return this;
	},

	removeListener: function(type, fn){
		if (this.removeEventListener) this.removeEventListener(type, fn, false);
		else this.detachEvent('on' + type, fn);
		return this;
	},

	retrieve: function(property, dflt){
		var storage = get(this.uid), prop = storage[property];
		if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
		return $pick(prop);
	},

	store: function(property, value){
		var storage = get(this.uid);
		storage[property] = value;
		return this;
	},

	eliminate: function(property){
		var storage = get(this.uid);
		delete storage[property];
		return this;
	}

});

window.addListener('unload', purge);

})();

Element.Properties = new Hash;

Element.Properties.style = {

	set: function(style){
		this.style.cssText = style;
	},

	get: function(){
		return this.style.cssText;
	},

	erase: function(){
		this.style.cssText = '';
	}

};

Element.Properties.tag = {

	get: function(){
		return this.tagName.toLowerCase();
	}

};

Element.Properties.html = (function(){
	var BUGGY_INNERHTML = true;
	try {
		var tester = document.createElement('tr');
		tester.innerHTML = '<td>x';
		tester = tester.firstChild;
		BUGGY_INNERHTML = !(tester.nodeName == 'TD' && tester.innerHTML == 'x');
	}
	catch (e){}
	if (!BUGGY_INNERHTML) return {
		set: function(){
			this.innerHTML = Array.flatten(arguments).join('');
		}
	};

	var wrapper = document.createElement('div');

	var translations = {
		table: [1, '<table>', '</table>'],
		select: [1, '<select>', '</select>'],
		tbody: [2, '<table><tbody>', '</tbody></table>'],
		tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
	};
	translations.thead = translations.tfoot = translations.tbody;

	var html = {
		set: function(){
			var html = Array.flatten(arguments).join('');
			var wrap = translations[this.get('tag')];
			if (wrap){
				var first = wrapper;
				first.innerHTML = wrap[1] + html + wrap[2];
				for (var i = wrap[0]; i--;) first = first.firstChild;
				this.empty().adopt(first.childNodes);
			} else {
				this.innerHTML = html;
			}
		}
	};

	html.erase = html.set;

	return html;
})();

if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
	get: function(){
		if (this.innerText) return this.innerText;
		var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
		var text = temp.innerText;
		temp.destroy();
		return text;
	}
};


/*
---

name: Element.Event

description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.

license: MIT-style license.

requires: [Element, Event]

provides: Element.Event

...
*/

Element.Properties.events = {set: function(events){
	this.addEvents(events);
}};

Native.implement([Element, Window, Document], {

	addEvent: function(type, fn){
		var events = this.retrieve('events', {});
		events[type] = events[type] || {'keys': [], 'values': []};
		if (events[type].keys.contains(fn)) return this;
		events[type].keys.push(fn);
		var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
		if (custom){
			if (custom.onAdd) custom.onAdd.call(this, fn);
			if (custom.condition){
				condition = function(event){
					if (custom.condition.call(this, event)) return fn.call(this, event);
					return true;
				};
			}
			realType = custom.base || realType;
		}
		var defn = function(){
			return fn.call(self);
		};
		var nativeEvent = Element.NativeEvents[realType];
		if (nativeEvent){
			if (nativeEvent == 2){
				defn = function(event){
					event = new Event(event, self.getWindow());
					if (condition.call(self, event) === false) event.stop();
				};
			}
			this.addListener(realType, defn);
		}
		events[type].values.push(defn);
		return this;
	},

	removeEvent: function(type, fn){
		var events = this.retrieve('events');
		if (!events || !events[type]) return this;
		var pos = events[type].keys.indexOf(fn);
		if (pos == -1) return this;
		events[type].keys.splice(pos, 1);
		var value = events[type].values.splice(pos, 1)[0];
		var custom = Element.Events.get(type);
		if (custom){
			if (custom.onRemove) custom.onRemove.call(this, fn);
			type = custom.base || type;
		}
		return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
	},

	addEvents: function(events){
		for (var event in events) this.addEvent(event, events[event]);
		return this;
	},

	removeEvents: function(events){
		var type;
		if ($type(events) == 'object'){
			for (type in events) this.removeEvent(type, events[type]);
			return this;
		}
		var attached = this.retrieve('events');
		if (!attached) return this;
		if (!events){
			for (type in attached) this.removeEvents(type);
			this.eliminate('events');
		} else if (attached[events]){
			while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
			attached[events] = null;
		}
		return this;
	},

	fireEvent: function(type, args, delay){
		var events = this.retrieve('events');
		if (!events || !events[type]) return this;
		events[type].keys.each(function(fn){
			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
		}, this);
		return this;
	},

	cloneEvents: function(from, type){
		from = document.id(from);
		var fevents = from.retrieve('events');
		if (!fevents) return this;
		if (!type){
			for (var evType in fevents) this.cloneEvents(from, evType);
		} else if (fevents[type]){
			fevents[type].keys.each(function(fn){
				this.addEvent(type, fn);
			}, this);
		}
		return this;
	}

});

// IE9
try {
	if (typeof HTMLElement != 'undefined')
		HTMLElement.prototype.fireEvent = Element.prototype.fireEvent;
} catch(e){}

Element.NativeEvents = {
	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
	keydown: 2, keypress: 2, keyup: 2, //keyboard
	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
	load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
	error: 1, abort: 1, scroll: 1 //misc
};

(function(){

var $check = function(event){
	var related = event.relatedTarget;
	if (related == undefined) return true;
	if (related === false) return false;
	return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
};

Element.Events = new Hash({

	mouseenter: {
		base: 'mouseover',
		condition: $check
	},

	mouseleave: {
		base: 'mouseout',
		condition: $check
	},

	mousewheel: {
		base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
	}

});

})();


/*
---

name: Element.Style

description: Contains methods for interacting with the styles of Elements in a fashionable way.

license: MIT-style license.

requires: Element

provides: Element.Style

...
*/

Element.Properties.styles = {set: function(styles){
	this.setStyles(styles);
}};

Element.Properties.opacity = {

	set: function(opacity, novisibility){
		if (!novisibility){
			if (opacity == 0){
				if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
			} else {
				if (this.style.visibility != 'visible') this.style.visibility = 'visible';
			}
		}
		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
		if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
		this.style.opacity = opacity;
		this.store('opacity', opacity);
	},

	get: function(){
		return this.retrieve('opacity', 1);
	}

};

Element.implement({

	setOpacity: function(value){
		return this.set('opacity', value, true);
	},

	getOpacity: function(){
		return this.get('opacity');
	},

	setStyle: function(property, value){
		switch (property){
			case 'opacity': return this.set('opacity', parseFloat(value));
			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
		}
		property = property.camelCase();
		if ($type(value) != 'string'){
			var map = (Element.Styles.get(property) || '@').split(' ');
			value = $splat(value).map(function(val, i){
				if (!map[i]) return '';
				return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
			}).join(' ');
		} else if (value == String(Number(value))){
			value = Math.round(value);
		}
		this.style[property] = value;
		return this;
	},

	getStyle: function(property){
		switch (property){
			case 'opacity': return this.get('opacity');
			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
		}
		property = property.camelCase();
		var result = this.style[property];
		if (!$chk(result)){
			result = [];
			for (var style in Element.ShortStyles){
				if (property != style) continue;
				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
				return result.join(' ');
			}
			result = this.getComputedStyle(property);
		}
		if (result){
			result = String(result);
			var color = result.match(/rgba?\([\d\s,]+\)/);
			if (color) result = result.replace(color[0], color[0].rgbToHex());
		}
		if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){
			if (property.test(/^(height|width)$/)){
				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
				values.each(function(value){
					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
				}, this);
				return this['offset' + property.capitalize()] - size + 'px';
			}
			if ((Browser.Engine.presto) && String(result).test('px')) return result;
			if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
		}
		return result;
	},

	setStyles: function(styles){
		for (var style in styles) this.setStyle(style, styles[style]);
		return this;
	},

	getStyles: function(){
		var result = {};
		Array.flatten(arguments).each(function(key){
			result[key] = this.getStyle(key);
		}, this);
		return result;
	}

});

Element.Styles = new Hash({
	left: '@px', top: '@px', bottom: '@px', right: '@px',
	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
});

Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};

['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
	var Short = Element.ShortStyles;
	var All = Element.Styles;
	['margin', 'padding'].each(function(style){
		var sd = style + direction;
		Short[style][sd] = All[sd] = '@px';
	});
	var bd = 'border' + direction;
	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
	Short[bd] = {};
	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
});


/*
---

name: Element.Dimensions

description: Contains methods to work with size, scroll, or positioning of Elements and the window object.

license: MIT-style license.

credits:
  - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
  - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).

requires: Element

provides: Element.Dimensions

...
*/

(function(){

Element.implement({

	scrollTo: function(x, y){
		if (isBody(this)){
			this.getWindow().scrollTo(x, y);
		} else {
			this.scrollLeft = x;
			this.scrollTop = y;
		}
		return this;
	},

	getSize: function(){
		if (isBody(this)) return this.getWindow().getSize();
		return {x: this.offsetWidth, y: this.offsetHeight};
	},

	getScrollSize: function(){
		if (isBody(this)) return this.getWindow().getScrollSize();
		return {x: this.scrollWidth, y: this.scrollHeight};
	},

	getScroll: function(){
		if (isBody(this)) return this.getWindow().getScroll();
		return {x: this.scrollLeft, y: this.scrollTop};
	},

	getScrolls: function(){
		var element = this, position = {x: 0, y: 0};
		while (element && !isBody(element)){
			position.x += element.scrollLeft;
			position.y += element.scrollTop;
			element = element.parentNode;
		}
		return position;
	},

	getOffsetParent: function(){
		var element = this;
		if (isBody(element)) return null;
		if (!Browser.Engine.trident) return element.offsetParent;
		while ((element = element.parentNode) && !isBody(element)){
			if (styleString(element, 'position') != 'static') return element;
		}
		return null;
	},

	getOffsets: function(){
		if (this.getBoundingClientRect){
			var bound = this.getBoundingClientRect(),
				html = document.id(this.getDocument().documentElement),
				htmlScroll = html.getScroll(),
				elemScrolls = this.getScrolls(),
				elemScroll = this.getScroll(),
				isFixed = (styleString(this, 'position') == 'fixed');

			return {
				x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
				y: bound.top.toInt()  + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
			};
		}

		var element = this, position = {x: 0, y: 0};
		if (isBody(this)) return position;

		while (element && !isBody(element)){
			position.x += element.offsetLeft;
			position.y += element.offsetTop;

			if (Browser.Engine.gecko){
				if (!borderBox(element)){
					position.x += leftBorder(element);
					position.y += topBorder(element);
				}
				var parent = element.parentNode;
				if (parent && styleString(parent, 'overflow') != 'visible'){
					position.x += leftBorder(parent);
					position.y += topBorder(parent);
				}
			} else if (element != this && Browser.Engine.webkit){
				position.x += leftBorder(element);
				position.y += topBorder(element);
			}

			element = element.offsetParent;
		}
		if (Browser.Engine.gecko && !borderBox(this)){
			position.x -= leftBorder(this);
			position.y -= topBorder(this);
		}
		return position;
	},

	getPosition: function(relative){
		if (isBody(this)) return {x: 0, y: 0};
		var offset = this.getOffsets(),
				scroll = this.getScrolls();
		var position = {
			x: offset.x - scroll.x,
			y: offset.y - scroll.y
		};
		var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
	},

	getCoordinates: function(element){
		if (isBody(this)) return this.getWindow().getCoordinates();
		var position = this.getPosition(element),
				size = this.getSize();
		var obj = {
			left: position.x,
			top: position.y,
			width: size.x,
			height: size.y
		};
		obj.right = obj.left + obj.width;
		obj.bottom = obj.top + obj.height;
		return obj;
	},

	computePosition: function(obj){
		return {
			left: obj.x - styleNumber(this, 'margin-left'),
			top: obj.y - styleNumber(this, 'margin-top')
		};
	},

	setPosition: function(obj){
		return this.setStyles(this.computePosition(obj));
	}

});


Native.implement([Document, Window], {

	getSize: function(){
		if (Browser.Engine.presto || Browser.Engine.webkit){
			var win = this.getWindow();
			return {x: win.innerWidth, y: win.innerHeight};
		}
		var doc = getCompatElement(this);
		return {x: doc.clientWidth, y: doc.clientHeight};
	},

	getScroll: function(){
		var win = this.getWindow(), doc = getCompatElement(this);
		return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
	},

	getScrollSize: function(){
		var doc = getCompatElement(this), min = this.getSize();
		return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)};
	},

	getPosition: function(){
		return {x: 0, y: 0};
	},

	getCoordinates: function(){
		var size = this.getSize();
		return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
	}

});

// private methods

var styleString = Element.getComputedStyle;

function styleNumber(element, style){
	return styleString(element, style).toInt() || 0;
};

function borderBox(element){
	return styleString(element, '-moz-box-sizing') == 'border-box';
};

function topBorder(element){
	return styleNumber(element, 'border-top-width');
};

function leftBorder(element){
	return styleNumber(element, 'border-left-width');
};

function isBody(element){
	return (/^(?:body|html)$/i).test(element.tagName);
};

function getCompatElement(element){
	var doc = element.getDocument();
	return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
};

})();

//aliases
Element.alias('setPosition', 'position'); //compatability

Native.implement([Window, Document, Element], {

	getHeight: function(){
		return this.getSize().y;
	},

	getWidth: function(){
		return this.getSize().x;
	},

	getScrollTop: function(){
		return this.getScroll().y;
	},

	getScrollLeft: function(){
		return this.getScroll().x;
	},

	getScrollHeight: function(){
		return this.getScrollSize().y;
	},

	getScrollWidth: function(){
		return this.getScrollSize().x;
	},

	getTop: function(){
		return this.getPosition().y;
	},

	getLeft: function(){
		return this.getPosition().x;
	}

});


/*
---

name: Selectors

description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors.

license: MIT-style license.

requires: Element

provides: Selectors

...
*/

Native.implement([Document, Element], {

	getElements: function(expression, nocash){
		expression = expression.split(',');
		var items, local = {};
		for (var i = 0, l = expression.length; i < l; i++){
			var selector = expression[i], elements = Selectors.Utils.search(this, selector, local);
			if (i != 0 && elements.item) elements = $A(elements);
			items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements);
		}
		return new Elements(items, {ddup: (expression.length > 1), cash: !nocash});
	}

});

Element.implement({

	match: function(selector){
		if (!selector || (selector == this)) return true;
		var tagid = Selectors.Utils.parseTagAndID(selector);
		var tag = tagid[0], id = tagid[1];
		if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
		var parsed = Selectors.Utils.parseSelector(selector);
		return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
	}

});

var Selectors = {Cache: {nth: {}, parsed: {}}};

Selectors.RegExps = {
	id: (/#([\w-]+)/),
	tag: (/^(\w+|\*)/),
	quick: (/^(\w+|\*)$/),
	splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
	combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
};

Selectors.Utils = {

	chk: function(item, uniques){
		if (!uniques) return true;
		var uid = $uid(item);
		if (!uniques[uid]) return uniques[uid] = true;
		return false;
	},

	parseNthArgument: function(argument){
		if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
		var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
		if (!parsed) return false;
		var inta = parseInt(parsed[1], 10);
		var a = (inta || inta === 0) ? inta : 1;
		var special = parsed[2] || false;
		var b = parseInt(parsed[3], 10) || 0;
		if (a != 0){
			b--;
			while (b < 1) b += a;
			while (b >= a) b -= a;
		} else {
			a = b;
			special = 'index';
		}
		switch (special){
			case 'n': parsed = {a: a, b: b, special: 'n'}; break;
			case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
			case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
			case 'first': parsed = {a: 0, special: 'index'}; break;
			case 'last': parsed = {special: 'last-child'}; break;
			case 'only': parsed = {special: 'only-child'}; break;
			default: parsed = {a: (a - 1), special: 'index'};
		}

		return Selectors.Cache.nth[argument] = parsed;
	},

	parseSelector: function(selector){
		if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
		var m, parsed = {classes: [], pseudos: [], attributes: []};
		while ((m = Selectors.RegExps.combined.exec(selector))){
			var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
			if (cn){
				parsed.classes.push(cn);
			} else if (pn){
				var parser = Selectors.Pseudo.get(pn);
				if (parser) parsed.pseudos.push({parser: parser, argument: pa});
				else parsed.attributes.push({name: pn, operator: '=', value: pa});
			} else if (an){
				parsed.attributes.push({name: an, operator: ao, value: av});
			}
		}
		if (!parsed.classes.length) delete parsed.classes;
		if (!parsed.attributes.length) delete parsed.attributes;
		if (!parsed.pseudos.length) delete parsed.pseudos;
		if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
		return Selectors.Cache.parsed[selector] = parsed;
	},

	parseTagAndID: function(selector){
		var tag = selector.match(Selectors.RegExps.tag);
		var id = selector.match(Selectors.RegExps.id);
		return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
	},

	filter: function(item, parsed, local){
		var i;
		if (parsed.classes){
			for (i = parsed.classes.length; i--; i){
				var cn = parsed.classes[i];
				if (!Selectors.Filters.byClass(item, cn)) return false;
			}
		}
		if (parsed.attributes){
			for (i = parsed.attributes.length; i--; i){
				var att = parsed.attributes[i];
				if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
			}
		}
		if (parsed.pseudos){
			for (i = parsed.pseudos.length; i--; i){
				var psd = parsed.pseudos[i];
				if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
			}
		}
		return true;
	},

	getByTagAndID: function(ctx, tag, id){
		if (id){
			var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
			return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
		} else {
			return ctx.getElementsByTagName(tag);
		}
	},

	search: function(self, expression, local){
		var splitters = [];

		var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
			splitters.push(m1);
			return ':)' + m2;
		}).split(':)');

		var items, filtered, item;

		for (var i = 0, l = selectors.length; i < l; i++){

			var selector = selectors[i];

			if (i == 0 && Selectors.RegExps.quick.test(selector)){
				items = self.getElementsByTagName(selector);
				continue;
			}

			var splitter = splitters[i - 1];

			var tagid = Selectors.Utils.parseTagAndID(selector);
			var tag = tagid[0], id = tagid[1];

			if (i == 0){
				items = Selectors.Utils.getByTagAndID(self, tag, id);
			} else {
				var uniques = {}, found = [];
				for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
				items = found;
			}

			var parsed = Selectors.Utils.parseSelector(selector);

			if (parsed){
				filtered = [];
				for (var m = 0, n = items.length; m < n; m++){
					item = items[m];
					if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
				}
				items = filtered;
			}

		}

		return items;

	}

};

Selectors.Getters = {

	' ': function(found, self, tag, id, uniques){
		var items = Selectors.Utils.getByTagAndID(self, tag, id);
		for (var i = 0, l = items.length; i < l; i++){
			var item = items[i];
			if (Selectors.Utils.chk(item, uniques)) found.push(item);
		}
		return found;
	},

	'>': function(found, self, tag, id, uniques){
		var children = Selectors.Utils.getByTagAndID(self, tag, id);
		for (var i = 0, l = children.length; i < l; i++){
			var child = children[i];
			if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
		}
		return found;
	},

	'+': function(found, self, tag, id, uniques){
		while ((self = self.nextSibling)){
			if (self.nodeType == 1){
				if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
				break;
			}
		}
		return found;
	},

	'~': function(found, self, tag, id, uniques){
		while ((self = self.nextSibling)){
			if (self.nodeType == 1){
				if (!Selectors.Utils.chk(self, uniques)) break;
				if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
			}
		}
		return found;
	}

};

Selectors.Filters = {

	byTag: function(self, tag){
		return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
	},

	byID: function(self, id){
		return (!id || (self.id && self.id == id));
	},

	byClass: function(self, klass){
		return (self.className && self.className.contains && self.className.contains(klass, ' '));
	},

	byPseudo: function(self, parser, argument, local){
		return parser.call(self, argument, local);
	},

	byAttribute: function(self, name, operator, value){
		var result = Element.prototype.getProperty.call(self, name);
		if (!result) return (operator == '!=');
		if (!operator || value == undefined) return true;
		switch (operator){
			case '=': return (result == value);
			case '*=': return (result.contains(value));
			case '^=': return (result.substr(0, value.length) == value);
			case '$=': return (result.substr(result.length - value.length) == value);
			case '!=': return (result != value);
			case '~=': return result.contains(value, ' ');
			case '|=': return result.contains(value, '-');
		}
		return false;
	}

};

Selectors.Pseudo = new Hash({

	// w3c pseudo selectors

	checked: function(){
		return this.checked;
	},

	empty: function(){
		return !(this.innerText || this.textContent || '').length;
	},

	not: function(selector){
		return !Element.match(this, selector);
	},

	contains: function(text){
		return (this.innerText || this.textContent || '').contains(text);
	},

	'first-child': function(){
		return Selectors.Pseudo.index.call(this, 0);
	},

	'last-child': function(){
		var element = this;
		while ((element = element.nextSibling)){
			if (element.nodeType == 1) return false;
		}
		return true;
	},

	'only-child': function(){
		var prev = this;
		while ((prev = prev.previousSibling)){
			if (prev.nodeType == 1) return false;
		}
		var next = this;
		while ((next = next.nextSibling)){
			if (next.nodeType == 1) return false;
		}
		return true;
	},

	'nth-child': function(argument, local){
		argument = (argument == undefined) ? 'n' : argument;
		var parsed = Selectors.Utils.parseNthArgument(argument);
		if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
		var count = 0;
		local.positions = local.positions || {};
		var uid = $uid(this);
		if (!local.positions[uid]){
			var self = this;
			while ((self = self.previousSibling)){
				if (self.nodeType != 1) continue;
				count ++;
				var position = local.positions[$uid(self)];
				if (position != undefined){
					count = position + count;
					break;
				}
			}
			local.positions[uid] = count;
		}
		return (local.positions[uid] % parsed.a == parsed.b);
	},

	// custom pseudo selectors

	index: function(index){
		var element = this, count = 0;
		while ((element = element.previousSibling)){
			if (element.nodeType == 1 && ++count > index) return false;
		}
		return (count == index);
	},

	even: function(argument, local){
		return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
	},

	odd: function(argument, local){
		return Selectors.Pseudo['nth-child'].call(this, '2n', local);
	},

	selected: function(){
		return this.selected;
	},

	enabled: function(){
		return (this.disabled === false);
	}

});


/*
---

name: DomReady

description: Contains the custom event domready.

license: MIT-style license.

requires: Element.Event

provides: DomReady

...
*/

Element.Events.domready = {

	onAdd: function(fn){
		if (Browser.loaded) fn.call(this);
	}

};

(function(){

	var domready = function(){
		if (Browser.loaded) return;
		Browser.loaded = true;
		window.fireEvent('domready');
		document.fireEvent('domready');
	};

	window.addEvent('load', domready);

	if (Browser.Engine.trident){
		var temp = document.createElement('div');
		(function(){
			($try(function(){
				temp.doScroll(); // Technique by Diego Perini
				return document.id(temp).inject(document.body).set('html', 'temp').dispose();
			})) ? domready() : arguments.callee.delay(50);
		})();
	} else if (Browser.Engine.webkit && Browser.Engine.version < 525){
		(function(){
			(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
		})();
	} else {
		document.addEvent('DOMContentLoaded', domready);
	}

})();


/*
---

name: JSON

description: JSON encoder and decoder.

license: MIT-style license.

see: <http://www.json.org/>

requires: [Array, String, Number, Function, Hash]

provides: JSON

...
*/

var JSON = new Hash(this.JSON && {
	stringify: JSON.stringify,
	parse: JSON.parse
}).extend({

	$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},

	$replaceChars: function(chr){
		return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
	},

	encode: function(obj){
		switch ($type(obj)){
			case 'string':
				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
			case 'array':
				return '[' + String(obj.map(JSON.encode).clean()) + ']';
			case 'object': case 'hash':
				var string = [];
				Hash.each(obj, function(value, key){
					var json = JSON.encode(value);
					if (json) string.push(JSON.encode(key) + ':' + json);
				});
				return '{' + string + '}';
			case 'number': case 'boolean': return String(obj);
			case false: return 'null';
		}
		return null;
	},

	decode: function(string, secure){
		if ($type(string) != 'string' || !string.length) return null;
		if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
		return eval('(' + string + ')');
	}

});


/*
---

name: Cookie

description: Class for creating, reading, and deleting browser Cookies.

license: MIT-style license.

credits: Based on the functions by Peter-Paul Koch (http://quirksmode.org).

requires: Options

provides: Cookie

...
*/

var Cookie = new Class({

	Implements: Options,

	options: {
		path: false,
		domain: false,
		duration: false,
		secure: false,
		document: document
	},

	initialize: function(key, options){
		this.key = key;
		this.setOptions(options);
	},

	write: function(value){
		value = encodeURIComponent(value);
		if (this.options.domain) value += '; domain=' + this.options.domain;
		if (this.options.path) value += '; path=' + this.options.path;
		if (this.options.duration){
			var date = new Date();
			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
			value += '; expires=' + date.toGMTString();
		}
		if (this.options.secure) value += '; secure';
		this.options.document.cookie = this.key + '=' + value;
		return this;
	},

	read: function(){
		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
		return (value) ? decodeURIComponent(value[1]) : null;
	},

	dispose: function(){
		new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
		return this;
	}

});

Cookie.write = function(key, value, options){
	return new Cookie(key, options).write(value);
};

Cookie.read = function(key){
	return new Cookie(key).read();
};

Cookie.dispose = function(key, options){
	return new Cookie(key, options).dispose();
};


/*
---

name: Swiff

description: Wrapper for embedding SWF movies. Supports External Interface Communication.

license: MIT-style license.

credits: Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.

requires: [Options, $util]

provides: Swiff

...
*/

var Swiff = new Class({

	Implements: [Options],

	options: {
		id: null,
		height: 1,
		width: 1,
		container: null,
		properties: {},
		params: {
			quality: 'high',
			allowScriptAccess: 'always',
			wMode: 'transparent',
			swLiveConnect: true
		},
		callBacks: {},
		vars: {}
	},

	toElement: function(){
		return this.object;
	},

	initialize: function(path, options){
		this.instance = 'Swiff_' + $time();

		this.setOptions(options);
		options = this.options;
		var id = this.id = options.id || this.instance;
		var container = document.id(options.container);

		Swiff.CallBacks[this.instance] = {};

		var params = options.params, vars = options.vars, callBacks = options.callBacks;
		var properties = $extend({height: options.height, width: options.width}, options.properties);

		var self = this;

		for (var callBack in callBacks){
			Swiff.CallBacks[this.instance][callBack] = (function(option){
				return function(){
					return option.apply(self.object, arguments);
				};
			})(callBacks[callBack]);
			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
		}

		params.flashVars = Hash.toQueryString(vars);
		if (Browser.Engine.trident){
			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
			params.movie = path;
		} else {
			properties.type = 'application/x-shockwave-flash';
			properties.data = path;
		}
		var build = '<object id="' + id + '"';
		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
		build += '>';
		for (var param in params){
			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
		}
		build += '</object>';
		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
	},

	replaces: function(element){
		element = document.id(element, true);
		element.parentNode.replaceChild(this.toElement(), element);
		return this;
	},

	inject: function(element){
		document.id(element, true).appendChild(this.toElement());
		return this;
	},

	remote: function(){
		return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments));
	}

});

Swiff.CallBacks = {};

Swiff.remote = function(obj, fn){
	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
	return eval(rs);
};


/*
---

name: Fx

description: Contains the basic animation logic to be extended by all other Fx Classes.

license: MIT-style license.

requires: [Chain, Events, Options]

provides: Fx

...
*/

var Fx = new Class({

	Implements: [Chain, Events, Options],

	options: {
		/*
		onStart: $empty,
		onCancel: $empty,
		onComplete: $empty,
		*/
		fps: 50,
		unit: false,
		duration: 500,
		link: 'ignore'
	},

	initialize: function(options){
		this.subject = this.subject || this;
		this.setOptions(options);
		this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
		var wait = this.options.wait;
		if (wait === false) this.options.link = 'cancel';
	},

	getTransition: function(){
		return function(p){
			return -(Math.cos(Math.PI * p) - 1) / 2;
		};
	},

	step: function(){
		var time = $time();
		if (time < this.time + this.options.duration){
			var delta = this.transition((time - this.time) / this.options.duration);
			this.set(this.compute(this.from, this.to, delta));
		} else {
			this.set(this.compute(this.from, this.to, 1));
			this.complete();
		}
	},

	set: function(now){
		return now;
	},

	compute: function(from, to, delta){
		return Fx.compute(from, to, delta);
	},

	check: function(){
		if (!this.timer) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
		}
		return false;
	},

	start: function(from, to){
		if (!this.check(from, to)) return this;
		this.from = from;
		this.to = to;
		this.time = 0;
		this.transition = this.getTransition();
		this.startTimer();
		this.onStart();
		return this;
	},

	complete: function(){
		if (this.stopTimer()) this.onComplete();
		return this;
	},

	cancel: function(){
		if (this.stopTimer()) this.onCancel();
		return this;
	},

	onStart: function(){
		this.fireEvent('start', this.subject);
	},

	onComplete: function(){
		this.fireEvent('complete', this.subject);
		if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
	},

	onCancel: function(){
		this.fireEvent('cancel', this.subject).clearChain();
	},

	pause: function(){
		this.stopTimer();
		return this;
	},

	resume: function(){
		this.startTimer();
		return this;
	},

	stopTimer: function(){
		if (!this.timer) return false;
		this.time = $time() - this.time;
		this.timer = $clear(this.timer);
		return true;
	},

	startTimer: function(){
		if (this.timer) return false;
		this.time = $time() - this.time;
		this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
		return true;
	}

});

Fx.compute = function(from, to, delta){
	return (to - from) * delta + from;
};

Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};


/*
---

name: Fx.CSS

description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.

license: MIT-style license.

requires: [Fx, Element.Style]

provides: Fx.CSS

...
*/

Fx.CSS = new Class({

	Extends: Fx,

	//prepares the base from/to object

	prepare: function(element, property, values){
		values = $splat(values);
		var values1 = values[1];
		if (!$chk(values1)){
			values[1] = values[0];
			values[0] = element.getStyle(property);
		}
		var parsed = values.map(this.parse);
		return {from: parsed[0], to: parsed[1]};
	},

	//parses a value into an array

	parse: function(value){
		value = $lambda(value)();
		value = (typeof value == 'string') ? value.split(' ') : $splat(value);
		return value.map(function(val){
			val = String(val);
			var found = false;
			Fx.CSS.Parsers.each(function(parser, key){
				if (found) return;
				var parsed = parser.parse(val);
				if ($chk(parsed)) found = {value: parsed, parser: parser};
			});
			found = found || {value: val, parser: Fx.CSS.Parsers.String};
			return found;
		});
	},

	//computes by a from and to prepared objects, using their parsers.

	compute: function(from, to, delta){
		var computed = [];
		(Math.min(from.length, to.length)).times(function(i){
			computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
		});
		computed.$family = {name: 'fx:css:value'};
		return computed;
	},

	//serves the value as settable

	serve: function(value, unit){
		if ($type(value) != 'fx:css:value') value = this.parse(value);
		var returned = [];
		value.each(function(bit){
			returned = returned.concat(bit.parser.serve(bit.value, unit));
		});
		return returned;
	},

	//renders the change to an element

	render: function(element, property, value, unit){
		element.setStyle(property, this.serve(value, unit));
	},

	//searches inside the page css to find the values for a selector

	search: function(selector){
		if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
		var to = {};
		Array.each(document.styleSheets, function(sheet, j){
			var href = sheet.href;
			if (href && href.contains('://') && !href.contains(document.domain)) return;
			var rules = sheet.rules || sheet.cssRules;
			Array.each(rules, function(rule, i){
				if (!rule.style) return;
				var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
					return m.toLowerCase();
				}) : null;
				if (!selectorText || !selectorText.test('^' + selector + '$')) return;
				Element.Styles.each(function(value, style){
					if (!rule.style[style] || Element.ShortStyles[style]) return;
					value = String(rule.style[style]);
					to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
				});
			});
		});
		return Fx.CSS.Cache[selector] = to;
	}

});

Fx.CSS.Cache = {};

Fx.CSS.Parsers = new Hash({

	Color: {
		parse: function(value){
			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
		},
		compute: function(from, to, delta){
			return from.map(function(value, i){
				return Math.round(Fx.compute(from[i], to[i], delta));
			});
		},
		serve: function(value){
			return value.map(Number);
		}
	},

	Number: {
		parse: parseFloat,
		compute: Fx.compute,
		serve: function(value, unit){
			return (unit) ? value + unit : value;
		}
	},

	String: {
		parse: $lambda(false),
		compute: $arguments(1),
		serve: $arguments(0)
	}

});


/*
---

name: Fx.Tween

description: Formerly Fx.Style, effect to transition any CSS property for an element.

license: MIT-style license.

requires: Fx.CSS

provides: [Fx.Tween, Element.fade, Element.highlight]

...
*/

Fx.Tween = new Class({

	Extends: Fx.CSS,

	initialize: function(element, options){
		this.element = this.subject = document.id(element);
		this.parent(options);
	},

	set: function(property, now){
		if (arguments.length == 1){
			now = property;
			property = this.property || this.options.property;
		}
		this.render(this.element, property, now, this.options.unit);
		return this;
	},

	start: function(property, from, to){
		if (!this.check(property, from, to)) return this;
		var args = Array.flatten(arguments);
		this.property = this.options.property || args.shift();
		var parsed = this.prepare(this.element, this.property, args);
		return this.parent(parsed.from, parsed.to);
	}

});

Element.Properties.tween = {

	set: function(options){
		var tween = this.retrieve('tween');
		if (tween) tween.cancel();
		return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options));
	},

	get: function(options){
		if (options || !this.retrieve('tween')){
			if (options || !this.retrieve('tween:options')) this.set('tween', options);
			this.store('tween', new Fx.Tween(this, this.retrieve('tween:options')));
		}
		return this.retrieve('tween');
	}

};

Element.implement({

	tween: function(property, from, to){
		this.get('tween').start(arguments);
		return this;
	},

	fade: function(how){
		var fade = this.get('tween'), o = 'opacity', toggle;
		how = $pick(how, 'toggle');
		switch (how){
			case 'in': fade.start(o, 1); break;
			case 'out': fade.start(o, 0); break;
			case 'show': fade.set(o, 1); break;
			case 'hide': fade.set(o, 0); break;
			case 'toggle':
				var flag = this.retrieve('fade:flag', this.get('opacity') == 1);
				fade.start(o, (flag) ? 0 : 1);
				this.store('fade:flag', !flag);
				toggle = true;
			break;
			default: fade.start(o, arguments);
		}
		if (!toggle) this.eliminate('fade:flag');
		return this;
	},

	highlight: function(start, end){
		if (!end){
			end = this.retrieve('highlight:original', this.getStyle('background-color'));
			end = (end == 'transparent') ? '#fff' : end;
		}
		var tween = this.get('tween');
		tween.start('background-color', start || '#ffff88', end).chain(function(){
			this.setStyle('background-color', this.retrieve('highlight:original'));
			tween.callChain();
		}.bind(this));
		return this;
	}

});


/*
---

name: Fx.Morph

description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.

license: MIT-style license.

requires: Fx.CSS

provides: Fx.Morph

...
*/

Fx.Morph = new Class({

	Extends: Fx.CSS,

	initialize: function(element, options){
		this.element = this.subject = document.id(element);
		this.parent(options);
	},

	set: function(now){
		if (typeof now == 'string') now = this.search(now);
		for (var p in now) this.render(this.element, p, now[p], this.options.unit);
		return this;
	},

	compute: function(from, to, delta){
		var now = {};
		for (var p in from) now[p] = this.parent(from[p], to[p], delta);
		return now;
	},

	start: function(properties){
		if (!this.check(properties)) return this;
		if (typeof properties == 'string') properties = this.search(properties);
		var from = {}, to = {};
		for (var p in properties){
			var parsed = this.prepare(this.element, p, properties[p]);
			from[p] = parsed.from;
			to[p] = parsed.to;
		}
		return this.parent(from, to);
	}

});

Element.Properties.morph = {

	set: function(options){
		var morph = this.retrieve('morph');
		if (morph) morph.cancel();
		return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options));
	},

	get: function(options){
		if (options || !this.retrieve('morph')){
			if (options || !this.retrieve('morph:options')) this.set('morph', options);
			this.store('morph', new Fx.Morph(this, this.retrieve('morph:options')));
		}
		return this.retrieve('morph');
	}

};

Element.implement({

	morph: function(props){
		this.get('morph').start(props);
		return this;
	}

});


/*
---

name: Fx.Transitions

description: Contains a set of advanced transitions to be used with any of the Fx Classes.

license: MIT-style license.

credits: Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.

requires: Fx

provides: Fx.Transitions

...
*/

Fx.implement({

	getTransition: function(){
		var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
		if (typeof trans == 'string'){
			var data = trans.split(':');
			trans = Fx.Transitions;
			trans = trans[data[0]] || trans[data[0].capitalize()];
			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
		}
		return trans;
	}

});

Fx.Transition = function(transition, params){
	params = $splat(params);
	return $extend(transition, {
		easeIn: function(pos){
			return transition(pos, params);
		},
		easeOut: function(pos){
			return 1 - transition(1 - pos, params);
		},
		easeInOut: function(pos){
			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
		}
	});
};

Fx.Transitions = new Hash({

	linear: $arguments(0)

});

Fx.Transitions.extend = function(transitions){
	for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
};

Fx.Transitions.extend({

	Pow: function(p, x){
		return Math.pow(p, x[0] || 6);
	},

	Expo: function(p){
		return Math.pow(2, 8 * (p - 1));
	},

	Circ: function(p){
		return 1 - Math.sin(Math.acos(p));
	},

	Sine: function(p){
		return 1 - Math.sin((1 - p) * Math.PI / 2);
	},

	Back: function(p, x){
		x = x[0] || 1.618;
		return Math.pow(p, 2) * ((x + 1) * p - x);
	},

	Bounce: function(p){
		var value;
		for (var a = 0, b = 1; 1; a += b, b /= 2){
			if (p >= (7 - 4 * a) / 11){
				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
				break;
			}
		}
		return value;
	},

	Elastic: function(p, x){
		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
	}

});

['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
	Fx.Transitions[transition] = new Fx.Transition(function(p){
		return Math.pow(p, [i + 2]);
	});
});


/*
---

name: Request

description: Powerful all purpose Request Class. Uses XMLHTTPRequest.

license: MIT-style license.

requires: [Element, Chain, Events, Options, Browser]

provides: Request

...
*/

var Request = new Class({

	Implements: [Chain, Events, Options],

	options: {/*
		onRequest: $empty,
		onComplete: $empty,
		onCancel: $empty,
		onSuccess: $empty,
		onFailure: $empty,
		onException: $empty,*/
		url: '',
		data: '',
		headers: {
			'X-Requested-With': 'XMLHttpRequest',
			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
		},
		async: true,
		format: false,
		method: 'post',
		link: 'ignore',
		isSuccess: null,
		emulation: true,
		urlEncoded: true,
		encoding: 'utf-8',
		evalScripts: false,
		evalResponse: false,
		noCache: false
	},

	initialize: function(options){
		this.xhr = new Browser.Request();
		this.setOptions(options);
		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
		this.headers = new Hash(this.options.headers);
	},

	onStateChange: function(){
		if (this.xhr.readyState != 4 || !this.running) return;
		this.running = false;
		this.status = 0;
		$try(function(){
			this.status = this.xhr.status;
		}.bind(this));
		this.xhr.onreadystatechange = $empty;
		if (this.options.isSuccess.call(this, this.status)){
			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
			this.success(this.response.text, this.response.xml);
		} else {
			this.response = {text: null, xml: null};
			this.failure();
		}
	},

	isSuccess: function(){
		return ((this.status >= 200) && (this.status < 300));
	},

	processScripts: function(text){
		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
		return text.stripScripts(this.options.evalScripts);
	},

	success: function(text, xml){
		this.onSuccess(this.processScripts(text), xml);
	},

	onSuccess: function(){
		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
	},

	failure: function(){
		this.onFailure();
	},

	onFailure: function(){
		this.fireEvent('complete').fireEvent('failure', this.xhr);
	},

	setHeader: function(name, value){
		this.headers.set(name, value);
		return this;
	},

	getHeader: function(name){
		return $try(function(){
			return this.xhr.getResponseHeader(name);
		}.bind(this));
	},

	check: function(){
		if (!this.running) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
		}
		return false;
	},

	send: function(options){
		if (!this.check(options)) return this;
		this.running = true;

		var type = $type(options);
		if (type == 'string' || type == 'element') options = {data: options};

		var old = this.options;
		options = $extend({data: old.data, url: old.url, method: old.method}, options);
		var data = options.data, url = String(options.url), method = options.method.toLowerCase();

		switch ($type(data)){
			case 'element': data = document.id(data).toQueryString(); break;
			case 'object': case 'hash': data = Hash.toQueryString(data);
		}

		if (this.options.format){
			var format = 'format=' + this.options.format;
			data = (data) ? format + '&' + data : format;
		}

		if (this.options.emulation && !['get', 'post'].contains(method)){
			var _method = '_method=' + method;
			data = (data) ? _method + '&' + data : _method;
			method = 'post';
		}

		if (this.options.urlEncoded && method == 'post'){
			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
		}

		if (this.options.noCache){
			var noCache = 'noCache=' + new Date().getTime();
			data = (data) ? noCache + '&' + data : noCache;
		}

		var trimPosition = url.lastIndexOf('/');
		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);

		if (data && method == 'get'){
			url = url + (url.contains('?') ? '&' : '?') + data;
			data = null;
		}

		this.xhr.open(method.toUpperCase(), url, this.options.async);

		this.xhr.onreadystatechange = this.onStateChange.bind(this);

		this.headers.each(function(value, key){
			try {
				this.xhr.setRequestHeader(key, value);
			} catch (e){
				this.fireEvent('exception', [key, value]);
			}
		}, this);

		this.fireEvent('request');
		this.xhr.send(data);
		if (!this.options.async) this.onStateChange();
		return this;
	},

	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.xhr.abort();
		this.xhr.onreadystatechange = $empty;
		this.xhr = new Browser.Request();
		this.fireEvent('cancel');
		return this;
	}

});

(function(){

var methods = {};
['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
	methods[method] = function(){
		var params = Array.link(arguments, {url: String.type, data: $defined});
		return this.send($extend(params, {method: method}));
	};
});

Request.implement(methods);

})();

Element.Properties.send = {

	set: function(options){
		var send = this.retrieve('send');
		if (send) send.cancel();
		return this.eliminate('send').store('send:options', $extend({
			data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
		}, options));
	},

	get: function(options){
		if (options || !this.retrieve('send')){
			if (options || !this.retrieve('send:options')) this.set('send', options);
			this.store('send', new Request(this.retrieve('send:options')));
		}
		return this.retrieve('send');
	}

};

Element.implement({

	send: function(url){
		var sender = this.get('send');
		sender.send({data: this, url: url || sender.options.url});
		return this;
	}

});


/*
---

name: Request.HTML

description: Extends the basic Request Class with additional methods for interacting with HTML responses.

license: MIT-style license.

requires: [Request, Element]

provides: Request.HTML

...
*/

Request.HTML = new Class({

	Extends: Request,

	options: {
		update: false,
		append: false,
		evalScripts: true,
		filter: false
	},

	processHTML: function(text){
		var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
		text = (match) ? match[1] : text;

		var container = new Element('div');

		return $try(function(){
			var root = '<root>' + text + '</root>', doc;
			if (Browser.Engine.trident){
				doc = new ActiveXObject('Microsoft.XMLDOM');
				doc.async = false;
				doc.loadXML(root);
			} else {
				doc = new DOMParser().parseFromString(root, 'text/xml');
			}
			root = doc.getElementsByTagName('root')[0];
			if (!root) return null;
			for (var i = 0, k = root.childNodes.length; i < k; i++){
				var child = Element.clone(root.childNodes[i], true, true);
				if (child) container.grab(child);
			}
			return container;
		}) || container.set('html', text);
	},

	success: function(text){
		var options = this.options, response = this.response;

		response.html = text.stripScripts(function(script){
			response.javascript = script;
		});

		var temp = this.processHTML(response.html);

		response.tree = temp.childNodes;
		response.elements = temp.getElements('*');

		if (options.filter) response.tree = response.elements.filter(options.filter);
		if (options.update) document.id(options.update).empty().set('html', response.html);
		else if (options.append) document.id(options.append).adopt(temp.getChildren());
		if (options.evalScripts) $exec(response.javascript);

		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
	}

});

Element.Properties.load = {

	set: function(options){
		var load = this.retrieve('load');
		if (load) load.cancel();
		return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options));
	},

	get: function(options){
		if (options || ! this.retrieve('load')){
			if (options || !this.retrieve('load:options')) this.set('load', options);
			this.store('load', new Request.HTML(this.retrieve('load:options')));
		}
		return this.retrieve('load');
	}

};

Element.implement({

	load: function(){
		this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
		return this;
	}

});


/*
---

name: Request.JSON

description: Extends the basic Request Class with additional methods for sending and receiving JSON data.

license: MIT-style license.

requires: [Request, JSON]

provides: [Request.JSON]

...
*/

Request.JSON = new Class({

	Extends: Request,

	options: {
		secure: true
	},

	initialize: function(options){
		this.parent(options);
		this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
	},

	success: function(text){
		this.response.json = JSON.decode(text, this.options.secure);
		this.onSuccess(this.response.json, text);
	}

});
//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2009 Aaron Newton <http://clientcide.com/>, Valerio Proietti <http://mad4milk.net> & the MooTools team <http://mootools.net/developers>, MIT Style License.

/*
---

script: More.js

name: More

description: MooTools More

license: MIT-style license

requires:
  - Core/MooTools

provides: [MooTools.More]

...
*/

MooTools.More = {
	'version': '1.2.5.1',
	'build': '254884f2b83651bf95260eed5c6cceb838e22d8e'
};


/*
---

script: MooTools.Lang.js

name: MooTools.Lang

description: Provides methods for localization.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Events
  - /MooTools.More

provides: [Lang]

...
*/

(function(){

	var data = {
		language: 'en-US',
		languages: {
			'en-US': {}
		},
		cascades: ['en-US']
	};

	var cascaded;

	MooTools.lang = new Events();

	$extend(MooTools.lang, {

		setLanguage: function(lang){
			if (!data.languages[lang]) return this;
			data.language = lang;
			this.load();
			this.fireEvent('langChange', lang);
			return this;
		},

		load: function() {
			var langs = this.cascade(this.getCurrentLanguage());
			cascaded = {};
			$each(langs, function(set, setName){
				cascaded[setName] = this.lambda(set);
			}, this);
		},

		getCurrentLanguage: function(){
			return data.language;
		},

		addLanguage: function(lang){
			data.languages[lang] = data.languages[lang] || {};
			return this;
		},

		cascade: function(lang){
			var cascades = (data.languages[lang] || {}).cascades || [];
			cascades.combine(data.cascades);
			cascades.erase(lang).push(lang);
			var langs = cascades.map(function(lng){
				return data.languages[lng];
			}, this);
			return $merge.apply(this, langs);
		},

		lambda: function(set) {
			(set || {}).get = function(key, args){
				return $lambda(set[key]).apply(this, $splat(args));
			};
			return set;
		},

		get: function(set, key, args){
			if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
		},

		set: function(lang, set, members){
			this.addLanguage(lang);
			langData = data.languages[lang];
			if (!langData[set]) langData[set] = {};
			$extend(langData[set], members);
			if (lang == this.getCurrentLanguage()){
				this.load();
				this.fireEvent('langChange', lang);
			}
			return this;
		},

		list: function(){
			return Hash.getKeys(data.languages);
		}

	});

})();


/*
---

script: Log.js

name: Log

description: Provides basic logging functionality for plugins to implement.

license: MIT-style license

authors:
  - Guillermo Rauch
  - Thomas Aylott
  - Scott Kyle

requires:
  - Core/Class
  - /MooTools.More

provides: [Log]

...
*/

(function(){

var global = this;

var log = function(){
	if (global.console && console.log){
		try {
			console.log.apply(console, arguments);
		} catch(e) {
			console.log(Array.slice(arguments));
		}
	} else {
		Log.logged.push(arguments);
	}
	return this;
};

var disabled = function(){
	this.logged.push(arguments);
	return this;
};

this.Log = new Class({

	logged: [],

	log: disabled,

	resetLog: function(){
		this.logged.empty();
		return this;
	},

	enableLog: function(){
		this.log = log;
		this.logged.each(function(args){
			this.log.apply(this, args);
		}, this);
		return this.resetLog();
	},

	disableLog: function(){
		this.log = disabled;
		return this;
	}

});

Log.extend(new Log).enableLog();

// legacy
Log.logger = function(){
	return this.log.apply(this, arguments);
};

})();


/*
---

script: Class.Refactor.js

name: Class.Refactor

description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Class
  - /MooTools.More

# Some modules declare themselves dependent on Class.Refactor
provides: [Class.refactor, Class.Refactor]

...
*/

Class.refactor = function(original, refactors){

	$each(refactors, function(item, name){
		var origin = original.prototype[name];
		if (origin && (origin = origin._origin ? origin._origin: origin) && typeof item == 'function') original.implement(name, function(){
			var old = this.previous;
			this.previous = origin;
			var value = item.apply(this, arguments);
			this.previous = old;
			return value;
		}); else original.implement(name, item);
	});

	return original;

};


/*
---

script: Class.Binds.js

name: Class.Binds

description: Automagically binds specified methods in a class to the instance of the class.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Class
  - /MooTools.More

provides: [Class.Binds]

...
*/

Class.Mutators.Binds = function(binds){
    return binds;
};

Class.Mutators.initialize = function(initialize){
	return function(){
		$splat(this.Binds).each(function(name){
			var original = this[name];
			if (original) this[name] = original.bind(this);
		}, this);
		return initialize.apply(this, arguments);
	};
};


/*
---

script: Class.Occlude.js

name: Class.Occlude

description: Prevents a class from being applied to a DOM element twice.

license: MIT-style license.

authors:
  - Aaron Newton

requires:
  - Core/Class
  - Core/Element
  - /MooTools.More

provides: [Class.Occlude]

...
*/

Class.Occlude = new Class({

	occlude: function(property, element){
		element = document.id(element || this.element);
		var instance = element.retrieve(property || this.property);
		if (instance && !$defined(this.occluded))
			return this.occluded = instance;

		this.occluded = false;
		element.store(property || this.property, this);
		return this.occluded;
	}

});


/*
---

script: Chain.Wait.js

name: Chain.Wait

description: value, Adds a method to inject pauses between chained events.

license: MIT-style license.

authors:
  - Aaron Newton

requires:
  - Core/Chain
  - Core/Element
  - Core/Fx
  - /MooTools.More

provides: [Chain.Wait]

...
*/

(function(){

	var wait = {
		wait: function(duration){
			return this.chain(function(){
				this.callChain.delay($pick(duration, 500), this);
			}.bind(this));
		}
	};

	Chain.implement(wait);

	if (window.Fx){
		Fx.implement(wait);
		['Css', 'Tween', 'Elements'].each(function(cls){
			if (Fx[cls]) Fx[cls].implement(wait);
		});
	}

	Element.implement({
		chains: function(effects){
			$splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect){
				effect = this.get(effect);
				if (!effect) return;
				effect.setOptions({
					link:'chain'
				});
			}, this);
			return this;
		},
		pauseFx: function(duration, effect){
			this.chains(effect).get($pick(effect, 'tween')).wait(duration);
			return this;
		}
	});

})();


/*
---

script: Array.Extras.js

name: Array.Extras

description: Extends the Array native object to include useful methods to work with arrays.

license: MIT-style license

authors:
  - Christoph Pojer

requires:
  - Core/Array

provides: [Array.Extras]

...
*/
Array.implement({

	min: function(){
		return Math.min.apply(null, this);
	},

	max: function(){
		return Math.max.apply(null, this);
	},

	average: function(){
		return this.length ? this.sum() / this.length : 0;
	},

	sum: function(){
		var result = 0, l = this.length;
		if (l){
			do {
				result += this[--l];
			} while (l);
		}
		return result;
	},

	unique: function(){
		return [].combine(this);
	},

	shuffle: function(){
		for (var i = this.length; i && --i;){
			var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
			this[i] = this[r];
			this[r] = temp;
		}
		return this;
	}

});

/*
---

script: Date.js

name: Date

description: Extends the Date native object to include methods useful in managing dates.

license: MIT-style license

authors:
  - Aaron Newton
  - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
  - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
  - Scott Kyle - scott [at] appden.com; http://appden.com

requires:
  - Core/Array
  - Core/String
  - Core/Number
  - /Lang
  - /Date.English.US
  - /MooTools.More

provides: [Date]

...
*/

(function(){

var Date = this.Date;

if (!Date.now) Date.now = $time;

Date.Methods = {
	ms: 'Milliseconds',
	year: 'FullYear',
	min: 'Minutes',
	mo: 'Month',
	sec: 'Seconds',
	hr: 'Hours'
};

['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
	Date.Methods[method.toLowerCase()] = method;
});

var pad = function(what, length){
	return new Array(length - String(what).length + 1).join('0') + what;
};

Date.implement({

	set: function(prop, value){
		switch ($type(prop)){
			case 'object':
				for (var p in prop) this.set(p, prop[p]);
				break;
			case 'string':
				prop = prop.toLowerCase();
				var m = Date.Methods;
				if (m[prop]) this['set' + m[prop]](value);
		}
		return this;
	},

	get: function(prop){
		prop = prop.toLowerCase();
		var m = Date.Methods;
		if (m[prop]) return this['get' + m[prop]]();
		return null;
	},

	clone: function(){
		return new Date(this.get('time'));
	},

	increment: function(interval, times){
		interval = interval || 'day';
		times = $pick(times, 1);

		switch (interval){
			case 'year':
				return this.increment('month', times * 12);
			case 'month':
				var d = this.get('date');
				this.set('date', 1).set('mo', this.get('mo') + times);
				return this.set('date', d.min(this.get('lastdayofmonth')));
			case 'week':
				return this.increment('day', times * 7);
			case 'day':
				return this.set('date', this.get('date') + times);
		}

		if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');

		return this.set('time', this.get('time') + times * Date.units[interval]());
	},

	decrement: function(interval, times){
		return this.increment(interval, -1 * $pick(times, 1));
	},

	isLeapYear: function(){
		return Date.isLeapYear(this.get('year'));
	},

	clearTime: function(){
		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
	},

	diff: function(date, resolution){
		if ($type(date) == 'string') date = Date.parse(date);

		return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
	},

	getLastDayOfMonth: function(){
		return Date.daysInMonth(this.get('mo'), this.get('year'));
	},

	getDayOfYear: function(){
		return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
	},

	getWeek: function(){
		return (this.get('dayofyear') / 7).ceil();
	},

	getOrdinal: function(day){
		return Date.getMsg('ordinal', day || this.get('date'));
	},

	getTimezone: function(){
		return this.toString()
			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
	},

	getGMTOffset: function(){
		var off = this.get('timezoneOffset');
		return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
	},

	setAMPM: function(ampm){
		ampm = ampm.toUpperCase();
		var hr = this.get('hr');
		if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
		else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
		return this;
	},

	getAMPM: function(){
		return (this.get('hr') < 12) ? 'AM' : 'PM';
	},

	parse: function(str){
		this.set('time', Date.parse(str));
		return this;
	},

	isValid: function(date) {
		return !isNaN((date || this).valueOf());
	},

	format: function(f){
		if (!this.isValid()) return 'invalid date';
		f = f || '%x %X';
		f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
		var d = this;
		return f.replace(/%([a-z%])/gi,
			function($0, $1){
				switch ($1){
					case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
					case 'A': return Date.getMsg('days')[d.get('day')];
					case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
					case 'B': return Date.getMsg('months')[d.get('month')];
					case 'c': return d.toString();
					case 'd': return pad(d.get('date'), 2);
					case 'D': return d.get('date');
					case 'e': return d.get('date');
					case 'H': return pad(d.get('hr'), 2);
					case 'I': return ((d.get('hr') % 12) || 12);
					case 'j': return pad(d.get('dayofyear'), 3);
					case 'm': return pad((d.get('mo') + 1), 2);
					case 'M': return pad(d.get('min'), 2);
					case 'o': return d.get('ordinal');
					case 'p': return Date.getMsg(d.get('ampm'));
					case 's': return Math.round(d / 1000);
					case 'S': return pad(d.get('seconds'), 2);
					case 'U': return pad(d.get('week'), 2);
					case 'w': return d.get('day');
					case 'x': return d.format(Date.getMsg('shortDate'));
					case 'X': return d.format(Date.getMsg('shortTime'));
					case 'y': return d.get('year').toString().substr(2);
					case 'Y': return d.get('year');
					case 'T': return d.get('GMTOffset');
					case 'Z': return d.get('Timezone');
					case 'z': return pad(d.get('ms'), 3);
				}
				return $1;
			}
		);
	},

	toISOString: function(){
		return this.format('iso8601');
	}

});

Date.alias('toISOString', 'toJSON');
Date.alias('diff', 'compare');
Date.alias('format', 'strftime');

var formats = {
	db: '%Y-%m-%d %H:%M:%S',
	compact: '%Y%m%dT%H%M%S',
	iso8601: '%Y-%m-%dT%H:%M:%S%T',
	rfc822: '%a, %d %b %Y %H:%M:%S %Z',
	'short': '%d %b %H:%M',
	'long': '%B %d, %Y %H:%M'
};

var parsePatterns = [];
var nativeParse = Date.parse;

var parseWord = function(type, word, num){
	var ret = -1;
	var translated = Date.getMsg(type + 's');
	switch ($type(word)){
		case 'object':
			ret = translated[word.get(type)];
			break;
		case 'number':
			ret = translated[word];
			if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
			break;
		case 'string':
			var match = translated.filter(function(name){
				return this.test(name);
			}, new RegExp('^' + word, 'i'));
			if (!match.length)    throw new Error('Invalid ' + type + ' string');
			if (match.length > 1) throw new Error('Ambiguous ' + type);
			ret = match[0];
	}

	return (num) ? translated.indexOf(ret) : ret;
};

Date.extend({

	getMsg: function(key, args) {
		return MooTools.lang.get('Date', key, args);
	},

	units: {
		ms: $lambda(1),
		second: $lambda(1000),
		minute: $lambda(60000),
		hour: $lambda(3600000),
		day: $lambda(86400000),
		week: $lambda(608400000),
		month: function(month, year){
			var d = new Date;
			return Date.daysInMonth($pick(month, d.get('mo')), $pick(year, d.get('year'))) * 86400000;
		},
		year: function(year){
			year = year || new Date().get('year');
			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
		}
	},

	daysInMonth: function(month, year){
		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
	},

	isLeapYear: function(year){
		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
	},

	parse: function(from){
		var t = $type(from);
		if (t == 'number') return new Date(from);
		if (t != 'string') return from;
		from = from.clean();
		if (!from.length) return null;

		var parsed;
		parsePatterns.some(function(pattern){
			var bits = pattern.re.exec(from);
			return (bits) ? (parsed = pattern.handler(bits)) : false;
		});

		return parsed || new Date(nativeParse(from));
	},

	parseDay: function(day, num){
		return parseWord('day', day, num);
	},

	parseMonth: function(month, num){
		return parseWord('month', month, num);
	},

	parseUTC: function(value){
		var localDate = new Date(value);
		var utcSeconds = Date.UTC(
			localDate.get('year'),
			localDate.get('mo'),
			localDate.get('date'),
			localDate.get('hr'),
			localDate.get('min'),
			localDate.get('sec'),
			localDate.get('ms')
		);
		return new Date(utcSeconds);
	},

	orderIndex: function(unit){
		return Date.getMsg('dateOrder').indexOf(unit) + 1;
	},

	defineFormat: function(name, format){
		formats[name] = format;
	},

	defineFormats: function(formats){
		for (var name in formats) Date.defineFormat(name, formats[name]);
	},

	parsePatterns: parsePatterns, // this is deprecated

	defineParser: function(pattern){
		parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
	},

	defineParsers: function(){
		Array.flatten(arguments).each(Date.defineParser);
	},

	define2DigitYearStart: function(year){
		startYear = year % 100;
		startCentury = year - startYear;
	}

});

var startCentury = 1900;
var startYear = 70;

var regexOf = function(type){
	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
		return name.substr(0, 3);
	}).join('|') + ')[a-z]*');
};

var replacers = function(key){
	switch(key){
		case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
			return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
		case 'X':
			return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?';
	}
	return null;
};

var keys = {
	d: /[0-2]?[0-9]|3[01]/,
	H: /[01]?[0-9]|2[0-3]/,
	I: /0?[1-9]|1[0-2]/,
	M: /[0-5]?\d/,
	s: /\d+/,
	o: /[a-z]*/,
	p: /[ap]\.?m\.?/,
	y: /\d{2}|\d{4}/,
	Y: /\d{4}/,
	T: /Z|[+-]\d{2}(?::?\d{2})?/
};

keys.m = keys.I;
keys.S = keys.M;

var currentLanguage;

var recompile = function(language){
	currentLanguage = language;

	keys.a = keys.A = regexOf('days');
	keys.b = keys.B = regexOf('months');

	parsePatterns.each(function(pattern, i){
		if (pattern.format) parsePatterns[i] = build(pattern.format);
	});
};

var build = function(format){
	if (!currentLanguage) return {format: format};

	var parsed = [];
	var re = (format.source || format) // allow format to be regex
	 .replace(/%([a-z])/gi,
		function($0, $1){
			return replacers($1) || $0;
		}
	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
	 .replace(/%([a-z%])/gi,
		function($0, $1){
			var p = keys[$1];
			if (!p) return $1;
			parsed.push($1);
			return '(' + p.source + ')';
		}
	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff]'); // handle unicode words

	return {
		format: format,
		re: new RegExp('^' + re + '$', 'i'),
		handler: function(bits){
			bits = bits.slice(1).associate(parsed);
			var date = new Date().clearTime(),
				year = bits.y || bits.Y;

			if (year != null) handle.call(date, 'y', year); // need to start in the right year
			if ('d' in bits) handle.call(date, 'd', 1);
			if ('m' in bits || 'b' in bits || 'B' in bits) handle.call(date, 'm', 1);

			for (var key in bits) handle.call(date, key, bits[key]);
			return date;
		}
	};
};

var handle = function(key, value){
	if (!value) return this;

	switch(key){
		case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
		case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
		case 'd': return this.set('date', value);
		case 'H': case 'I': return this.set('hr', value);
		case 'm': return this.set('mo', value - 1);
		case 'M': return this.set('min', value);
		case 'p': return this.set('ampm', value.replace(/\./g, ''));
		case 'S': return this.set('sec', value);
		case 's': return this.set('ms', ('0.' + value) * 1000);
		case 'w': return this.set('day', value);
		case 'Y': return this.set('year', value);
		case 'y':
			value = +value;
			if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
			return this.set('year', value);
		case 'T':
			if (value == 'Z') value = '+00';
			var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
			offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
			return this.set('time', this - offset * 60000);
	}

	return this;
};

Date.defineParsers(
	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
	'%Y %b( %d%o( %X)?)?', // Same as above with year coming first
	'%o %b %d %X %T %Y' // "Thu Oct 22 08:11:23 +0000 2009"
);

MooTools.lang.addEvent('langChange', function(language){
	if (MooTools.lang.get('Date')) recompile(language);
}).fireEvent('langChange', MooTools.lang.getCurrentLanguage());

})();


/*
---

script: Date.Extras.js

name: Date.Extras

description: Extends the Date native object to include extra methods (on top of those in Date.js).

license: MIT-style license

authors:
  - Aaron Newton
  - Scott Kyle

requires:
  - /Date

provides: [Date.Extras]

...
*/

Date.implement({

	timeDiffInWords: function(relative_to){
		return Date.distanceOfTimeInWords(this, relative_to || new Date);
	},

	timeDiff: function(to, joiner){
		if (to == null) to = new Date;
		var delta = ((to - this) / 1000).toInt();
		if (!delta) return '0s';

		var durations = {s: 60, m: 60, h: 24, d: 365, y: 0};
		var duration, vals = [];

		for (var step in durations){
			if (!delta) break;
			if ((duration = durations[step])){
				vals.unshift((delta % duration) + step);
				delta = (delta / duration).toInt();
			} else {
				vals.unshift(delta + step);
			}
		}

		return vals.join(joiner || ':');
	}

});

Date.alias('timeDiffInWords', 'timeAgoInWords');

Date.extend({

	distanceOfTimeInWords: function(from, to){
		return Date.getTimePhrase(((to - from) / 1000).toInt());
	},

	getTimePhrase: function(delta){
		var suffix = (delta < 0) ? 'Until' : 'Ago';
		if (delta < 0) delta *= -1;

		var units = {
			minute: 60,
			hour: 60,
			day: 24,
			week: 7,
			month: 52 / 12,
			year: 12,
			eon: Infinity
		};

		var msg = 'lessThanMinute';

		for (var unit in units){
			var interval = units[unit];
			if (delta < 1.5 * interval){
				if (delta > 0.75 * interval) msg = unit;
				break;
			}
			delta /= interval;
			msg = unit + 's';
		}

		return Date.getMsg(msg + suffix, delta).substitute({delta: delta.round()});
	}

});


Date.defineParsers(

	{
		// "today", "tomorrow", "yesterday"
		re: /^(?:tod|tom|yes)/i,
		handler: function(bits){
			var d = new Date().clearTime();
			switch(bits[0]){
				case 'tom': return d.increment();
				case 'yes': return d.decrement();
				default: 	return d;
			}
		}
	},

	{
		// "next Wednesday", "last Thursday"
		re: /^(next|last) ([a-z]+)$/i,
		handler: function(bits){
			var d = new Date().clearTime();
			var day = d.getDay();
			var newDay = Date.parseDay(bits[2], true);
			var addDays = newDay - day;
			if (newDay <= day) addDays += 7;
			if (bits[1] == 'last') addDays -= 7;
			return d.set('date', d.getDate() + addDays);
		}
	}

);


/*
---

script: Hash.Extras.js

name: Hash.Extras

description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Hash.base
  - /MooTools.More

provides: [Hash.Extras]

...
*/

Hash.implement({

	getFromPath: function(notation){
		var source = this.getClean();
		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
			if (!source) return null;
			var prop = arguments[2] || arguments[1] || arguments[0];
			source = (prop in source) ? source[prop] : null;
			return match;
		});
		return source;
	},

	cleanValues: function(method){
		method = method || $defined;
		this.each(function(v, k){
			if (!method(v)) this.erase(k);
		}, this);
		return this;
	},

	run: function(){
		var args = arguments;
		this.each(function(v, k){
			if ($type(v) == 'function') v.run(args);
		});
	}

});

/*
---

script: String.Extras.js

name: String.Extras

description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).

license: MIT-style license

authors:
  - Aaron Newton
  - Guillermo Rauch
  - Christopher Pitt

requires:
  - Core/String
  - Core/$util
  - Core/Array

provides: [String.Extras]

...
*/

(function(){

var special = {
	'a': '[àáâãäåăą]',
	'A': '[ÀÁÂÃÄÅĂĄ]',
	'c': '[ćčç]',
	'C': '[ĆČÇ]',
	'd': '[ďđ]',
	'D': '[ĎÐ]',
	'e': '[èéêëěę]',
	'E': '[ÈÉÊËĚĘ]',
	'g': '[ğ]',
	'G': '[Ğ]',
	'i': '[ìíîï]',
	'I': '[ÌÍÎÏ]',
	'l': '[ĺľł]',
	'L': '[ĹĽŁ]',
	'n': '[ñňń]',
	'N': '[ÑŇŃ]',
	'o': '[òóôõöøő]',
	'O': '[ÒÓÔÕÖØ]',
	'r': '[řŕ]',
	'R': '[ŘŔ]',
	's': '[ššş]',
	'S': '[ŠŞŚ]',
	't': '[ťţ]',
	'T': '[ŤŢ]',
	'ue': '[ü]',
	'UE': '[Ü]',
	'u': '[ùúûůµ]',
	'U': '[ÙÚÛŮ]',
	'y': '[ÿý]',
	'Y': '[ŸÝ]',
	'z': '[žźż]',
	'Z': '[ŽŹŻ]',
	'th': '[þ]',
	'TH': '[Þ]',
	'dh': '[ð]',
	'DH': '[Ð]',
	'ss': '[ß]',
	'oe': '[œ]',
	'OE': '[Œ]',
	'ae': '[æ]',
	'AE': '[Æ]'
},

tidy = {
	' ': '[\xa0\u2002\u2003\u2009]',
	'*': '[\xb7]',
	'\'': '[\u2018\u2019]',
	'"': '[\u201c\u201d]',
	'...': '[\u2026]',
	'-': '[\u2013]',
	'--': '[\u2014]',
	'&raquo;': '[\uFFFD]'
};

function walk(string, replacements) {
	var result = string;

	for (key in replacements) {
		result = result.replace(new RegExp(replacements[key], 'g'), key);
	}

	return result;
}

function getRegForTag(tag, contents) {
	tag = tag || '';
	var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>";
	reg = new RegExp(regstr, "gi");
	return reg;
};

String.implement({

	standardize: function(){
		return walk(this, special);
	},

	repeat: function(times){
		return new Array(times + 1).join(this);
	},

	pad: function(length, str, dir){
		if (this.length >= length) return this;
		var pad = (str == null ? ' ' : '' + str).repeat(length - this.length).substr(0, length - this.length);
		if (!dir || dir == 'right') return this + pad;
		if (dir == 'left') return pad + this;
		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
	},

	getTags: function(tag, contents){
		return this.match(getRegForTag(tag, contents)) || [];
	},

	stripTags: function(tag, contents){
		return this.replace(getRegForTag(tag, contents), '');
	},

	tidy: function(){
		return walk(this, tidy);
	}

});

})();


/*
---

script: String.QueryString.js

name: String.QueryString

description: Methods for dealing with URI query strings.

license: MIT-style license

authors:
  - Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti

requires:
  - Core/Array
  - Core/String
  - /MooTools.More

provides: [String.QueryString]

...
*/

String.implement({

	parseQueryString: function(decodeKeys, decodeValues){
		if (decodeKeys == null) decodeKeys = true;
		if (decodeValues == null) decodeValues = true;
		var vars = this.split(/[&;]/), res = {};
		if (vars.length) vars.each(function(val){
			var index = val.indexOf('='),
				keys = index < 0 ? [''] : val.substr(0, index).match(/([^\]\[]+|(\B)(?=\]))/g),
				value = decodeValues ? decodeURIComponent(val.substr(index + 1)) : val.substr(index + 1),
				obj = res;
			keys.each(function(key, i){
				if (decodeKeys) key = decodeURIComponent(key);
				var current = obj[key];
				if(i < keys.length - 1)
					obj = obj[key] = current || {};
				else if($type(current) == 'array')
					current.push(value);
				else
					obj[key] = $defined(current) ? [current, value] : value;
			});
		});
		return res;
	},

	cleanQueryString: function(method){
		return this.split('&').filter(function(val){
			var index = val.indexOf('='),
			key = index < 0 ? '' : val.substr(0, index),
			value = val.substr(index + 1);
			return method ? method.run([key, value]) : $chk(value);
		}).join('&');
	}

});

/*
---

script: URI.js

name: URI

description: Provides methods useful in managing the window location and uris.

license: MIT-style license

authors:
  - Sebastian Markbåge
  - Aaron Newton

requires:
  - Core/Selectors
  - /String.QueryString

provides: [URI]

...
*/

var URI = new Class({

	Implements: Options,

	options: {
		/*base: false*/
	},

	regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
	parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
	schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},

	initialize: function(uri, options){
		this.setOptions(options);
		var base = this.options.base || URI.base;
		if(!uri) uri = base;

		if (uri && uri.parsed) this.parsed = $unlink(uri.parsed);
		else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
	},

	parse: function(value, base){
		var bits = value.match(this.regex);
		if (!bits) return false;
		bits.shift();
		return this.merge(bits.associate(this.parts), base);
	},

	merge: function(bits, base){
		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
		if (base){
			this.parts.every(function(part){
				if (bits[part]) return false;
				bits[part] = base[part] || '';
				return true;
			});
		}
		bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
		bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
		return bits;
	},

	parseDirectory: function(directory, baseDirectory) {
		directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
		if (!directory.test(URI.regs.directoryDot)) return directory;
		var result = [];
		directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
			if (dir == '..' && result.length > 0) result.pop();
			else if (dir != '.') result.push(dir);
		});
		return result.join('/') + '/';
	},

	combine: function(bits){
		return bits.value || bits.scheme + '://' +
			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
			(bits.directory || '/') + (bits.file || '') +
			(bits.query ? '?' + bits.query : '') +
			(bits.fragment ? '#' + bits.fragment : '');
	},

	set: function(part, value, base){
		if (part == 'value'){
			var scheme = value.match(URI.regs.scheme);
			if (scheme) scheme = scheme[1];
			if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
			else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
		} else if (part == 'data') {
			this.setData(value);
		} else {
			this.parsed[part] = value;
		}
		return this;
	},

	get: function(part, base){
		switch(part){
			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
			case 'data' : return this.getData();
		}
		return this.parsed[part] || '';
	},

	go: function(){
		document.location.href = this.toString();
	},

	toURI: function(){
		return this;
	},

	getData: function(key, part){
		var qs = this.get(part || 'query');
		if (!$chk(qs)) return key ? null : {};
		var obj = qs.parseQueryString();
		return key ? obj[key] : obj;
	},

	setData: function(values, merge, part){
		if (typeof values == 'string'){
			data = this.getData();
			data[arguments[0]] = arguments[1];
			values = data;
		} else if (merge) {
			values = $merge(this.getData(), values);
		}
		return this.set(part || 'query', Hash.toQueryString(values));
	},

	clearData: function(part){
		return this.set(part || 'query', '');
	}

});

URI.prototype.toString = URI.prototype.valueOf = function(){
	return this.get('value');
};

URI.regs = {
	endSlash: /\/$/,
	scheme: /^(\w+):/,
	directoryDot: /\.\/|\.$/
};

URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});

String.implement({

	toURI: function(options){
		return new URI(this, options);
	}

});


/*
---

script: URI.Relative.js

name: URI.Relative

description: Extends the URI class to add methods for computing relative and absolute urls.

license: MIT-style license

authors:
  - Sebastian Markbåge


requires:
  - /Class.refactor
  - /URI

provides: [URI.Relative]

...
*/

URI = Class.refactor(URI, {

	combine: function(bits, base){
		if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
			return this.previous.apply(this, arguments);
		var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');

		if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;

		var baseDir = base.directory.split('/'),
			relDir = bits.directory.split('/'),
			path = '',
			offset;

		var i = 0;
		for(offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
		for(i = 0; i < baseDir.length - offset - 1; i++) path += '../';
		for(i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';

		return (path || (bits.file ? '' : './')) + end;
	},

	toAbsolute: function(base){
		base = new URI(base);
		if (base) base.set('directory', '').set('file', '');
		return this.toRelative(base);
	},

	toRelative: function(base){
		return this.get('value', new URI(base));
	}

});


/*
---

script: Element.Forms.js

name: Element.Forms

description: Extends the Element native object to include methods useful in managing inputs.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element
  - /MooTools.More

provides: [Element.Forms]

...
*/

Element.implement({

	tidy: function(){
		this.set('value', this.get('value').tidy());
	},

	getTextInRange: function(start, end){
		return this.get('value').substring(start, end);
	},

	getSelectedText: function(){
		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
		return document.selection.createRange().text;
	},

	getSelectedRange: function() {
		if ($defined(this.selectionStart)) return {start: this.selectionStart, end: this.selectionEnd};
		var pos = {start: 0, end: 0};
		var range = this.getDocument().selection.createRange();
		if (!range || range.parentElement() != this) return pos;
		var dup = range.duplicate();
		if (this.type == 'text') {
			pos.start = 0 - dup.moveStart('character', -100000);
			pos.end = pos.start + range.text.length;
		} else {
			var value = this.get('value');
			var offset = value.length;
			dup.moveToElementText(this);
			dup.setEndPoint('StartToEnd', range);
			if(dup.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
			pos.end = offset - dup.text.length;
			dup.setEndPoint('StartToStart', range);
			pos.start = offset - dup.text.length;
		}
		return pos;
	},

	getSelectionStart: function(){
		return this.getSelectedRange().start;
	},

	getSelectionEnd: function(){
		return this.getSelectedRange().end;
	},

	setCaretPosition: function(pos){
		if (pos == 'end') pos = this.get('value').length;
		this.selectRange(pos, pos);
		return this;
	},

	getCaretPosition: function(){
		return this.getSelectedRange().start;
	},

	selectRange: function(start, end){
		if (this.setSelectionRange) {
			this.focus();
			this.setSelectionRange(start, end);
		} else {
			var value = this.get('value');
			var diff = value.substr(start, end - start).replace(/\r/g, '').length;
			start = value.substr(0, start).replace(/\r/g, '').length;
			var range = this.createTextRange();
			range.collapse(true);
			range.moveEnd('character', start + diff);
			range.moveStart('character', start);
			range.select();
		}
		return this;
	},

	insertAtCursor: function(value, select){
		var pos = this.getSelectedRange();
		var text = this.get('value');
		this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
		if ($pick(select, true)) this.selectRange(pos.start, pos.start + value.length);
		else this.setCaretPosition(pos.start + value.length);
		return this;
	},

	insertAroundCursor: function(options, select){
		options = $extend({
			before: '',
			defaultMiddle: '',
			after: ''
		}, options);
		var value = this.getSelectedText() || options.defaultMiddle;
		var pos = this.getSelectedRange();
		var text = this.get('value');
		if (pos.start == pos.end){
			this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
			this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
		} else {
			var current = text.substring(pos.start, pos.end);
			this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
			var selStart = pos.start + options.before.length;
			if ($pick(select, true)) this.selectRange(selStart, selStart + current.length);
			else this.setCaretPosition(selStart + text.length);
		}
		return this;
	}

});

/*
---

script: Elements.From.js

name: Elements.From

description: Returns a collection of elements from a string of html.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element
  - /MooTools.More

provides: [Elements.from, Elements.From]

...
*/

Elements.from = function(text, excludeScripts){
	if ($pick(excludeScripts, true)) text = text.stripScripts();

	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);

	if (match){
		container = new Element('table');
		var tag = match[1].toLowerCase();
		if (['td', 'th', 'tr'].contains(tag)){
			container = new Element('tbody').inject(container);
			if (tag != 'tr') container = new Element('tr').inject(container);
		}
	}

	return (container || new Element('div')).set('html', text).getChildren();
};


/*
---

script: Element.Delegation.js

name: Element.Delegation

description: Extends the Element native object to include the delegate method for more efficient event management.

credits:
  - "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"

license: MIT-style license

authors:
  - Aaron Newton
  - Daniel Steigerwald

requires:
  - Core/Element.Event
  - Core/Selectors
  - /MooTools.More

provides: [Element.Delegation]

...
*/

(function(addEvent, removeEvent){

	var match = /(.*?):relay\(((?:\(.*?\)|.)+)\)$/,
		combinators = /[+>~\s]/,
		splitType = function(type){
			var bits = type.match(match);
			return !bits ? {event: type} : {
				event: bits[1],
				selector: bits[2]
			};
		},
		check = function(e, selector){
			var t = e.target;
			if (combinators.test(selector = selector.trim())){
				var els = this.getElements(selector);
				for (var i = els.length; i--; ){
					var el = els[i];
					if (t == el || el.hasChild(t)) return el;
				}
			} else {
				for ( ; t && t != this; t = t.parentNode){
					if (Element.match(t, selector)) return document.id(t);
				}
			}
			return null;
		};

	Element.implement({

		addEvent: function(type, fn){
			var split = splitType(type);
			if (split.selector){
				var monitors = this.retrieve('delegation:_delegateMonitors', {});
				if (!monitors[type]){
					var monitor = function(e){
						var el = check.call(this, e, split.selector);
						if (el) this.fireEvent(type, [e, el], 0, el);
					}.bind(this);
					monitors[type] = monitor;
					addEvent.call(this, split.event, monitor);
				}
			}
			return addEvent.apply(this, arguments);
		},

		removeEvent: function(type, fn){
			var split = splitType(type);
			if (split.selector){
				var events = this.retrieve('events');
				if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;

				if (fn) removeEvent.apply(this, [type, fn]);
				else removeEvent.apply(this, type);

				events = this.retrieve('events');
				if (events && events[type] && events[type].keys.length == 0){
					var monitors = this.retrieve('delegation:_delegateMonitors', {});
					removeEvent.apply(this, [split.event, monitors[type]]);
					delete monitors[type];
				}
				return this;
			}
			return removeEvent.apply(this, arguments);
		},

		fireEvent: function(type, args, delay, bind){
			var events = this.retrieve('events');
			var e, el;
			if (args) {
				e = args[0];
				el = args[1];
			}
			if (!events || !events[type]) return this;
			events[type].keys.each(function(fn){
				fn.create({bind: bind || this, delay: delay, arguments: args})();
			}, this);
			return this;
		}

	});

})(Element.prototype.addEvent, Element.prototype.removeEvent);

try {
	if (typeof HTMLElement != 'undefined')
		HTMLElement.prototype.fireEvent = Element.prototype.fireEvent;
} catch(e){}


/*
---

script: Element.Measure.js

name: Element.Measure

description: Extends the Element native object to include methods useful in measuring dimensions.

credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element.Style
  - Core/Element.Dimensions
  - /MooTools.More

provides: [Element.Measure]

...
*/

Element.implement({

	measure: function(fn){
		var vis = function(el) {
			return !!(!el || el.offsetHeight || el.offsetWidth);
		};
		if (vis(this)) return fn.apply(this);
		var parent = this.getParent(),
			restorers = [],
			toMeasure = [];
		while (!vis(parent) && parent != document.body) {
			toMeasure.push(parent.expose());
			parent = parent.getParent();
		}
		var restore = this.expose();
		var result = fn.apply(this);
		restore();
		toMeasure.each(function(restore){
			restore();
		});
		return result;
	},

	expose: function(){
		if (this.getStyle('display') != 'none') return $empty;
		var before = this.style.cssText;
		this.setStyles({
			display: 'block',
			position: 'absolute',
			visibility: 'hidden'
		});
		return function(){
			this.style.cssText = before;
		}.bind(this);
	},

	getDimensions: function(options){
		options = $merge({computeSize: false},options);
		var dim = {};
		var getSize = function(el, options){
			return (options.computeSize)?el.getComputedSize(options):el.getSize();
		};
		var parent = this.getParent('body');
		if (parent && this.getStyle('display') == 'none'){
			dim = this.measure(function(){
				return getSize(this, options);
			});
		} else if (parent){
			try { //safari sometimes crashes here, so catch it
				dim = getSize(this, options);
			}catch(e){}
		} else {
			dim = {x: 0, y: 0};
		}
		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
	},

	getComputedSize: function(options){
		//legacy support for my stupid spelling error
		if (options && options.plains) options.planes = options.plains;

		options = $merge({
			styles: ['padding','border'],
			planes: {
				height: ['top','bottom'],
				width: ['left','right']
			},
			mode: 'both'
		}, options);

		var size = {width: 0,height: 0};
		switch (options.mode){
			case 'vertical':
				delete size.width;
				delete options.planes.width;
				break;
			case 'horizontal':
				delete size.height;
				delete options.planes.height;
				break;
		}
		var getStyles = [];
		//this function might be useful in other places; perhaps it should be outside this function?
		$each(options.planes, function(plane, key){
			plane.each(function(edge){
				options.styles.each(function(style){
					getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
				});
			});
		});
		var styles = {};
		getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
		var subtracted = [];
		$each(options.planes, function(plane, key){ //keys: width, height, planes: ['left', 'right'], ['top','bottom']
			var capitalized = key.capitalize();
			size['total' + capitalized] = size['computed' + capitalized] = 0;
			plane.each(function(edge){ //top, left, right, bottom
				size['computed' + edge.capitalize()] = 0;
				getStyles.each(function(style, i){ //padding, border, etc.
					//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
					if (style.test(edge)){
						styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
						size['total' + capitalized] = size['total' + capitalized] + styles[style];
						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
					}
					//if width != width (so, padding-left, for instance), then subtract that from the total
					if (style.test(edge) && key != style &&
						(style.test('border') || style.test('padding')) && !subtracted.contains(style)){
						subtracted.push(style);
						size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
					}
				});
			});
		});

		['Width', 'Height'].each(function(value){
			var lower = value.toLowerCase();
			if(!$chk(size[lower])) return;

			size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
			size['total' + value] = size[lower] + size['total' + value];
			delete size['computed' + value];
		}, this);

		return $extend(styles, size);
	}

});

/*
---

script: Element.Pin.js

name: Element.Pin

description: Extends the Element native object to include the pin method useful for fixed positioning for elements.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element.Event
  - Core/Element.Dimensions
  - Core/Element.Style
  - /MooTools.More

provides: [Element.Pin]

...
*/

(function(){
	var supportsPositionFixed = false,
		supportTested = false;

	var testPositionFixed = function(){
		var test = new Element('div').setStyles({
			position: 'fixed',
			top: 0,
			right: 0
		}).inject(document.body);
		supportsPositionFixed = (test.offsetTop === 0);
		test.dispose();
		supportTested = true;
	}

	Element.implement({

		pin: function(enable, forceScroll){
			if (!supportTested) testPositionFixed();
			if (this.getStyle('display') == 'none') return this;

			var pinnedPosition,
				scroll = window.getScroll();

			if (enable !== false){
				pinnedPosition = this.getPosition(supportsPositionFixed ? document.body : this.getOffsetParent());
				if (!this.retrieve('pin:_pinned')){
					var currentPosition = {
						top: pinnedPosition.y - scroll.y,
						left: pinnedPosition.x - scroll.x
					};

					if (supportsPositionFixed && !forceScroll){
						this.setStyle('position', 'fixed').setStyles(currentPosition);
					} else {

						var parent = this.getOffsetParent(),
							position = this.getPosition(parent),
							styles = this.getStyles('left', 'top');

						if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
						if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');

						position = {
							x: styles.left.toInt() - scroll.x,
							y: styles.top.toInt() - scroll.y
						};

						var scrollFixer = function(){
							if (!this.retrieve('pin:_pinned')) return;
							var scroll = window.getScroll();
							this.setStyles({
								left: position.x + scroll.x,
								top: position.y + scroll.y
							});
						}.bind(this);

						this.store('pin:_scrollFixer', scrollFixer);
						window.addEvent('scroll', scrollFixer);
					}
					this.store('pin:_pinned', true);
				}

			} else {
				if (!this.retrieve('pin:_pinned')) return this;

				var parent = this.getParent(),
					offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());

				pinnedPosition = this.getPosition(offsetParent);

				this.store('pin:_pinned', false);
				var scrollFixer = this.retrieve('pin:_scrollFixer');
				if (!scrollFixer){
					this.setStyles({
						position: 'absolute',
						top: pinnedPosition.y + scroll.y,
						left: pinnedPosition.x + scroll.x
					});
				} else {
					this.store('pin:_scrollFixer', null);
					window.removeEvent('scroll', scrollFixer);
				}
				this.removeClass('isPinned');
			}
			return this;
		},

		unpin: function(){
			return this.pin(false);
		},

		togglepin: function(){
			return this.pin(!this.retrieve('pin:_pinned'));
		}

	});

})();


/*
---

script: Element.Position.js

name: Element.Position

description: Extends the Element native object to include methods useful positioning elements relative to others.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element.Dimensions
  - /Element.Measure

provides: [Element.Position]

...
*/

(function(){

var original = Element.prototype.position;

Element.implement({

	position: function(options){
		//call original position if the options are x/y values
		if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
		$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
		options = $merge({
			// minimum: { x: 0, y: 0 },
			// maximum: { x: 0, y: 0},
			relativeTo: document.body,
			position: {
				x: 'center', //left, center, right
				y: 'center' //top, center, bottom
			},
			edge: false,
			offset: {x: 0, y: 0},
			returnPos: false,
			relFixedPosition: false,
			ignoreMargins: false,
			ignoreScroll: false,
			allowNegative: false
		}, options);
		//compute the offset of the parent positioned element if this element is in one
		var parentOffset = {x: 0, y: 0},
				parentPositioned = false;
		/* dollar around getOffsetParent should not be necessary, but as it does not return
		 * a mootools extended element in IE, an error occurs on the call to expose. See:
		 * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
		var offsetParent = this.measure(function(){
			return document.id(this.getOffsetParent());
		});
		if (offsetParent && offsetParent != this.getDocument().body){
			parentOffset = offsetParent.measure(function(){
				return this.getPosition();
			});
			parentPositioned = offsetParent != document.id(options.relativeTo);
			options.offset.x = options.offset.x - parentOffset.x;
			options.offset.y = options.offset.y - parentOffset.y;
		}
		//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
		//topRight, topLeft, centerTop, centerBottom, center
		var fixValue = function(option){
			if ($type(option) != 'string') return option;
			option = option.toLowerCase();
			var val = {};

			if (option.test('left')) val.x = 'left';
			else if (option.test('right')) val.x = 'right';
			else val.x = 'center';

			if (option.test('upper') || option.test('top')) val.y = 'top';
			else if (option.test('bottom')) val.y = 'bottom';
			else val.y = 'center';

			return val;
		};
		options.edge = fixValue(options.edge);
		options.position = fixValue(options.position);
		if (!options.edge){
			if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
			else options.edge = {x:'left', y:'top'};
		}

		this.setStyle('position', 'absolute');
		var rel = document.id(options.relativeTo) || document.body,
				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
				top = calc.y, left = calc.x;

		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
		var pos = {},
				prefY = options.offset.y,
				prefX = options.offset.x,
				winSize = window.getSize();
		switch(options.position.x){
			case 'left':
				pos.x = left + prefX;
				break;
			case 'right':
				pos.x = left + prefX + rel.offsetWidth;
				break;
			default: //center
				pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
				break;
		}
		switch(options.position.y){
			case 'top':
				pos.y = top + prefY;
				break;
			case 'bottom':
				pos.y = top + prefY + rel.offsetHeight;
				break;
			default: //center
				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
				break;
		}
		if (options.edge){
			var edgeOffset = {};

			switch(options.edge.x){
				case 'left':
					edgeOffset.x = 0;
					break;
				case 'right':
					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
					break;
				default: //center
					edgeOffset.x = -(dim.totalWidth/2);
					break;
			}
			switch(options.edge.y){
				case 'top':
					edgeOffset.y = 0;
					break;
				case 'bottom':
					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
					break;
				default: //center
					edgeOffset.y = -(dim.totalHeight/2);
					break;
			}
			pos.x += edgeOffset.x;
			pos.y += edgeOffset.y;
		}
		pos = {
			left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
			top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
		};
		var xy = {left: 'x', top: 'y'};
		['minimum', 'maximum'].each(function(minmax) {
			['left', 'top'].each(function(lr) {
				var val = options[minmax] ? options[minmax][xy[lr]] : null;
				if (val != null && ((minmax == 'minimum') ? pos[lr] < val: pos[lr] > val)) pos[lr] = val;
			});
		});
		if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
			var winScroll = window.getScroll();
			pos.top+= winScroll.y;
			pos.left+= winScroll.x;
		}
		var relScroll = rel.getScroll();
		if (options.ignoreScroll) {
			pos.top -= relScroll.y;
			pos.left -= relScroll.x;
		} else {
			pos.top += relScroll.y;
			pos.left += relScroll.x;
		}
		if (options.ignoreMargins) {
			pos.left += (
				options.edge.x == 'right' ? dim['margin-right'] :
				options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) :
					- dim['margin-left']
			);
			pos.top += (
				options.edge.y == 'bottom' ? dim['margin-bottom'] :
				options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) :
					- dim['margin-top']
			);
		}
		pos.left = Math.ceil(pos.left);
		pos.top = Math.ceil(pos.top);
		if (options.returnPos) return pos;
		else this.setStyles(pos);
		return this;
	}

});

})();


/*
---

script: Element.Shortcuts.js

name: Element.Shortcuts

description: Extends the Element native object to include some shortcut methods.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element.Style
  - /MooTools.More

provides: [Element.Shortcuts]

...
*/

Element.implement({

	isDisplayed: function(){
		return this.getStyle('display') != 'none';
	},

	isVisible: function(){
		var w = this.offsetWidth,
			h = this.offsetHeight;
		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
	},

	toggle: function(){
		return this[this.isDisplayed() ? 'hide' : 'show']();
	},

	hide: function(){
		var d;
		try {
			//IE fails here if the element is not in the dom
			d = this.getStyle('display');
		} catch(e){}
		if (d == "none") return this;
		return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
	},

	show: function(display){
		if (!display && this.isDisplayed()) return this;
		display = display || this.retrieve('element:_originalDisplay') || 'block';
		return this.setStyle('display', (display == 'none') ? 'block' : display);
	},

	swapClass: function(remove, add){
		return this.removeClass(remove).addClass(add);
	}
});

Document.implement({
	clearSelection: function(){
		if (document.selection && document.selection.empty) {
			document.selection.empty();
		} else if (window.getSelection) {
			var selection = window.getSelection();
			if (selection && selection.removeAllRanges) selection.removeAllRanges();
		}
	}
});

/*
---

script: Form.Request.js

name: Form.Request

description: Handles the basic functionality of submitting a form and updating a dom element with the result.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Request.HTML
  - /Class.Binds
  - /Class.Occlude
  - /Spinner
  - /String.QueryString
  - /Element.Delegation

provides: [Form.Request]

...
*/

if (!window.Form) window.Form = {};

(function(){

	Form.Request = new Class({

		Binds: ['onSubmit', 'onFormValidate'],

		Implements: [Options, Events, Class.Occlude],

		options: {
			//onFailure: $empty,
			//onSuccess: #empty, //aliased to onComplete,
			//onSend: $empty
			requestOptions: {
				evalScripts: true,
				useSpinner: true,
				emulation: false,
				link: 'ignore'
			},
			sendButtonClicked: true,
			extraData: {},
			resetForm: true
		},

		property: 'form.request',

		initialize: function(form, update, options) {
			this.element = document.id(form);
			if (this.occlude()) return this.occluded;
			this.update = document.id(update);
			this.setOptions(options);
			this.makeRequest();
			if (this.options.resetForm) {
				this.request.addEvent('success', function(){
					$try(function(){ this.element.reset(); }.bind(this));
					if (window.OverText) OverText.update();
				}.bind(this));
			}
			this.attach();
		},

		toElement: function() {
			return this.element;
		},

		makeRequest: function(){
			this.request = new Request.HTML($merge({
					update: this.update,
					emulation: false,
					spinnerTarget: this.element,
					method: this.element.get('method') || 'post'
			}, this.options.requestOptions)).addEvents({
				success: function(tree, elements, html, javascript){
					['complete', 'success'].each(function(evt){
						this.fireEvent(evt, [this.update, tree, elements, html, javascript]);
					}, this);
				}.bind(this),
				failure: function(){
					this.fireEvent('complete', arguments).fireEvent('failure', arguments);
				}.bind(this),
				exception: function(){
					this.fireEvent('failure', arguments);
				}.bind(this)
			});
		},

		attach: function(attach){
			attach = $pick(attach, true);
			method = attach ? 'addEvent' : 'removeEvent';

			this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));

			var fv = this.element.retrieve('validator');
			if (fv) fv[method]('onFormValidate', this.onFormValidate);
			else this.element[method]('submit', this.onSubmit);
		},

		detach: function(){
			this.attach(false);
			return this;
		},

		//public method
		enable: function(){
			this.attach();
			return this;
		},

		//public method
		disable: function(){
			this.detach();
			return this;
		},

		onFormValidate: function(valid, form, e) {
			//if there's no event, then this wasn't a submit event
			if (!e) return;
			var fv = this.element.retrieve('validator');
			if (valid || (fv && !fv.options.stopOnFailure)) {
				if (e && e.stop) e.stop();
				this.send();
			}
		},

		onSubmit: function(e){
			var fv = this.element.retrieve('validator');
			if (fv) {
				//form validator was created after Form.Request
				this.element.removeEvent('submit', this.onSubmit);
				fv.addEvent('onFormValidate', this.onFormValidate);
				this.element.validate();
				return;
			}
			if (e) e.stop();
			this.send();
		},

		saveClickedButton: function(event, target) {
			if (!this.options.sendButtonClicked) return;
			if (!target.get('name')) return;
			this.options.extraData[target.get('name')] = target.get('value') || true;
			this.clickedCleaner = function(){
				delete this.options.extraData[target.get('name')];
				this.clickedCleaner = $empty;
			}.bind(this);
		},

		clickedCleaner: $empty,

		send: function(){
			var str = this.element.toQueryString().trim();
			var data = $H(this.options.extraData).toQueryString();
			if (str) str += "&" + data;
			else str = data;
			this.fireEvent('send', [this.element, str.parseQueryString()]);
			this.request.send({data: str, url: this.element.get("action")});
			this.clickedCleaner();
			return this;
		}

	});

	Element.Properties.formRequest = {

		set: function(){
			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
			var update = opt.update || opt.updateId;
			var updater = this.retrieve('form.request');
			if (update) {
				if (updater) updater.update = document.id(update);
				this.store('form.request:update', update);
			}
			if (opt.options) {
				if (updater) updater.setOptions(opt.options);
				this.store('form.request:options', opt.options);
			}
			return this;
		},

		get: function(){
			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
			var update = opt.update || opt.updateId;
			if (opt.options || update || !this.retrieve('form.request')){
				if (opt.options || !this.retrieve('form.request:options')) this.set('form.request', opt.options);
				if (update) this.set('form.request', update);
				this.store('form.request', new Form.Request(this, this.retrieve('form.request:update'), this.retrieve('form.request:options')));
			}
			return this.retrieve('form.request');
		}

	};

	Element.implement({

		formUpdate: function(update, options){
			this.get('formRequest', update, options).send();
			return this;
		}

	});

})();

/*
---

script: Form.Request.Append.js

name: Form.Request.Append

description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - /Form.Request
  - /Fx.Reveal
  - /Elements.from

provides: [Form.Request.Append]

...
*/

Form.Request.Append = new Class({

	Extends: Form.Request,

	options: {
		//onBeforeEffect: $empty,
		useReveal: true,
		revealOptions: {},
		inject: 'bottom'
	},

	makeRequest: function(){
		this.request = new Request.HTML($merge({
				url: this.element.get('action'),
				method: this.element.get('method') || 'post',
				spinnerTarget: this.element
			}, this.options.requestOptions, {
				evalScripts: false
			})
		).addEvents({
			success: function(tree, elements, html, javascript){
				var container;
				var kids = Elements.from(html);
				if (kids.length == 1) {
					container = kids[0];
				} else {
					 container = new Element('div', {
						styles: {
							display: 'none'
						}
					}).adopt(kids);
				}
				container.inject(this.update, this.options.inject);
				if (this.options.requestOptions.evalScripts) $exec(javascript);
				this.fireEvent('beforeEffect', container);
				var finish = function(){
					this.fireEvent('success', [container, this.update, tree, elements, html, javascript]);
				}.bind(this);
				if (this.options.useReveal) {
					container.get('reveal', this.options.revealOptions).chain(finish);
					container.reveal();
				} else {
					finish();
				}
			}.bind(this),
			failure: function(xhr){
				this.fireEvent('failure', xhr);
			}.bind(this)
		});
	}

});

/*
---

script: Form.Validator.js

name: Form.Validator

description: A css-class based form validation system.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Options
  - Core/Events
  - Core/Selectors
  - Core/Element.Event
  - Core/Element.Style
  - Core/JSON
  - /Lang
  - /Class.Binds
  - /Date
  - /Element.Forms
  - /Form.Validator.English
  - /Element.Shortcuts

provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]

...
*/
if (!window.Form) window.Form = {};

var InputValidator = new Class({

	Implements: [Options],

	options: {
		errorMsg: 'Validation failed.',
		test: function(field){return true;}
	},

	initialize: function(className, options){
		this.setOptions(options);
		this.className = className;
	},

	test: function(field, props){
		if (document.id(field)) return this.options.test(document.id(field), props||this.getProps(field));
		else return false;
	},

	getError: function(field, props){
		var err = this.options.errorMsg;
		if ($type(err) == 'function') err = err(document.id(field), props||this.getProps(field));
		return err;
	},

	getProps: function(field){
		if (!document.id(field)) return {};
		return field.get('validatorProps');
	}

});

Element.Properties.validatorProps = {

	set: function(props){
		return this.eliminate('validatorProps').store('validatorProps', props);
	},

	get: function(props){
		if (props) this.set(props);
		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
		if (this.getProperty('validatorProps')){
			try {
				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
			}catch(e){
				return {};
			}
		} else {
			var vals = this.get('class').split(' ').filter(function(cls){
				return cls.test(':');
			});
			if (!vals.length){
				this.store('validatorProps', {});
			} else {
				props = {};
				vals.each(function(cls){
					var split = cls.split(':');
					if (split[1]) {
						try {
							props[split[0]] = JSON.decode(split[1]);
						} catch(e) {}
					}
				});
				this.store('validatorProps', props);
			}
		}
		return this.retrieve('validatorProps');
	}

};

Form.Validator = new Class({

	Implements:[Options, Events],

	Binds: ['onSubmit'],

	options: {/*
		onFormValidate: $empty(isValid, form, event),
		onElementValidate: $empty(isValid, field, className, warn),
		onElementPass: $empty(field),
		onElementFail: $empty(field, validatorsFailed) */
		fieldSelectors: 'input, select, textarea',
		ignoreHidden: true,
		ignoreDisabled: true,
		useTitles: false,
		evaluateOnSubmit: true,
		evaluateFieldsOnBlur: true,
		evaluateFieldsOnChange: true,
		serial: true,
		stopOnFailure: true,
		warningPrefix: function(){
			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
		},
		errorPrefix: function(){
			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
		}
	},

	initialize: function(form, options){
		this.setOptions(options);
		this.element = document.id(form);
		this.element.store('validator', this);
		this.warningPrefix = $lambda(this.options.warningPrefix)();
		this.errorPrefix = $lambda(this.options.errorPrefix)();
		if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
	},

	toElement: function(){
		return this.element;
	},

	getFields: function(){
		return (this.fields = this.element.getElements(this.options.fieldSelectors));
	},

	watchFields: function(fields){
		fields.each(function(el){
			if (this.options.evaluateFieldsOnBlur)
				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
			if (this.options.evaluateFieldsOnChange)
				el.addEvent('change', this.validationMonitor.pass([el, true], this));
		}, this);
	},

	validationMonitor: function(){
		$clear(this.timer);
		this.timer = this.validateField.delay(50, this, arguments);
	},

	onSubmit: function(event){
		if (!this.validate(event) && event) event.preventDefault();
		else this.reset();
	},

	reset: function(){
		this.getFields().each(this.resetField, this);
		return this;
	},

	validate: function(event){
		var result = this.getFields().map(function(field){
			return this.validateField(field, true);
		}, this).every(function(v){ return v;});
		this.fireEvent('formValidate', [result, this.element, event]);
		if (this.options.stopOnFailure && !result && event) event.preventDefault();
		return result;
	},

	validateField: function(field, force){
		if (this.paused) return true;
		field = document.id(field);
		var passed = !field.hasClass('validation-failed');
		var failed, warned;
		if (this.options.serial && !force){
			failed = this.element.getElement('.validation-failed');
			warned = this.element.getElement('.warning');
		}
		if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
			var validators = field.className.split(' ').some(function(cn){
				return this.getValidator(cn);
			}, this);
			var validatorsFailed = [];
			field.className.split(' ').each(function(className){
				if (className && !this.test(className, field)) validatorsFailed.include(className);
			}, this);
			passed = validatorsFailed.length === 0;
			if (validators && !field.hasClass('warnOnly')){
				if (passed){
					field.addClass('validation-passed').removeClass('validation-failed');
					this.fireEvent('elementPass', field);
				} else {
					field.addClass('validation-failed').removeClass('validation-passed');
					this.fireEvent('elementFail', [field, validatorsFailed]);
				}
			}
			if (!warned){
				var warnings = field.className.split(' ').some(function(cn){
					if (cn.test('^warn-') || field.hasClass('warnOnly'))
						return this.getValidator(cn.replace(/^warn-/,''));
					else return null;
				}, this);
				field.removeClass('warning');
				var warnResult = field.className.split(' ').map(function(cn){
					if (cn.test('^warn-') || field.hasClass('warnOnly'))
						return this.test(cn.replace(/^warn-/,''), field, true);
					else return null;
				}, this);
			}
		}
		return passed;
	},

	test: function(className, field, warn){
		field = document.id(field);
		if((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
		var validator = this.getValidator(className);
		warn = $pick(warn, false);
		if (field.hasClass('warnOnly')) warn = true;
		var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
		if (warn) return true;
		return isValid;
	},

	resetField: function(field){
		field = document.id(field);
		if (field){
			field.className.split(' ').each(function(className){
				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
				field.removeClass('validation-failed');
				field.removeClass('warning');
				field.removeClass('validation-passed');
			}, this);
		}
		return this;
	},

	stop: function(){
		this.paused = true;
		return this;
	},

	start: function(){
		this.paused = false;
		return this;
	},

	ignoreField: function(field, warn){
		field = document.id(field);
		if (field){
			this.enforceField(field);
			if (warn) field.addClass('warnOnly');
			else field.addClass('ignoreValidation');
		}
		return this;
	},

	enforceField: function(field){
		field = document.id(field);
		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
		return this;
	}

});

Form.Validator.getMsg = function(key){
	return MooTools.lang.get('Form.Validator', key);
};

Form.Validator.adders = {

	validators:{},

	add : function(className, options){
		this.validators[className] = new InputValidator(className, options);
		//if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
		//extend these validators into it
		//this allows validators to be global and/or per instance
		if (!this.initialize){
			this.implement({
				validators: this.validators
			});
		}
	},

	addAllThese : function(validators){
		$A(validators).each(function(validator){
			this.add(validator[0], validator[1]);
		}, this);
	},

	getValidator: function(className){
		return this.validators[className.split(':')[0]];
	}

};

$extend(Form.Validator, Form.Validator.adders);

Form.Validator.implement(Form.Validator.adders);

Form.Validator.add('IsEmpty', {

	errorMsg: false,
	test: function(element){
		if (element.type == 'select-one' || element.type == 'select')
			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
		else
			return ((element.get('value') == null) || (element.get('value').length == 0));
	}

});

Form.Validator.addAllThese([

	['required', {
		errorMsg: function(){
			return Form.Validator.getMsg('required');
		},
		test: function(element){
			return !Form.Validator.getValidator('IsEmpty').test(element);
		}
	}],

	['minLength', {
		errorMsg: function(element, props){
			if ($type(props.minLength))
				return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
			else return '';
		},
		test: function(element, props){
			if ($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
			else return true;
		}
	}],

	['maxLength', {
		errorMsg: function(element, props){
			//props is {maxLength:10}
			if ($type(props.maxLength))
				return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
			else return '';
		},
		test: function(element, props){
			//if the value is <= than the maxLength value, element passes test
			return (element.get('value').length <= $pick(props.maxLength, 10000));
		}
	}],

	['validate-integer', {
		errorMsg: Form.Validator.getMsg.pass('integer'),
		test: function(element){
			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
		}
	}],

	['validate-numeric', {
		errorMsg: Form.Validator.getMsg.pass('numeric'),
		test: function(element){
			return Form.Validator.getValidator('IsEmpty').test(element) ||
				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
		}
	}],

	['validate-digits', {
		errorMsg: Form.Validator.getMsg.pass('digits'),
		test: function(element){
			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
		}
	}],

	['validate-alpha', {
		errorMsg: Form.Validator.getMsg.pass('alpha'),
		test: function(element){
			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
		}
	}],

	['validate-alphanum', {
		errorMsg: Form.Validator.getMsg.pass('alphanum'),
		test: function(element){
			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
		}
	}],

	['validate-date', {
		errorMsg: function(element, props){
			if (Date.parse){
				var format = props.dateFormat || '%x';
				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
			} else {
				return Form.Validator.getMsg('dateInFormatMDY');
			}
		},
		test: function(element, props){
			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
			var d;
			if (Date.parse){
				var format = props.dateFormat || '%x';
				d = Date.parse(element.get('value'));
				var formatted = d.format(format);
				if (formatted != 'invalid date') element.set('value', formatted);
				return !isNaN(d);
			} else {
				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
				if (!regex.test(element.get('value'))) return false;
				d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
				return (parseInt(RegExp.$1, 10) == (1 + d.getMonth())) &&
					(parseInt(RegExp.$2, 10) == d.getDate()) &&
					(parseInt(RegExp.$3, 10) == d.getFullYear());
			}
		}
	}],

	['validate-email', {
		errorMsg: Form.Validator.getMsg.pass('email'),
		test: function(element){
			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
		}
	}],

	['validate-url', {
		errorMsg: Form.Validator.getMsg.pass('url'),
		test: function(element){
			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
		}
	}],

	['validate-currency-dollar', {
		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
		test: function(element){
			// [$]1[##][,###]+[.##]
			// [$]1###+[.##]
			// [$]0.##
			// [$].##
			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
		}
	}],

	['validate-one-required', {
		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
		test: function(element, props){
			var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
			return p.getElements('input').some(function(el){
				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
				return el.get('value');
			});
		}
	}]

]);

Element.Properties.validator = {

	set: function(options){
		var validator = this.retrieve('validator');
		if (validator) validator.setOptions(options);
		return this.store('validator:options', options);
	},

	get: function(options){
		if (options || !this.retrieve('validator')){
			if (options || !this.retrieve('validator:options')) this.set('validator', options);
			this.store('validator', new Form.Validator(this, this.retrieve('validator:options')));
		}
		return this.retrieve('validator');
	}

};

Element.implement({

	validate: function(options){
		if (options) this.set('validator', options);
		return this.get('validator', options).validate();
	}

});
//legacy
var FormValidator = Form.Validator;


/*
---

script: Form.Validator.Inline.js

name: Form.Validator.Inline

description: Extends Form.Validator to add inline messages.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - /Form.Validator

provides: [Form.Validator.Inline]

...
*/

Form.Validator.Inline = new Class({

	Extends: Form.Validator,

	options: {
		showError: function(errorElement){
			if (errorElement.reveal) errorElement.reveal();
			else errorElement.setStyle('display', 'block');
		},
		hideError: function(errorElement){
			if (errorElement.dissolve) errorElement.dissolve();
			else errorElement.setStyle('display', 'none');
		},
		scrollToErrorsOnSubmit: true,
		scrollToErrorsOnBlur: false,
		scrollToErrorsOnChange: false,
		scrollFxOptions: {
			transition: 'quad:out',
			offset: {
				y: -20
			}
		}
	},

	initialize: function(form, options){
		this.parent(form, options);
		this.addEvent('onElementValidate', function(isValid, field, className, warn){
			var validator = this.getValidator(className);
			if (!isValid && validator.getError(field)){
				if (warn) field.addClass('warning');
				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
				this.insertAdvice(advice, field);
				this.showAdvice(className, field);
			} else {
				this.hideAdvice(className, field);
			}
		});
	},

	makeAdvice: function(className, field, error, warn){
		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
			errorMsg += (this.options.useTitles) ? field.title || error:error;
		var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
		var advice = this.getAdvice(className, field);
		if(advice) {
			advice = advice.set('html', errorMsg);
		} else {
			advice = new Element('div', {
				html: errorMsg,
				styles: { display: 'none' },
				id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
			}).addClass(cssClass);
		}
		field.store('advice-' + className, advice);
		return advice;
	},

	getFieldId : function(field){
		return field.id ? field.id : field.id = 'input_' + field.name;
	},

	showAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if (advice && !field.retrieve(this.getPropName(className))
				&& (advice.getStyle('display') == 'none'
				|| advice.getStyle('visiblity') == 'hidden'
				|| advice.getStyle('opacity') == 0)){
			field.store(this.getPropName(className), true);
			this.options.showError(advice);
			this.fireEvent('showAdvice', [field, advice, className]);
		}
	},

	hideAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if (advice && field.retrieve(this.getPropName(className))){
			field.store(this.getPropName(className), false);
			this.options.hideError(advice);
			this.fireEvent('hideAdvice', [field, advice, className]);
		}
	},

	getPropName: function(className){
		return 'advice' + className;
	},

	resetField: function(field){
		field = document.id(field);
		if (!field) return this;
		this.parent(field);
		field.className.split(' ').each(function(className){
			this.hideAdvice(className, field);
		}, this);
		return this;
	},

	getAllAdviceMessages: function(field, force){
		var advice = [];
		if (field.hasClass('ignoreValidation') && !force) return advice;
		var validators = field.className.split(' ').some(function(cn){
			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
			if (warner) cn = cn.replace(/^warn-/, '');
			var validator = this.getValidator(cn);
			if (!validator) return;
			advice.push({
				message: validator.getError(field),
				warnOnly: warner,
				passed: validator.test(),
				validator: validator
			});
		}, this);
		return advice;
	},

	getAdvice: function(className, field){
		return field.retrieve('advice-' + className);
	},

	insertAdvice: function(advice, field){
		//Check for error position prop
		var props = field.get('validatorProps');
		//Build advice
		if (!props.msgPos || !document.id(props.msgPos)){
			if(field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
			else advice.inject(document.id(field), 'after');
		} else {
			document.id(props.msgPos).grab(advice);
		}
	},

	validateField: function(field, force, scroll){
		var result = this.parent(field, force);
		if (((this.options.scrollToErrorsOnSubmit && scroll === undefined) || scroll) && !result){
			var failed = document.id(this).getElement('.validation-failed');
			var par = document.id(this).getParent();
			while (par != document.body && par.getScrollSize().y == par.getSize().y){
				par = par.getParent();
			}
			var fx = par.retrieve('fvScroller');
			if (!fx && window.Fx && Fx.Scroll){
				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
				par.store('fvScroller', fx);
			}
			if (failed){
				if (fx) fx.toElement(failed);
				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
			}
		}
		return result;
	},

	watchFields: function(fields){
		fields.each(function(el){
			if (this.options.evaluateFieldsOnBlur){
				el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
			}
			if (this.options.evaluateFieldsOnChange){
				el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
			}
		}, this);
	}

});


/*
---

script: Form.Validator.Extras.js

name: Form.Validator.Extras

description: Additional validators for the Form.Validator class.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - /Form.Validator

provides: [Form.Validator.Extras]

...
*/
Form.Validator.addAllThese([

	['validate-enforce-oncheck', {
		test: function(element, props){
			var fv = element.getParent('form').retrieve('validator');
			if (!fv) return true;
			(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
				if (element.checked) {
					fv.enforceField(item);
				} else {
					fv.ignoreField(item);
					fv.resetField(item);
				}
			});
			return true;
		}
	}],

	['validate-ignore-oncheck', {
		test: function(element, props){
			var fv = element.getParent('form').retrieve('validator');
			if (!fv) return true;
			(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
				if (element.checked) {
					fv.ignoreField(item);
					fv.resetField(item);
				} else {
					fv.enforceField(item);
				}
			});
			return true;
		}
	}],

	['validate-nospace', {
		errorMsg: function(){
			return Form.Validator.getMsg('noSpace');
		},
		test: function(element, props){
			return !element.get('value').test(/\s/);
		}
	}],

	['validate-toggle-oncheck', {
		test: function(element, props){
			var fv = element.getParent('form').retrieve('validator');
			if (!fv) return true;
			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
			if (!element.checked){
				eleArr.each(function(item){
					fv.ignoreField(item);
					fv.resetField(item);
				});
			} else {
				eleArr.each(function(item){
					fv.enforceField(item);
				});
			}
			return true;
		}
	}],

	['validate-reqchk-bynode', {
		errorMsg: function(){
			return Form.Validator.getMsg('reqChkByNode');
		},
		test: function(element, props){
			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
				return item.checked;
			});
		}
	}],

	['validate-required-check', {
		errorMsg: function(element, props){
			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
		},
		test: function(element, props){
			return !!element.checked;
		}
	}],

	['validate-reqchk-byname', {
		errorMsg: function(element, props){
			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
		},
		test: function(element, props){
			var grpName = props.groupName || element.get('name');
			var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
				return item.checked;
			});
			var fv = element.getParent('form').retrieve('validator');
			if (oneCheckedItem && fv) fv.resetField(element);
			return oneCheckedItem;
		}
	}],

	['validate-match', {
		errorMsg: function(element, props){
			return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
		},
		test: function(element, props){
			var eleVal = element.get('value');
			var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
			return eleVal && matchVal ? eleVal == matchVal : true;
		}
	}],

	['validate-after-date', {
		errorMsg: function(element, props){
			return Form.Validator.getMsg('afterDate').substitute({
				label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
			});
		},
		test: function(element, props){
			var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
			var end = Date.parse(element.get('value'));
			return end && start ? end >= start : true;
		}
	}],

	['validate-before-date', {
		errorMsg: function(element, props){
			return Form.Validator.getMsg('beforeDate').substitute({
				label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
			});
		},
		test: function(element, props){
			var start = Date.parse(element.get('value'));
			var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
			return end && start ? end >= start : true;
		}
	}],

	['validate-custom-required', {
		errorMsg: function(){
			return Form.Validator.getMsg('required');
		},
		test: function(element, props){
			return element.get('value') != props.emptyValue;
		}
	}],

	['validate-same-month', {
		errorMsg: function(element, props){
			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
			var eleVal = element.get('value');
			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
		},
		test: function(element, props){
			var d1 = Date.parse(element.get('value'));
			var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
			return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
		}
	}],


	['validate-cc-num', {
		errorMsg: function(element){
			var ccNum = element.get('value').replace(/[^0-9]/g, '');
			return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
		},
		test: function(element){
			// required is a different test
			if (Form.Validator.getValidator('IsEmpty').test(element)) { return true; }

			// Clean number value
			var ccNum = element.get('value');
			ccNum = ccNum.replace(/[^0-9]/g, '');

			var valid_type = false;

			if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
			else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
			else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
			else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';

			if (valid_type) {
				var sum = 0;
				var cur = 0;

				for(var i=ccNum.length-1; i>=0; --i) {
					cur = ccNum.charAt(i).toInt();
					if (cur == 0) { continue; }

					if ((ccNum.length-i) % 2 == 0) { cur += cur; }
					if (cur > 9) { cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); }

					sum += cur;
				}
				if ((sum % 10) == 0) { return true; }
			}

			var chunks = '';
			while (ccNum != '') {
				chunks += ' ' + ccNum.substr(0,4);
				ccNum = ccNum.substr(4);
			}

			element.getParent('form').retrieve('validator').ignoreField(element);
			element.set('value', chunks.clean());
			element.getParent('form').retrieve('validator').enforceField(element);
			return false;
		}
	}]


]);

/*
---

script: OverText.js

name: OverText

description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Options
  - Core/Events
  - Core/Element.Event
  - /Class.Binds
  - /Class.Occlude
  - /Element.Position
  - /Element.Shortcuts

provides: [OverText]

...
*/

var OverText = new Class({

	Implements: [Options, Events, Class.Occlude],

	Binds: ['reposition', 'assert', 'focus', 'hide'],

	options: {/*
		textOverride: null,
		onFocus: $empty()
		onTextHide: $empty(textEl, inputEl),
		onTextShow: $empty(textEl, inputEl), */
		element: 'label',
		positionOptions: {
			position: 'upperLeft',
			edge: 'upperLeft',
			offset: {
				x: 4,
				y: 2
			}
		},
		poll: false,
		pollInterval: 250,
		wrap: false
	},

	property: 'OverText',

	initialize: function(element, options){
		this.element = document.id(element);
		if (this.occlude()) return this.occluded;
		this.setOptions(options);
		this.attach(this.element);
		OverText.instances.push(this);
		if (this.options.poll) this.poll();
		return this;
	},

	toElement: function(){
		return this.element;
	},

	attach: function(){
		var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
		if (!val) return;
		this.text = new Element(this.options.element, {
			'class': 'overTxtLabel',
			styles: {
				lineHeight: 'normal',
				position: 'absolute',
				cursor: 'text'
			},
			html: val,
			events: {
				click: this.hide.pass(this.options.element == 'label', this)
			}
		}).inject(this.element, 'after');
		if (this.options.element == 'label') {
			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
			this.text.set('for', this.element.get('id'));
		}

		if (this.options.wrap) {
			this.textHolder = new Element('div', {
				styles: {
					lineHeight: 'normal',
					position: 'relative'
				},
				'class':'overTxtWrapper'
			}).adopt(this.text).inject(this.element, 'before');
		}

		return this.enable();
	},

	destroy: function(){
		this.element.eliminate('OverTextDiv').eliminate('OverText');
		this.disable();
		if (this.text) this.text.destroy();
		if (this.textHolder) this.textHolder.destroy();
		return this;
	},

	disable: function(){
		this.element.removeEvents({
			focus: this.focus,
			blur: this.assert,
			change: this.assert
		});
		window.removeEvent('resize', this.reposition);
		this.hide(true, true);
		return this;
	},

	enable: function(){
		this.element.addEvents({
			focus: this.focus,
			blur: this.assert,
			change: this.assert
		});
		window.addEvent('resize', this.reposition);
		this.assert(true);
		this.reposition();
		return this;
	},

	wrap: function(){
		if (this.options.element == 'label') {
			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
			this.text.set('for', this.element.get('id'));
		}
	},

	startPolling: function(){
		this.pollingPaused = false;
		return this.poll();
	},

	poll: function(stop){
		//start immediately
		//pause on focus
		//resumeon blur
		if (this.poller && !stop) return this;
		var test = function(){
			if (!this.pollingPaused) this.assert(true);
		}.bind(this);
		if (stop) $clear(this.poller);
		else this.poller = test.periodical(this.options.pollInterval, this);
		return this;
	},

	stopPolling: function(){
		this.pollingPaused = true;
		return this.poll(true);
	},

	focus: function(){
		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
		this.hide();
	},

	hide: function(suppressFocus, force){
		if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
			this.text.hide();
			this.fireEvent('textHide', [this.text, this.element]);
			this.pollingPaused = true;
			if (!suppressFocus){
				try {
					this.element.fireEvent('focus');
					this.element.focus();
				} catch(e){} //IE barfs if you call focus on hidden elements
			}
		}
		return this;
	},

	show: function(){
		if (this.text && !this.text.isDisplayed()){
			this.text.show();
			this.reposition();
			this.fireEvent('textShow', [this.text, this.element]);
			this.pollingPaused = false;
		}
		return this;
	},

	assert: function(suppressFocus){
		this[this.test() ? 'show' : 'hide'](suppressFocus);
	},

	test: function(){
		var v = this.element.get('value');
		return !v;
	},

	reposition: function(){
		this.assert(true);
		if (!this.element.isVisible()) return this.stopPolling().hide();
		if (this.text && this.test()) this.text.position($merge(this.options.positionOptions, {relativeTo: this.element}));
		return this;
	}

});

OverText.instances = [];

$extend(OverText, {

	each: function(fn) {
		return OverText.instances.map(function(ot, i){
			if (ot.element && ot.text) return fn.apply(OverText, [ot, i]);
			return null; //the input or the text was destroyed
		});
	},

	update: function(){

		return OverText.each(function(ot){
			return ot.reposition();
		});

	},

	hideAll: function(){

		return OverText.each(function(ot){
			return ot.hide(true, true);
		});

	},

	showAll: function(){
		return OverText.each(function(ot) {
			return ot.show();
		});
	}

});

if (window.Fx && Fx.Reveal) {
	Fx.Reveal.implement({
		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
	});
}

/*
---

script: Fx.Elements.js

name: Fx.Elements

description: Effect to change any number of CSS properties of any number of Elements.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Fx.CSS
  - /MooTools.More

provides: [Fx.Elements]

...
*/

Fx.Elements = new Class({

	Extends: Fx.CSS,

	initialize: function(elements, options){
		this.elements = this.subject = $$(elements);
		this.parent(options);
	},

	compute: function(from, to, delta){
		var now = {};

		for (var i in from){
			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
			for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
		}

		return now;
	},

	set: function(now){
		for (var i in now){
			if (!this.elements[i]) continue;

			var iNow = now[i];
			for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
		}

		return this;
	},

	start: function(obj){
		if (!this.check(obj)) return this;
		var from = {}, to = {};

		for (var i in obj){
			if (!this.elements[i]) continue;

			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};

			for (var p in iProps){
				var parsed = this.prepare(this.elements[i], p, iProps[p]);
				iFrom[p] = parsed.from;
				iTo[p] = parsed.to;
			}
		}

		return this.parent(from, to);
	}

});


/*
---

script: Fx.Accordion.js

name: Fx.Accordion

description: An Fx.Elements extension which allows you to easily create accordion type controls.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Element.Event
  - /Fx.Elements

provides: [Fx.Accordion]

...
*/

Fx.Accordion = new Class({

	Extends: Fx.Elements,

	options: {/*
		onActive: $empty(toggler, section),
		onBackground: $empty(toggler, section),
		*/
		fixedHeight: false,
		fixedWidth: false,
		display: 0,
		show: false,
		height: true,
		width: false,
		opacity: true,
		alwaysHide: false,
		trigger: 'click',
		initialDisplayFx: true,
		returnHeightToAuto: true
	},

	initialize: function(){
		var params = Array.link(arguments, {
			'container': Element.type, //deprecated
			'options': Object.type,
			'togglers': $defined,
			'elements': $defined
		});
		this.parent(params.elements, params.options);
		this.togglers = $$(params.togglers);
		this.previous = -1;
		this.internalChain = new Chain();
		if (this.options.alwaysHide) this.options.wait = true;
		if ($chk(this.options.show)){
			this.options.display = false;
			this.previous = this.options.show;
		}
		if (this.options.start){
			this.options.display = false;
			this.options.show = false;
		}
		this.effects = {};
		if (this.options.opacity) this.effects.opacity = 'fullOpacity';
		if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
		if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
		for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
		this.elements.each(function(el, i){
			if (this.options.show === i){
				this.fireEvent('active', [this.togglers[i], el]);
			} else {
				for (var fx in this.effects) el.setStyle(fx, 0);
			}
		}, this);
		if ($chk(this.options.display) || this.options.initialDisplayFx === false) this.display(this.options.display, this.options.initialDisplayFx);
		if (this.options.fixedHeight !== false) this.options.returnHeightToAuto = false;
		this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
	},

	addSection: function(toggler, element){
		toggler = document.id(toggler);
		element = document.id(element);
		var test = this.togglers.contains(toggler);
		this.togglers.include(toggler);
		this.elements.include(element);
		var idx = this.togglers.indexOf(toggler);
		var displayer = this.display.bind(this, idx);
		toggler.store('accordion:display', displayer);
		toggler.addEvent(this.options.trigger, displayer);
		if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
		if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
		element.fullOpacity = 1;
		if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
		if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
		element.setStyle('overflow', 'hidden');
		if (!test){
			for (var fx in this.effects) element.setStyle(fx, 0);
		}
		return this;
	},

	removeSection: function(toggler, displayIndex) {
		var idx = this.togglers.indexOf(toggler);
		var element = this.elements[idx];
		var remover = function(){
			this.togglers.erase(toggler);
			this.elements.erase(element);
			this.detach(toggler);
		}.bind(this);
		if (this.now == idx || displayIndex != undefined) this.display($pick(displayIndex, idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
		else remover();
		return this;
	},

	detach: function(toggler){
		var remove = function(toggler) {
			toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
		}.bind(this);
		if (!toggler) this.togglers.each(remove);
		else remove(toggler);
		return this;
	},

	display: function(index, useFx){
		if (!this.check(index, useFx)) return this;
		useFx = $pick(useFx, true);
		index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
		if (index == this.previous && !this.options.alwaysHide) return this;
		if (this.options.returnHeightToAuto){
			var prev = this.elements[this.previous];
			if (prev && !this.selfHidden){
				for (var fx in this.effects){
					prev.setStyle(fx, prev[this.effects[fx]]);
				}
			}
		}
		if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
		this.previous = index;
		var obj = {};
		this.elements.each(function(el, i){
			obj[i] = {};
			var hide;
			if (i != index){
				hide = true;
			} else if (this.options.alwaysHide && ((el.offsetHeight > 0 && this.options.height) || el.offsetWidth > 0 && this.options.width)){
				hide = true;
				this.selfHidden = true;
			}
			this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
			for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
		}, this);
		this.internalChain.clearChain();
		this.internalChain.chain(function(){
			if (this.options.returnHeightToAuto && !this.selfHidden){
				var el = this.elements[index];
				if (el) el.setStyle('height', 'auto');
			};
		}.bind(this));
		return useFx ? this.start(obj) : this.set(obj);
	}

});

/*
	Compatibility with 1.2.0
*/
var Accordion = new Class({

	Extends: Fx.Accordion,

	initialize: function(){
		this.parent.apply(this, arguments);
		var params = Array.link(arguments, {'container': Element.type});
		this.container = params.container;
	},

	addSection: function(toggler, element, pos){
		toggler = document.id(toggler);
		element = document.id(element);
		var test = this.togglers.contains(toggler);
		var len = this.togglers.length;
		if (len && (!test || pos)){
			pos = $pick(pos, len - 1);
			toggler.inject(this.togglers[pos], 'before');
			element.inject(toggler, 'after');
		} else if (this.container && !test){
			toggler.inject(this.container);
			element.inject(this.container);
		}
		return this.parent.apply(this, arguments);
	}

});

/*
---

script: Fx.Move.js

name: Fx.Move

description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Fx.Morph
  - /Element.Position

provides: [Fx.Move]

...
*/

Fx.Move = new Class({

	Extends: Fx.Morph,

	options: {
		relativeTo: document.body,
		position: 'center',
		edge: false,
		offset: {x: 0, y: 0}
	},

	start: function(destination){
		var element = this.element,
			topLeft = element.getStyles('top', 'left');
		if (topLeft.top == 'auto' || topLeft.left == 'auto'){
			element.setPosition(element.getPosition(element.getOffsetParent()));
		}
		return this.parent(element.position($merge(this.options, destination, {returnPos: true})));
	}

});

Element.Properties.move = {

	set: function(options){
		var morph = this.retrieve('move');
		if (morph) morph.cancel();
		return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
	},

	get: function(options){
		if (options || !this.retrieve('move')){
			if (options || !this.retrieve('move:options')) this.set('move', options);
			this.store('move', new Fx.Move(this, this.retrieve('move:options')));
		}
		return this.retrieve('move');
	}

};

Element.implement({

	move: function(options){
		this.get('move').start(options);
		return this;
	}

});


/*
---

script: Fx.Reveal.js

name: Fx.Reveal

description: Defines Fx.Reveal, a class that shows and hides elements with a transition.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Fx.Morph
  - /Element.Shortcuts
  - /Element.Measure

provides: [Fx.Reveal]

...
*/

Fx.Reveal = new Class({

	Extends: Fx.Morph,

	options: {/*
		onShow: $empty(thisElement),
		onHide: $empty(thisElement),
		onComplete: $empty(thisElement),
		heightOverride: null,
		widthOverride: null, */
		link: 'cancel',
		styles: ['padding', 'border', 'margin'],
		transitionOpacity: !Browser.Engine.trident4,
		mode: 'vertical',
		display: function(){
			return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
		},
		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false,
		opacity: 1
	},

	dissolve: function(){
		try {
			if (!this.hiding && !this.showing){
				if (this.element.getStyle('display') != 'none'){
					this.hiding = true;
					this.showing = false;
					this.hidden = true;
					this.cssText = this.element.style.cssText;
					var startStyles = this.element.getComputedSize({
						styles: this.options.styles,
						mode: this.options.mode
					});
					this.element.setStyle('display', $lambda(this.options.display).apply(this));
					if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
					var zero = {};
					$each(startStyles, function(style, name){
						zero[name] = [style, 0];
					}, this);
					this.element.setStyle('overflow', 'hidden');
					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
					this.$chain.unshift(function(){
						if (this.hidden){
							this.hiding = false;
							$each(startStyles, function(style, name){
								startStyles[name] = style;
							}, this);
							this.element.style.cssText = this.cssText;
							this.element.setStyle('display', 'none');
							if (hideThese) hideThese.setStyle('visibility', 'visible');
						}
						this.fireEvent('hide', this.element);
						this.callChain();
					}.bind(this));
					if (hideThese) hideThese.setStyle('visibility', 'hidden');
					this.start(zero);
				} else {
					this.callChain.delay(10, this);
					this.fireEvent('complete', this.element);
					this.fireEvent('hide', this.element);
				}
			} else if (this.options.link == 'chain'){
				this.chain(this.dissolve.bind(this));
			} else if (this.options.link == 'cancel' && !this.hiding){
				this.cancel();
				this.dissolve();
			}
		} catch(e){
			this.hiding = false;
			this.element.setStyle('display', 'none');
			this.callChain.delay(10, this);
			this.fireEvent('complete', this.element);
			this.fireEvent('hide', this.element);
		}
		return this;
	},

	reveal: function(){
		try {
			if (!this.showing && !this.hiding){
				if (this.element.getStyle('display') == 'none'){
					this.showing = true;
					this.hiding = this.hidden =  false;
					var startStyles;
					this.cssText = this.element.style.cssText;
					//toggle display, but hide it
					this.element.measure(function(){
						//create the styles for the opened/visible state
						startStyles = this.element.getComputedSize({
							styles: this.options.styles,
							mode: this.options.mode
						});
					}.bind(this));
					$each(startStyles, function(style, name){
						startStyles[name] = style;
					});
					//if we're overridding height/width
					if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
					if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
					if (this.options.transitionOpacity) {
						this.element.setStyle('opacity', 0);
						startStyles.opacity = this.options.opacity;
					}
					//create the zero state for the beginning of the transition
					var zero = {
						height: 0,
						display: $lambda(this.options.display).apply(this)
					};
					$each(startStyles, function(style, name){ zero[name] = 0; });
					//set to zero
					this.element.setStyles($merge(zero, {overflow: 'hidden'}));
					//hide inputs
					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
					if (hideThese) hideThese.setStyle('visibility', 'hidden');
					//start the effect
					this.start(startStyles);
					this.$chain.unshift(function(){
						this.element.style.cssText = this.cssText;
						this.element.setStyle('display', $lambda(this.options.display).apply(this));
						if (!this.hidden) this.showing = false;
						if (hideThese) hideThese.setStyle('visibility', 'visible');
						this.callChain();
						this.fireEvent('show', this.element);
					}.bind(this));
				} else {
					this.callChain();
					this.fireEvent('complete', this.element);
					this.fireEvent('show', this.element);
				}
			} else if (this.options.link == 'chain'){
				this.chain(this.reveal.bind(this));
			} else if (this.options.link == 'cancel' && !this.showing){
				this.cancel();
				this.reveal();
			}
		} catch(e){
			this.element.setStyles({
				display: $lambda(this.options.display).apply(this),
				visiblity: 'visible',
				opacity: this.options.opacity
			});
			this.showing = false;
			this.callChain.delay(10, this);
			this.fireEvent('complete', this.element);
			this.fireEvent('show', this.element);
		}
		return this;
	},

	toggle: function(){
		if (this.element.getStyle('display') == 'none'){
			this.reveal();
		} else {
			this.dissolve();
		}
		return this;
	},

	cancel: function(){
		this.parent.apply(this, arguments);
		this.element.style.cssText = this.cssText;
		this.hiding = false;
		this.showing = false;
		return this;
	}

});

Element.Properties.reveal = {

	set: function(options){
		var reveal = this.retrieve('reveal');
		if (reveal) reveal.cancel();
		return this.eliminate('reveal').store('reveal:options', options);
	},

	get: function(options){
		if (options || !this.retrieve('reveal')){
			if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
			this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
		}
		return this.retrieve('reveal');
	}

};

Element.Properties.dissolve = Element.Properties.reveal;

Element.implement({

	reveal: function(options){
		this.get('reveal', options).reveal();
		return this;
	},

	dissolve: function(options){
		this.get('reveal', options).dissolve();
		return this;
	},

	nix: function(){
		var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
		this.get('reveal', params.options).dissolve().chain(function(){
			this[params.destroy ? 'destroy' : 'dispose']();
		}.bind(this));
		return this;
	},

	wink: function(){
		var params = Array.link(arguments, {duration: Number.type, options: Object.type});
		var reveal = this.get('reveal', params.options);
		reveal.reveal().chain(function(){
			(function(){
				reveal.dissolve();
			}).delay(params.duration || 2000);
		});
	}


});

/*
---

script: Fx.Scroll.js

name: Fx.Scroll

description: Effect to smoothly scroll any element, including the window.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Fx
  - Core/Element.Event
  - Core/Element.Dimensions
  - /MooTools.More

provides: [Fx.Scroll]

...
*/

Fx.Scroll = new Class({

	Extends: Fx,

	options: {
		offset: {x: 0, y: 0},
		wheelStops: true
	},

	initialize: function(element, options){
		this.element = this.subject = document.id(element);
		this.parent(options);
		var cancel = this.cancel.bind(this, false);

		if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);

		var stopper = this.element;

		if (this.options.wheelStops){
			this.addEvent('start', function(){
				stopper.addEvent('mousewheel', cancel);
			}, true);
			this.addEvent('complete', function(){
				stopper.removeEvent('mousewheel', cancel);
			}, true);
		}
	},

	set: function(){
		var now = Array.flatten(arguments);
		if (Browser.Engine.gecko) now = [Math.round(now[0]), Math.round(now[1])];
		this.element.scrollTo(now[0] + this.options.offset.x, now[1] + this.options.offset.y);
	},

	compute: function(from, to, delta){
		return [0, 1].map(function(i){
			return Fx.compute(from[i], to[i], delta);
		});
	},

	start: function(x, y){
		if (!this.check(x, y)) return this;
		var scrollSize = this.element.getScrollSize(),
			scroll = this.element.getScroll(),
			values = {x: x, y: y};
		for (var z in values){
			var max = scrollSize[z];
			if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z] : max;
			else values[z] = scroll[z];
			values[z] += this.options.offset[z];
		}
		return this.parent([scroll.x, scroll.y], [values.x, values.y]);
	},

	toTop: function(){
		return this.start(false, 0);
	},

	toLeft: function(){
		return this.start(0, false);
	},

	toRight: function(){
		return this.start('right', false);
	},

	toBottom: function(){
		return this.start(false, 'bottom');
	},

	toElement: function(el){
		var position = document.id(el).getPosition(this.element);
		return this.start(position.x, position.y);
	},

	scrollIntoView: function(el, axes, offset){
		axes = axes ? $splat(axes) : ['x','y'];
		var to = {};
		el = document.id(el);
		var pos = el.getPosition(this.element);
		var size = el.getSize();
		var scroll = this.element.getScroll();
		var containerSize = this.element.getSize();
		var edge = {
			x: pos.x + size.x,
			y: pos.y + size.y
		};
		['x','y'].each(function(axis) {
			if (axes.contains(axis)) {
				if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
				if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
			}
			if (to[axis] == null) to[axis] = scroll[axis];
			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
		}, this);
		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
		return this;
	},

	scrollToCenter: function(el, axes, offset){
		axes = axes ? $splat(axes) : ['x', 'y'];
		el = $(el);
		var to = {},
			pos = el.getPosition(this.element),
			size = el.getSize(),
			scroll = this.element.getScroll(),
			containerSize = this.element.getSize(),
			edge = {
				x: pos.x + size.x,
				y: pos.y + size.y
			};

		['x','y'].each(function(axis){
			if(axes.contains(axis)){
				to[axis] = pos[axis] - (containerSize[axis] - size[axis])/2;
			}
			if(to[axis] == null) to[axis] = scroll[axis];
			if(offset && offset[axis]) to[axis] = to[axis] + offset[axis];
		}, this);
		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
		return this;
	}

});


/*
---

script: Fx.Slide.js

name: Fx.Slide

description: Effect to slide an element in and out of view.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Fx
  - Core/Element.Style
  - /MooTools.More

provides: [Fx.Slide]

...
*/

Fx.Slide = new Class({

	Extends: Fx,

	options: {
		mode: 'vertical',
		wrapper: false,
		hideOverflow: true,
		resetHeight: false
	},

	initialize: function(element, options){
		this.addEvent('complete', function(){
			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
			if (this.open && this.options.resetHeight) this.wrapper.setStyle('height', '');
			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
		}, true);
		this.element = this.subject = document.id(element);
		this.parent(options);
		var wrapper = this.element.retrieve('wrapper');
		var styles = this.element.getStyles('margin', 'position', 'overflow');
		if (this.options.hideOverflow) styles = $extend(styles, {overflow: 'hidden'});
		if (this.options.wrapper) wrapper = document.id(this.options.wrapper).setStyles(styles);
		this.wrapper = wrapper || new Element('div', {
			styles: styles
		}).wraps(this.element);
		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
		this.now = [];
		this.open = true;
	},

	vertical: function(){
		this.margin = 'margin-top';
		this.layout = 'height';
		this.offset = this.element.offsetHeight;
	},

	horizontal: function(){
		this.margin = 'margin-left';
		this.layout = 'width';
		this.offset = this.element.offsetWidth;
	},

	set: function(now){
		this.element.setStyle(this.margin, now[0]);
		this.wrapper.setStyle(this.layout, now[1]);
		return this;
	},

	compute: function(from, to, delta){
		return [0, 1].map(function(i){
			return Fx.compute(from[i], to[i], delta);
		});
	},

	start: function(how, mode){
		if (!this.check(how, mode)) return this;
		this[mode || this.options.mode]();
		var margin = this.element.getStyle(this.margin).toInt();
		var layout = this.wrapper.getStyle(this.layout).toInt();
		var caseIn = [[margin, layout], [0, this.offset]];
		var caseOut = [[margin, layout], [-this.offset, 0]];
		var start;
		switch (how){
			case 'in': start = caseIn; break;
			case 'out': start = caseOut; break;
			case 'toggle': start = (layout == 0) ? caseIn : caseOut;
		}
		return this.parent(start[0], start[1]);
	},

	slideIn: function(mode){
		return this.start('in', mode);
	},

	slideOut: function(mode){
		return this.start('out', mode);
	},

	hide: function(mode){
		this[mode || this.options.mode]();
		this.open = false;
		return this.set([-this.offset, 0]);
	},

	show: function(mode){
		this[mode || this.options.mode]();
		this.open = true;
		return this.set([0, this.offset]);
	},

	toggle: function(mode){
		return this.start('toggle', mode);
	}

});

Element.Properties.slide = {

	set: function(options){
		var slide = this.retrieve('slide');
		if (slide) slide.cancel();
		return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
	},

	get: function(options){
		if (options || !this.retrieve('slide')){
			if (options || !this.retrieve('slide:options')) this.set('slide', options);
			this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
		}
		return this.retrieve('slide');
	}

};

Element.implement({

	slide: function(how, mode){
		how = how || 'toggle';
		var slide = this.get('slide'), toggle;
		switch (how){
			case 'hide': slide.hide(mode); break;
			case 'show': slide.show(mode); break;
			case 'toggle':
				var flag = this.retrieve('slide:flag', slide.open);
				slide[flag ? 'slideOut' : 'slideIn'](mode);
				this.store('slide:flag', !flag);
				toggle = true;
			break;
			default: slide.start(how, mode);
		}
		if (!toggle) this.eliminate('slide:flag');
		return this;
	}

});


/*
---

script: Fx.SmoothScroll.js

name: Fx.SmoothScroll

description: Class for creating a smooth scrolling effect to all internal links on the page.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Selectors
  - /Fx.Scroll

provides: [Fx.SmoothScroll]

...
*/

var SmoothScroll = Fx.SmoothScroll = new Class({

	Extends: Fx.Scroll,

	initialize: function(options, context){
		context = context || document;
		this.doc = context.getDocument();
		var win = context.getWindow();
		this.parent(this.doc, options);
		this.links = $$(this.options.links || this.doc.links);
		var location = win.location.href.match(/^[^#]*/)[0] + '#';
		this.links.each(function(link){
			if (link.href.indexOf(location) != 0) {return;}
			var anchor = link.href.substr(location.length);
			if (anchor) this.useLink(link, anchor);
		}, this);
		if (!Browser.Engine.webkit419) {
			this.addEvent('complete', function(){
				win.location.hash = this.anchor;
			}, true);
		}
	},

	useLink: function(link, anchor){
		var el;
		link.addEvent('click', function(event){
			if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
			if (el) {
				event.preventDefault();
				this.anchor = anchor;
				this.toElement(el).chain(function(){
					this.fireEvent('scrolledTo', [link, el]);
				}.bind(this));
				link.blur();
			}
		}.bind(this));
	}
});

/*
---

script: Fx.Sort.js

name: Fx.Sort

description: Defines Fx.Sort, a class that reorders lists with a transition.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element.Dimensions
  - /Fx.Elements
  - /Element.Measure

provides: [Fx.Sort]

...
*/

Fx.Sort = new Class({

	Extends: Fx.Elements,

	options: {
		mode: 'vertical'
	},

	initialize: function(elements, options){
		this.parent(elements, options);
		this.elements.each(function(el){
			if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
		});
		this.setDefaultOrder();
	},

	setDefaultOrder: function(){
		this.currentOrder = this.elements.map(function(el, index){
			return index;
		});
	},

	sort: function(newOrder){
		if ($type(newOrder) != 'array') return false;
		var top = 0,
			left = 0,
			next = {},
			zero = {},
			vert = this.options.mode == 'vertical';
		var current = this.elements.map(function(el, index){
			var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
			var val;
			if (vert){
				val = {
					top: top,
					margin: size['margin-top'],
					height: size.totalHeight
				};
				top += val.height - size['margin-top'];
			} else {
				val = {
					left: left,
					margin: size['margin-left'],
					width: size.totalWidth
				};
				left += val.width;
			}
			var plane = vert ? 'top' : 'left';
			zero[index] = {};
			var start = el.getStyle(plane).toInt();
			zero[index][plane] = start || 0;
			return val;
		}, this);
		this.set(zero);
		newOrder = newOrder.map(function(i){ return i.toInt(); });
		if (newOrder.length != this.elements.length){
			this.currentOrder.each(function(index){
				if (!newOrder.contains(index)) newOrder.push(index);
			});
			if (newOrder.length > this.elements.length)
				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
		}
		var margin = top = left = 0;
		newOrder.each(function(item, index){
			var newPos = {};
			if (vert){
				newPos.top = top - current[item].top - margin;
				top += current[item].height;
			} else {
				newPos.left = left - current[item].left;
				left += current[item].width;
			}
			margin = margin + current[item].margin;
			next[item]=newPos;
		}, this);
		var mapped = {};
		$A(newOrder).sort().each(function(index){
			mapped[index] = next[index];
		});
		this.start(mapped);
		this.currentOrder = newOrder;
		return this;
	},

	rearrangeDOM: function(newOrder){
		newOrder = newOrder || this.currentOrder;
		var parent = this.elements[0].getParent();
		var rearranged = [];
		this.elements.setStyle('opacity', 0);
		//move each element and store the new default order
		newOrder.each(function(index){
			rearranged.push(this.elements[index].inject(parent).setStyles({
				top: 0,
				left: 0
			}));
		}, this);
		this.elements.setStyle('opacity', 1);
		this.elements = $$(rearranged);
		this.setDefaultOrder();
		return this;
	},

	getDefaultOrder: function(){
		return this.elements.map(function(el, index){
			return index;
		});
	},

	forward: function(){
		return this.sort(this.getDefaultOrder());
	},

	backward: function(){
		return this.sort(this.getDefaultOrder().reverse());
	},

	reverse: function(){
		return this.sort(this.currentOrder.reverse());
	},

	sortByElements: function(elements){
		return this.sort(elements.map(function(el){
			return this.elements.indexOf(el);
		}, this));
	},

	swap: function(one, two){
		if ($type(one) == 'element') one = this.elements.indexOf(one);
		if ($type(two) == 'element') two = this.elements.indexOf(two);

		var newOrder = $A(this.currentOrder);
		newOrder[this.currentOrder.indexOf(one)] = two;
		newOrder[this.currentOrder.indexOf(two)] = one;
		return this.sort(newOrder);
	}

});

/*
---

script: Drag.js

name: Drag

description: The base Drag Class. Can be used to drag and resize Elements using mouse events.

license: MIT-style license

authors:
  - Valerio Proietti
  - Tom Occhinno
  - Jan Kassens

requires:
  - Core/Events
  - Core/Options
  - Core/Element.Event
  - Core/Element.Style
  - Core/Element.Dimensions
  - /MooTools.More

provides: [Drag]
...

*/

var Drag = new Class({

	Implements: [Events, Options],

	options: {/*
		onBeforeStart: $empty(thisElement),
		onStart: $empty(thisElement, event),
		onSnap: $empty(thisElement)
		onDrag: $empty(thisElement, event),
		onCancel: $empty(thisElement),
		onComplete: $empty(thisElement, event),*/
		snap: 6,
		unit: 'px',
		grid: false,
		style: true,
		limit: false,
		handle: false,
		invert: false,
		preventDefault: false,
		stopPropagation: false,
		modifiers: {x: 'left', y: 'top'}
	},

	initialize: function(){
		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
		this.element = document.id(params.element);
		this.document = this.element.getDocument();
		this.setOptions(params.options || {});
		var htype = $type(this.options.handle);
		this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
		this.mouse = {'now': {}, 'pos': {}};
		this.value = {'start': {}, 'now': {}};

		this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';

		this.bound = {
			start: this.start.bind(this),
			check: this.check.bind(this),
			drag: this.drag.bind(this),
			stop: this.stop.bind(this),
			cancel: this.cancel.bind(this),
			eventStop: $lambda(false)
		};
		this.attach();
	},

	attach: function(){
		this.handles.addEvent('mousedown', this.bound.start);
		return this;
	},

	detach: function(){
		this.handles.removeEvent('mousedown', this.bound.start);
		return this;
	},

	start: function(event){
		if (event.rightClick) return;
		if (this.options.preventDefault) event.preventDefault();
		if (this.options.stopPropagation) event.stopPropagation();
		this.mouse.start = event.page;
		this.fireEvent('beforeStart', this.element);
		var limit = this.options.limit;
		this.limit = {x: [], y: []};
		var styles = this.element.getStyles('left', 'right', 'top', 'bottom');
		this._invert = {
			x: this.options.modifiers.x == 'left' && styles.left == 'auto' &&
			   !isNaN(styles.right.toInt()) && (this.options.modifiers.x = 'right'),
			y: this.options.modifiers.y == 'top' && styles.top == 'auto' &&
			   !isNaN(styles.bottom.toInt()) && (this.options.modifiers.y = 'bottom')
		};

		var z, coordinates;
		for (z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;

			var style = this.element.getStyle(this.options.modifiers[z]);

			// Some browsers (IE and Opera) don't always return pixels.
			if (style && !style.match(/px$/)){
				if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent());
				style = coordinates[this.options.modifiers[z]];
			}

			if (this.options.style) this.value.now[z] = (style || 0).toInt();
			else this.value.now[z] = this.element[this.options.modifiers[z]];

			if (this.options.invert) this.value.now[z] *= -1;
			if (this._invert[z]) this.value.now[z] *= -1;

			this.mouse.pos[z] = event.page[z] - this.value.now[z];

			if (limit && limit[z]){
				for (var i = 2; i--; i){
					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
				}
			}
		}

		if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
		this.document.addEvent(this.selection, this.bound.eventStop);
	},

	check: function(event){
		if (this.options.preventDefault) event.preventDefault();
		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
		if (distance > this.options.snap){
			this.cancel();
			this.document.addEvents({
				mousemove: this.bound.drag,
				mouseup: this.bound.stop
			});
			this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
		}
	},

	drag: function(event){
		if (this.options.preventDefault) event.preventDefault();
		this.mouse.now = event.page;
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
			if (this.options.invert) this.value.now[z] *= -1;
			if (this._invert[z]) this.value.now[z] *= -1;
			if (this.options.limit && this.limit[z]){
				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
					this.value.now[z] = this.limit[z][1];
				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
					this.value.now[z] = this.limit[z][0];
				}
			}
			if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % this.options.grid[z]);
			if (this.options.style) {
				this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
			} else {
				this.element[this.options.modifiers[z]] = this.value.now[z];
			}
		}
		this.fireEvent('drag', [this.element, event]);
	},

	cancel: function(event){
		this.document.removeEvent('mousemove', this.bound.check);
		this.document.removeEvent('mouseup', this.bound.cancel);
		if (event){
			this.document.removeEvent(this.selection, this.bound.eventStop);
			this.fireEvent('cancel', this.element);
		}
	},

	stop: function(event){
		this.document.removeEvent(this.selection, this.bound.eventStop);
		this.document.removeEvent('mousemove', this.bound.drag);
		this.document.removeEvent('mouseup', this.bound.stop);
		if (event) this.fireEvent('complete', [this.element, event]);
	}

});

Element.implement({

	makeResizable: function(options){
		var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
		this.store('resizer', drag);
		return drag.addEvent('drag', function(){
			this.fireEvent('resize', drag);
		}.bind(this));
	}

});


/*
---

script: Drag.Move.js

name: Drag.Move

description: A Drag extension that provides support for the constraining of draggables to containers and droppables.

license: MIT-style license

authors:
  - Valerio Proietti
  - Tom Occhinno
  - Jan Kassens
  - Aaron Newton
  - Scott Kyle

requires:
  - Core/Element.Dimensions
  - /Drag

provides: [Drag.Move]

...
*/

Drag.Move = new Class({

	Extends: Drag,

	options: {/*
		onEnter: $empty(thisElement, overed),
		onLeave: $empty(thisElement, overed),
		onDrop: $empty(thisElement, overed, event),*/
		droppables: [],
		container: false,
		precalculate: false,
		includeMargins: true,
		checkDroppables: true
	},

	initialize: function(element, options){
		this.parent(element, options);
		element = this.element;

		this.droppables = $$(this.options.droppables);
		this.container = document.id(this.options.container);

		if (this.container && $type(this.container) != 'element')
			this.container = document.id(this.container.getDocument().body);

		if (this.options.style){
			if (this.options.modifiers.x == "left" && this.options.modifiers.y == "top"){
				var parentStyles,
					parent = document.id(element.getOffsetParent());
				if (parent) parentStyles = parent.getStyles('border-top-width', 'border-left-width');

				var styles = element.getStyles('left', 'top');
				if (parent && (styles.left == 'auto' || styles.top == 'auto')){
					var parentPosition = element.getPosition(parent);
					parentPosition.x = parentPosition.x - (parentStyles['border-left-width'] ? parentStyles['border-left-width'].toInt() : 0);
					parentPosition.y = parentPosition.y - (parentStyles['border-top-width'] ? parentStyles['border-top-width'].toInt() : 0);
					element.setPosition(parentPosition);
				}
			}
			if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
		}

		this.addEvent('start', this.checkDroppables, true);

		this.overed = null;
	},

	start: function(event){
		if (this.container) this.options.limit = this.calculateLimit();

		if (this.options.precalculate){
			this.positions = this.droppables.map(function(el){
				return el.getCoordinates();
			});
		}

		this.parent(event);
	},

	calculateLimit: function(){
		var offsetParent = document.id(this.element.getOffsetParent()) || document.body,
			containerCoordinates = this.container.getCoordinates(offsetParent),
			containerBorder = {},
			elementMargin = {},
			elementBorder = {},
			containerMargin = {},
			offsetParentBorder = {},
			offsetParentPadding = {};

		['top', 'right', 'bottom', 'left'].each(function(pad){
			containerBorder[pad] = this.container.getStyle('border-' + pad).toInt();
			elementBorder[pad] = this.element.getStyle('border-' + pad).toInt();
			elementMargin[pad] = this.element.getStyle('margin-' + pad).toInt();
			containerMargin[pad] = this.container.getStyle('margin-' + pad).toInt();
			offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
			offsetParentBorder[pad] = offsetParent.getStyle('border-' + pad).toInt();
		}, this);

		var width = this.element.offsetWidth + elementMargin.left + elementMargin.right,
			height = this.element.offsetHeight + elementMargin.top + elementMargin.bottom,
			left = 0,
			top = 0,
			right = containerCoordinates.right - containerBorder.right - width,
			bottom = containerCoordinates.bottom - containerBorder.bottom - height;

		if (this.options.includeMargins){
			left += elementMargin.left;
			top += elementMargin.top;
		} else {
			right += elementMargin.right;
			bottom += elementMargin.bottom;
		}

		if (this.element.getStyle('position') == 'relative'){
			var coords = this.element.getCoordinates(offsetParent);
			coords.left -= this.element.getStyle('left').toInt();
			coords.top -= this.element.getStyle('top').toInt();

			left += containerBorder.left - coords.left;
			top += containerBorder.top - coords.top;
			right += elementMargin.left - coords.left;
			bottom += elementMargin.top - coords.top;

			if (this.container != offsetParent){
				left += containerMargin.left + offsetParentPadding.left;
				top += (Browser.Engine.trident4 ? 0 : containerMargin.top) + offsetParentPadding.top;
			}
		} else {
			left -= elementMargin.left;
			top -= elementMargin.top;
			if (this.container == offsetParent){
				right -= containerBorder.left;
				bottom -= containerBorder.top;
			} else {
				left += containerCoordinates.left + containerBorder.left - offsetParentBorder.left;
				top += containerCoordinates.top + containerBorder.top - offsetParentBorder.top;
				right -= offsetParentBorder.left;
				bottom -= offsetParentBorder.top;
			}
		}

		return {
			x: [left, right],
			y: [top, bottom]
		};
	},

	checkAgainst: function(el, i){
		el = (this.positions) ? this.positions[i] : el.getCoordinates();
		var now = this.mouse.now;
		return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
	},

	checkDroppables: function(){
		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
		if (this.overed != overed){
			if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
			if (overed) this.fireEvent('enter', [this.element, overed]);
			this.overed = overed;
		}
	},

	drag: function(event){
		this.parent(event);
		if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
	},

	stop: function(event){
		this.checkDroppables();
		this.fireEvent('drop', [this.element, this.overed, event]);
		this.overed = null;
		return this.parent(event);
	}

});

Element.implement({

	makeDraggable: function(options){
		var drag = new Drag.Move(this, options);
		this.store('dragger', drag);
		return drag;
	}

});


/*
---

script: Slider.js

name: Slider

description: Class for creating horizontal and vertical slider controls.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Element.Dimensions
  - /Class.Binds
  - /Drag
  - /Element.Measure

provides: [Slider]

...
*/

var Slider = new Class({

	Implements: [Events, Options],

	Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],

	options: {/*
		onTick: $empty(intPosition),
		onChange: $empty(intStep),
		onComplete: $empty(strStep),*/
		onTick: function(position){
			if (this.options.snap) position = this.toPosition(this.step);
			this.knob.setStyle(this.property, position);
		},
		initialStep: 0,
		snap: false,
		offset: 0,
		range: false,
		wheel: false,
		steps: 100,
		mode: 'horizontal'
	},

	initialize: function(element, knob, options){
		this.setOptions(options);
		this.element = document.id(element);
		this.knob = document.id(knob);
		this.previousChange = this.previousEnd = this.step = -1;
		var offset, limit = {}, modifiers = {'x': false, 'y': false};
		switch (this.options.mode){
			case 'vertical':
				this.axis = 'y';
				this.property = 'top';
				offset = 'offsetHeight';
				break;
			case 'horizontal':
				this.axis = 'x';
				this.property = 'left';
				offset = 'offsetWidth';
		}

		this.full = this.element.measure(function(){
			this.half = this.knob[offset] / 2;
			return this.element[offset] - this.knob[offset] + (this.options.offset * 2);
		}.bind(this));

		this.setRange(this.options.range);

		this.knob.setStyle('position', 'relative').setStyle(this.property, - this.options.offset);
		modifiers[this.axis] = this.property;
		limit[this.axis] = [- this.options.offset, this.full - this.options.offset];

		var dragOptions = {
			snap: 0,
			limit: limit,
			modifiers: modifiers,
			onDrag: this.draggedKnob,
			onStart: this.draggedKnob,
			onBeforeStart: (function(){
				this.isDragging = true;
			}).bind(this),
			onCancel: function() {
				this.isDragging = false;
			}.bind(this),
			onComplete: function(){
				this.isDragging = false;
				this.draggedKnob();
				this.end();
			}.bind(this)
		};
		if (this.options.snap){
			dragOptions.grid = Math.ceil(this.stepWidth);
			dragOptions.limit[this.axis][1] = this.full;
		}

		this.drag = new Drag(this.knob, dragOptions);
		this.attach();
		if (this.options.initialStep != null) this.set(this.options.initialStep)
	},

	attach: function(){
		this.element.addEvent('mousedown', this.clickedElement);
		if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
		this.drag.attach();
		return this;
	},

	detach: function(){
		this.element.removeEvent('mousedown', this.clickedElement);
		this.element.removeEvent('mousewheel', this.scrolledElement);
		this.drag.detach();
		return this;
	},

	set: function(step){
		if (!((this.range > 0) ^ (step < this.min))) step = this.min;
		if (!((this.range > 0) ^ (step > this.max))) step = this.max;

		this.step = Math.round(step);
		this.checkStep();
		this.fireEvent('tick', this.toPosition(this.step));
		this.end();
		return this;
	},

	setRange: function(range, pos){
		this.min = $pick(range[0], 0);
		this.max = $pick(range[1], this.options.steps);
		this.range = this.max - this.min;
		this.steps = this.options.steps || this.full;
		this.stepSize = Math.abs(this.range) / this.steps;
		this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
		this.set($pick(pos, this.step).floor(this.min).max(this.max));
		return this;
	},

	clickedElement: function(event){
		if (this.isDragging || event.target == this.knob) return;

		var dir = this.range < 0 ? -1 : 1;
		var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
		position = position.limit(-this.options.offset, this.full -this.options.offset);

		this.step = Math.round(this.min + dir * this.toStep(position));
		this.checkStep();
		this.fireEvent('tick', position);
		this.end();
	},

	scrolledElement: function(event){
		var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
		this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
		event.stop();
	},

	draggedKnob: function(){
		var dir = this.range < 0 ? -1 : 1;
		var position = this.drag.value.now[this.axis];
		position = position.limit(-this.options.offset, this.full -this.options.offset);
		this.step = Math.round(this.min + dir * this.toStep(position));
		this.checkStep();
	},

	checkStep: function(){
		if (this.previousChange != this.step){
			this.previousChange = this.step;
			this.fireEvent('change', this.step);
		}
	},

	end: function(){
		if (this.previousEnd !== this.step){
			this.previousEnd = this.step;
			this.fireEvent('complete', this.step + '');
		}
	},

	toStep: function(position){
		var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
		return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
	},

	toPosition: function(step){
		return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
	}

});


/*
---

script: Sortables.js

name: Sortables

description: Class for creating a drag and drop sorting interface for lists of items.

license: MIT-style license

authors:
  - Tom Occhino

requires:
  - /Drag.Move

provides: [Sortables]

...
*/

var Sortables = new Class({

	Implements: [Events, Options],

	options: {/*
		onSort: $empty(element, clone),
		onStart: $empty(element, clone),
		onComplete: $empty(element),*/
		snap: 4,
		opacity: 1,
		clone: false,
		revert: false,
		handle: false,
		constrain: false,
		preventDefault: false
	},

	initialize: function(lists, options){
		this.setOptions(options);
		this.elements = [];
		this.lists = [];
		this.idle = true;

		this.addLists($$(document.id(lists) || lists));
		if (!this.options.clone) this.options.revert = false;
		if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
	},

	attach: function(){
		this.addLists(this.lists);
		return this;
	},

	detach: function(){
		this.lists = this.removeLists(this.lists);
		return this;
	},

	addItems: function(){
		Array.flatten(arguments).each(function(element){
			this.elements.push(element);
			var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
			(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
		}, this);
		return this;
	},

	addLists: function(){
		Array.flatten(arguments).each(function(list){
			this.lists.push(list);
			this.addItems(list.getChildren());
		}, this);
		return this;
	},

	removeItems: function(){
		return $$(Array.flatten(arguments).map(function(element){
			this.elements.erase(element);
			var start = element.retrieve('sortables:start');
			(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);

			return element;
		}, this));
	},

	removeLists: function(){
		return $$(Array.flatten(arguments).map(function(list){
			this.lists.erase(list);
			this.removeItems(list.getChildren());

			return list;
		}, this));
	},

	getClone: function(event, element){
		if (!this.options.clone) return new Element(element.tagName).inject(document.body);
		if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
		var clone = element.clone(true).setStyles({
			margin: '0px',
			position: 'absolute',
			visibility: 'hidden',
			'width': element.getStyle('width')
		});
		//prevent the duplicated radio inputs from unchecking the real one
		if (clone.get('html').test('radio')) {
			clone.getElements('input[type=radio]').each(function(input, i) {
				input.set('name', 'clone_' + i);
				if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
			});
		}

		return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
	},

	getDroppables: function(){
		var droppables = this.list.getChildren();
		if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
		return droppables.erase(this.clone).erase(this.element);
	},

	insert: function(dragging, element){
		var where = 'inside';
		if (this.lists.contains(element)){
			this.list = element;
			this.drag.droppables = this.getDroppables();
		} else {
			where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
		}
		this.element.inject(element, where);
		this.fireEvent('sort', [this.element, this.clone]);
	},

	start: function(event, element){
		if (
			!this.idle ||
			event.rightClick ||
			['button', 'input'].contains(document.id(event.target).get('tag'))
		) return;

		this.idle = false;
		this.element = element;
		this.opacity = element.get('opacity');
		this.list = element.getParent();
		this.clone = this.getClone(event, element);

		this.drag = new Drag.Move(this.clone, {
			preventDefault: this.options.preventDefault,
			snap: this.options.snap,
			container: this.options.constrain && this.element.getParent(),
			droppables: this.getDroppables(),
			onSnap: function(){
				event.stop();
				this.clone.setStyle('visibility', 'visible');
				this.element.set('opacity', this.options.opacity || 0);
				this.fireEvent('start', [this.element, this.clone]);
			}.bind(this),
			onEnter: this.insert.bind(this),
			onCancel: this.reset.bind(this),
			onComplete: this.end.bind(this)
		});

		this.clone.inject(this.element, 'before');
		this.drag.start(event);
	},

	end: function(){
		this.drag.detach();
		this.element.set('opacity', this.opacity);
		if (this.effect){
			var dim = this.element.getStyles('width', 'height');
			var pos = this.clone.computePosition(this.element.getPosition(this.clone.getOffsetParent()));
			this.effect.element = this.clone;
			this.effect.start({
				top: pos.top,
				left: pos.left,
				width: dim.width,
				height: dim.height,
				opacity: 0.25
			}).chain(this.reset.bind(this));
		} else {
			this.reset();
		}
	},

	reset: function(){
		this.idle = true;
		this.clone.destroy();
		this.fireEvent('complete', this.element);
	},

	serialize: function(){
		var params = Array.link(arguments, {modifier: Function.type, index: $defined});
		var serial = this.lists.map(function(list){
			return list.getChildren().map(params.modifier || function(element){
				return element.get('id');
			}, this);
		}, this);

		var index = params.index;
		if (this.lists.length == 1) index = 0;
		return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
	}

});


/*
---

script: Request.JSONP.js

name: Request.JSONP

description: Defines Request.JSONP, a class for cross domain javascript via script injection.

license: MIT-style license

authors:
  - Aaron Newton
  - Guillermo Rauch

requires:
  - Core/Element
  - Core/Request
  - /Log

provides: [Request.JSONP]

...
*/

Request.JSONP = new Class({

	Implements: [Chain, Events, Options, Log],

	options: {/*
		onRetry: $empty(intRetries),
		onRequest: $empty(scriptElement),
		onComplete: $empty(data),
		onSuccess: $empty(data),
		onCancel: $empty(),
		log: false,
		*/
		url: '',
		data: {},
		retries: 0,
		timeout: 0,
		link: 'ignore',
		callbackKey: 'callback',
		injectScript: document.head
	},

	initialize: function(options){
		this.setOptions(options);
		if (this.options.log) this.enableLog();
		this.running = false;
		this.requests = 0;
		this.triesRemaining = [];
	},

	check: function(){
		if (!this.running) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
		}
		return false;
	},

	send: function(options){
		if (!$chk(arguments[1]) && !this.check(options)) return this;

		var type = $type(options),
				old = this.options,
				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
		if (type == 'string' || type == 'element') options = {data: options};

		options = $extend({data: old.data, url: old.url}, options);

		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
		var remaining = this.triesRemaining[index];

		(function(){
			var script = this.getScript(options);
			this.log('JSONP retrieving script with url: ' + script.get('src'));
			this.fireEvent('request', script);
			this.running = true;

			(function(){
				if (remaining){
					this.triesRemaining[index] = remaining - 1;
					if (script){
						script.destroy();
						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
					}
				} else if(this.running && script && this.options.timeout){
					script.destroy();
					this.cancel().fireEvent('failure');
				}
			}).delay(this.options.timeout, this);
		}).delay(Browser.Engine.trident ? 50 : 0, this);
		return this;
	},

	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.fireEvent('cancel');
		return this;
	},

	getScript: function(options){
		var index = Request.JSONP.counter,
				data;
		Request.JSONP.counter++;

		switch ($type(options.data)){
			case 'element': data = document.id(options.data).toQueryString(); break;
			case 'object': case 'hash': data = Hash.toQueryString(options.data);
		}

		var src = options.url +
			 (options.url.test('\\?') ? '&' :'?') +
			 (options.callbackKey || this.options.callbackKey) +
			 '=Request.JSONP.request_map.request_'+ index +
			 (data ? '&' + data : '');
		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');

		var script = new Element('script', {type: 'text/javascript', src: src});
		Request.JSONP.request_map['request_' + index] = function(){ this.success(arguments, script); }.bind(this);
		return script.inject(this.options.injectScript);
	},

	success: function(args, script){
		if (!this.running) return false;
		if (script) script.destroy();
		this.running = false;
		this.log('JSONP successfully retrieved: ', args);
		this.fireEvent('complete', args).fireEvent('success', args).callChain();
	}

});

Request.JSONP.counter = 0;
Request.JSONP.request_map = {};


/*
---

script: Request.Queue.js

name: Request.Queue

description: Controls several instances of Request and its variants to run only one request at a time.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element
  - Core/Request
  - /Log

provides: [Request.Queue]

...
*/

Request.Queue = new Class({

	Implements: [Options, Events],

	Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],

	options: {/*
		onRequest: $empty(argsPassedToOnRequest),
		onSuccess: $empty(argsPassedToOnSuccess),
		onComplete: $empty(argsPassedToOnComplete),
		onCancel: $empty(argsPassedToOnCancel),
		onException: $empty(argsPassedToOnException),
		onFailure: $empty(argsPassedToOnFailure),
		onEnd: $empty,
		*/
		stopOnFailure: true,
		autoAdvance: true,
		concurrent: 1,
		requests: {}
	},

	initialize: function(options){
		if(options){
			var requests = options.requests;
			delete options.requests;
		}
		this.setOptions(options);
		this.requests = new Hash;
		this.queue = [];
		this.reqBinders = {};

		if(requests) this.addRequests(requests);
	},

	addRequest: function(name, request){
		this.requests.set(name, request);
		this.attach(name, request);
		return this;
	},

	addRequests: function(obj){
		$each(obj, function(req, name){
			this.addRequest(name, req);
		}, this);
		return this;
	},

	getName: function(req){
		return this.requests.keyOf(req);
	},

	attach: function(name, req){
		if (req._groupSend) return this;
		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
			if(!this.reqBinders[name]) this.reqBinders[name] = {};
			this.reqBinders[name][evt] = function(){
				this['on' + evt.capitalize()].apply(this, [name, req].extend(arguments));
			}.bind(this);
			req.addEvent(evt, this.reqBinders[name][evt]);
		}, this);
		req._groupSend = req.send;
		req.send = function(options){
			this.send(name, options);
			return req;
		}.bind(this);
		return this;
	},

	removeRequest: function(req){
		var name = $type(req) == 'object' ? this.getName(req) : req;
		if (!name && $type(name) != 'string') return this;
		req = this.requests.get(name);
		if (!req) return this;
		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
			req.removeEvent(evt, this.reqBinders[name][evt]);
		}, this);
		req.send = req._groupSend;
		delete req._groupSend;
		return this;
	},

	getRunning: function(){
		return this.requests.filter(function(r){
			return r.running;
		});
	},

	isRunning: function(){
		return !!(this.getRunning().getKeys().length);
	},

	send: function(name, options){
		var q = function(){
			this.requests.get(name)._groupSend(options);
			this.queue.erase(q);
		}.bind(this);
		q.name = name;
		if (this.getRunning().getKeys().length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
		else q();
		return this;
	},

	hasNext: function(name){
		return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
	},

	resume: function(){
		this.error = false;
		(this.options.concurrent - this.getRunning().getKeys().length).times(this.runNext, this);
		return this;
	},

	runNext: function(name){
		if (!this.queue.length) return this;
		if (!name){
			this.queue[0]();
		} else {
			var found;
			this.queue.each(function(q){
				if (!found && q.name == name){
					found = true;
					q();
				}
			});
		}
		return this;
	},

	runAll: function() {
		this.queue.each(function(q) {
			q();
		});
		return this;
	},

	clear: function(name){
		if (!name){
			this.queue.empty();
		} else {
			this.queue = this.queue.map(function(q){
				if (q.name != name) return q;
				else return false;
			}).filter(function(q){ return q; });
		}
		return this;
	},

	cancel: function(name){
		this.requests.get(name).cancel();
		return this;
	},

	onRequest: function(){
		this.fireEvent('request', arguments);
	},

	onComplete: function(){
		this.fireEvent('complete', arguments);
		if (!this.queue.length) this.fireEvent('end');
	},

	onCancel: function(){
		if (this.options.autoAdvance && !this.error) this.runNext();
		this.fireEvent('cancel', arguments);
	},

	onSuccess: function(){
		if (this.options.autoAdvance && !this.error) this.runNext();
		this.fireEvent('success', arguments);
	},

	onFailure: function(){
		this.error = true;
		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
		this.fireEvent('failure', arguments);
	},

	onException: function(){
		this.error = true;
		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
		this.fireEvent('exception', arguments);
	}

});


/*
---

script: Request.Periodical.js

name: Request.Periodical

description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load

license: MIT-style license

authors:
  - Christoph Pojer

requires:
  - Core/Request
  - /MooTools.More

provides: [Request.Periodical]

...
*/

Request.implement({

	options: {
		initialDelay: 5000,
		delay: 5000,
		limit: 60000
	},

	startTimer: function(data){
		var fn = function(){
			if (!this.running) this.send({data: data});
		};
		this.timer = fn.delay(this.options.initialDelay, this);
		this.lastDelay = this.options.initialDelay;
		this.completeCheck = function(response){
			$clear(this.timer);
			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
			this.timer = fn.delay(this.lastDelay, this);
		};
		return this.addEvent('complete', this.completeCheck);
	},

	stopTimer: function(){
		$clear(this.timer);
		return this.removeEvent('complete', this.completeCheck);
	}

});

/*
---

script: Assets.js

name: Assets

description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Element.Event
  - /MooTools.More

provides: [Assets]

...
*/

var Asset = {

	javascript: function(source, properties){
		properties = $extend({
			onload: $empty,
			document: document,
			check: $lambda(true)
		}, properties);

		if (properties.onLoad) {
			properties.onload = properties.onLoad;
			delete properties.onLoad;
		}
		var script = new Element('script', {src: source, type: 'text/javascript'});

		var load = properties.onload.bind(script),
			check = properties.check,
			doc = properties.document;
		delete properties.onload;
		delete properties.check;
		delete properties.document;

		script.addEvents({
			load: load,
			readystatechange: function(){
				if (['loaded', 'complete'].contains(this.readyState)) load();
			}
		}).set(properties);

		if (Browser.Engine.webkit419) var checker = (function(){
			if (!$try(check)) return;
			$clear(checker);
			load();
		}).periodical(50);

		return script.inject(doc.head);
	},

	css: function(source, properties){
		properties = properties || {};
		var onload = properties.onload || properties.onLoad;
		if (onload) {
			properties.events = properties.events || {};
			properties.events.load = onload;
			delete properties.onload;
			delete properties.onLoad;
		}
		return new Element('link', $merge({
			rel: 'stylesheet',
			media: 'screen',
			type: 'text/css',
			href: source
		}, properties)).inject(document.head);
	},

	image: function(source, properties){
		properties = $merge({
			onload: $empty,
			onabort: $empty,
			onerror: $empty
		}, properties);
		var image = new Image();
		var element = document.id(image) || new Element('img');
		['load', 'abort', 'error'].each(function(name){
			var type = 'on' + name;
			var cap = name.capitalize();
			if (properties['on' + cap]) {
				properties[type] = properties['on' + cap];
				delete properties['on' + cap];
			}
			var event = properties[type];
			delete properties[type];
			image[type] = function(){
				if (!image) return;
				if (!element.parentNode){
					element.width = image.width;
					element.height = image.height;
				}
				image = image.onload = image.onabort = image.onerror = null;
				event.delay(1, element, element);
				element.fireEvent(name, element, 1);
			};
		});
		image.src = element.src = source;
		if (image && image.complete) image.onload.delay(1);
		return element.set(properties);
	},

	images: function(sources, options){
		options = $merge({
			onComplete: $empty,
			onProgress: $empty,
			onError: $empty,
			properties: {}
		}, options);
		sources = $splat(sources);
		var images = [];
		var counter = 0;
		return new Elements(sources.map(function(source, index){
			return Asset.image(source, $extend(options.properties, {
				onload: function(){
					options.onProgress.call(this, counter, index);
					counter++;
					if (counter == sources.length) options.onComplete();
				},
				onerror: function(){
					options.onError.call(this, counter, index);
					counter++;
					if (counter == sources.length) options.onComplete();
				}
			}));
		}));
	}

};

/*
---

script: Color.js

name: Color

description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Array
  - Core/String
  - Core/Number
  - Core/Hash
  - Core/Function
  - Core/$util

provides: [Color]

...
*/

var Color = new Native({

	initialize: function(color, type){
		if (arguments.length >= 3){
			type = 'rgb'; color = Array.slice(arguments, 0, 3);
		} else if (typeof color == 'string'){
			if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
			else if (color.match(/hsb/)) color = color.hsbToRgb();
			else color = color.hexToRgb(true);
		}
		type = type || 'rgb';
		switch (type){
			case 'hsb':
				var old = color;
				color = color.hsbToRgb();
				color.hsb = old;
			break;
			case 'hex': color = color.hexToRgb(true); break;
		}
		color.rgb = color.slice(0, 3);
		color.hsb = color.hsb || color.rgbToHsb();
		color.hex = color.rgbToHex();
		return $extend(color, this);
	}

});

Color.implement({

	mix: function(){
		var colors = Array.slice(arguments);
		var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
		var rgb = this.slice();
		colors.each(function(color){
			color = new Color(color);
			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
		});
		return new Color(rgb, 'rgb');
	},

	invert: function(){
		return new Color(this.map(function(value){
			return 255 - value;
		}));
	},

	setHue: function(value){
		return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
	},

	setSaturation: function(percent){
		return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
	},

	setBrightness: function(percent){
		return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
	}

});

var $RGB = function(r, g, b){
	return new Color([r, g, b], 'rgb');
};

var $HSB = function(h, s, b){
	return new Color([h, s, b], 'hsb');
};

var $HEX = function(hex){
	return new Color(hex, 'hex');
};

Array.implement({

	rgbToHsb: function(){
		var red = this[0],
				green = this[1],
				blue = this[2],
				hue = 0;
		var max = Math.max(red, green, blue),
				min = Math.min(red, green, blue);
		var delta = max - min;
		var brightness = max / 255,
				saturation = (max != 0) ? delta / max : 0;
		if(saturation != 0) {
			var rr = (max - red) / delta;
			var gr = (max - green) / delta;
			var br = (max - blue) / delta;
			if (red == max) hue = br - gr;
			else if (green == max) hue = 2 + rr - br;
			else hue = 4 + gr - rr;
			hue /= 6;
			if (hue < 0) hue++;
		}
		return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
	},

	hsbToRgb: function(){
		var br = Math.round(this[2] / 100 * 255);
		if (this[1] == 0){
			return [br, br, br];
		} else {
			var hue = this[0] % 360;
			var f = hue % 60;
			var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
			var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
			var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
			switch (Math.floor(hue / 60)){
				case 0: return [br, t, p];
				case 1: return [q, br, p];
				case 2: return [p, br, t];
				case 3: return [p, q, br];
				case 4: return [t, p, br];
				case 5: return [br, p, q];
			}
		}
		return false;
	}

});

String.implement({

	rgbToHsb: function(){
		var rgb = this.match(/\d{1,3}/g);
		return (rgb) ? rgb.rgbToHsb() : null;
	},

	hsbToRgb: function(){
		var hsb = this.match(/\d{1,3}/g);
		return (hsb) ? hsb.hsbToRgb() : null;
	}

});


/*
---

script: Group.js

name: Group

description: Class for monitoring collections of events

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Events
  - /MooTools.More

provides: [Group]

...
*/

var Group = new Class({

	initialize: function(){
		this.instances = Array.flatten(arguments);
		this.events = {};
		this.checker = {};
	},

	addEvent: function(type, fn){
		this.checker[type] = this.checker[type] || {};
		this.events[type] = this.events[type] || [];
		if (this.events[type].contains(fn)) return false;
		else this.events[type].push(fn);
		this.instances.each(function(instance, i){
			instance.addEvent(type, this.check.bind(this, [type, instance, i]));
		}, this);
		return this;
	},

	check: function(type, instance, i){
		this.checker[type][i] = true;
		var every = this.instances.every(function(current, j){
			return this.checker[type][j] || false;
		}, this);
		if (!every) return;
		this.checker[type] = {};
		this.events[type].each(function(event){
			event.call(this, this.instances, instance);
		}, this);
	}

});


/*
---

script: Hash.Cookie.js

name: Hash.Cookie

description: Class for creating, reading, and deleting Cookies in JSON format.

license: MIT-style license

authors:
  - Valerio Proietti
  - Aaron Newton

requires:
  - Core/Cookie
  - Core/JSON
  - /MooTools.More

provides: [Hash.Cookie]

...
*/

Hash.Cookie = new Class({

	Extends: Cookie,

	options: {
		autoSave: true
	},

	initialize: function(name, options){
		this.parent(name, options);
		this.load();
	},

	save: function(){
		var value = JSON.encode(this.hash);
		if (!value || value.length > 4096) return false; //cookie would be truncated!
		if (value == '{}') this.dispose();
		else this.write(value);
		return true;
	},

	load: function(){
		this.hash = new Hash(JSON.decode(this.read(), true));
		return this;
	}

});

Hash.each(Hash.prototype, function(method, name){
	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
		var value = method.apply(this.hash, arguments);
		if (this.options.autoSave) this.save();
		return value;
	});
});

/*
---

script: IframeShim.js

name: IframeShim

description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Element.Event
  - Core/Element.Style
  - Core/Options
  - Core/Events
  - /Element.Position
  - /Class.Occlude

provides: [IframeShim]

...
*/

var IframeShim = new Class({

	Implements: [Options, Events, Class.Occlude],

	options: {
		className: 'iframeShim',
		src: 'javascript:false;document.write("");',
		display: false,
		zIndex: null,
		margin: 0,
		offset: {x: 0, y: 0},
		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
	},

	property: 'IframeShim',

	initialize: function(element, options){
		this.element = document.id(element);
		if (this.occlude()) return this.occluded;
		this.setOptions(options);
		this.makeShim();
		return this;
	},

	makeShim: function(){
		if(this.options.browsers){
			var zIndex = this.element.getStyle('zIndex').toInt();

			if (!zIndex){
				zIndex = 1;
				var pos = this.element.getStyle('position');
				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
				this.element.setStyle('zIndex', zIndex);
			}
			zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
			if (zIndex < 0) zIndex = 1;
			this.shim = new Element('iframe', {
				src: this.options.src,
				scrolling: 'no',
				frameborder: 0,
				styles: {
					zIndex: zIndex,
					position: 'absolute',
					border: 'none',
					filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
				},
				'class': this.options.className
			}).store('IframeShim', this);
			var inject = (function(){
				this.shim.inject(this.element, 'after');
				this[this.options.display ? 'show' : 'hide']();
				this.fireEvent('inject');
			}).bind(this);
			if (!IframeShim.ready) window.addEvent('load', inject);
			else inject();
		} else {
			this.position = this.hide = this.show = this.dispose = $lambda(this);
		}
	},

	position: function(){
		if (!IframeShim.ready || !this.shim) return this;
		var size = this.element.measure(function(){
			return this.getSize();
		});
		if (this.options.margin != undefined){
			size.x = size.x - (this.options.margin * 2);
			size.y = size.y - (this.options.margin * 2);
			this.options.offset.x += this.options.margin;
			this.options.offset.y += this.options.margin;
		}
		this.shim.set({width: size.x, height: size.y}).position({
			relativeTo: this.element,
			offset: this.options.offset
		});
		return this;
	},

	hide: function(){
		if (this.shim) this.shim.setStyle('display', 'none');
		return this;
	},

	show: function(){
		if (this.shim) this.shim.setStyle('display', 'block');
		return this.position();
	},

	dispose: function(){
		if (this.shim) this.shim.dispose();
		return this;
	},

	destroy: function(){
		if (this.shim) this.shim.destroy();
		return this;
	}

});

window.addEvent('load', function(){
	IframeShim.ready = true;
});


/*
---

script: HtmlTable.js

name: HtmlTable

description: Builds table elements with methods to add rows.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Options
  - Core/Events
  - /Class.Occlude

provides: [HtmlTable]

...
*/

var HtmlTable = new Class({

	Implements: [Options, Events, Class.Occlude],

	options: {
		properties: {
			cellpadding: 0,
			cellspacing: 0,
			border: 0
		},
		rows: [],
		headers: [],
		footers: []
	},

	property: 'HtmlTable',

	initialize: function(){
		var params = Array.link(arguments, {options: Object.type, table: Element.type});
		this.setOptions(params.options);
		this.element = params.table || new Element('table', this.options.properties);
		if (this.occlude()) return this.occluded;
		this.build();
	},

	build: function(){
		this.element.store('HtmlTable', this);

		this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
		$$(this.body.rows);

		if (this.options.headers.length) this.setHeaders(this.options.headers);
		else this.thead = document.id(this.element.tHead);
		if (this.thead) this.head = document.id(this.thead.rows[0]);

		if (this.options.footers.length) this.setFooters(this.options.footers);
		this.tfoot = document.id(this.element.tFoot);
		if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);

		this.options.rows.each(function(row){
			this.push(row);
		}, this);

		['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
				this[method] = this.element[method].bind(this.element);
		}, this);
	},

	toElement: function(){
		return this.element;
	},

	empty: function(){
		this.body.empty();
		return this;
	},

	set: function(what, items) {
		var target = (what == 'headers') ? 'tHead' : 'tFoot';
		this[target.toLowerCase()] = (document.id(this.element[target]) || new Element(target.toLowerCase()).inject(this.element, 'top')).empty();
		var data = this.push(items, {}, this[target.toLowerCase()], what == 'headers' ? 'th' : 'td');
		if (what == 'headers') this.head = document.id(this.thead.rows[0]);
		else this.foot = document.id(this.thead.rows[0]);
		return data;
	},

	setHeaders: function(headers){
		this.set('headers', headers);
		return this;
	},

	setFooters: function(footers){
		this.set('footers', footers);
		return this;
	},

	push: function(row, rowProperties, target, tag){
		if ($type(row) == "element" && row.get('tag') == 'tr') {
			row.inject(target || this.body);
			return {
				tr: row,
				tds: row.getChildren('td')
			};
		}
		var tds = row.map(function(data){
			var td = new Element(tag || 'td', data ? data.properties : {}),
				type = (data ? data.content : '') || data,
				element = document.id(type);
			if($type(type) != 'string' && element) td.adopt(element);
			else td.set('html', type);

			return td;
		});

		return {
			tr: new Element('tr', rowProperties).inject(target || this.body).adopt(tds),
			tds: tds
		};
	}

});


/*
---

script: HtmlTable.Zebra.js

name: HtmlTable.Zebra

description: Builds a stripy table with methods to add rows.

license: MIT-style license

authors:
  - Harald Kirschner
  - Aaron Newton

requires:
  - /HtmlTable
  - /Class.refactor

provides: [HtmlTable.Zebra]

...
*/

HtmlTable = Class.refactor(HtmlTable, {

	options: {
		classZebra: 'table-tr-odd',
		zebra: true
	},

	initialize: function(){
		this.previous.apply(this, arguments);
		if (this.occluded) return this.occluded;
		if (this.options.zebra) this.updateZebras();
	},

	updateZebras: function(){
		Array.each(this.body.rows, this.zebra, this);
	},

	zebra: function(row, i){
		return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
	},

	push: function(){
		var pushed = this.previous.apply(this, arguments);
		if (this.options.zebra) this.updateZebras();
		return pushed;
	}

});

/*
---

script: HtmlTable.Sort.js

name: HtmlTable.Sort

description: Builds a stripy, sortable table with methods to add rows.

license: MIT-style license

authors:
  - Harald Kirschner
  - Aaron Newton

requires:
  - Core/Hash
  - /HtmlTable
  - /Class.refactor
  - /Element.Delegation
  - /String.Extras
  - /Date

provides: [HtmlTable.Sort]

...
*/

HtmlTable = Class.refactor(HtmlTable, {

	options: {/*
		onSort: $empty, */
		sortIndex: 0,
		sortReverse: false,
		parsers: [],
		defaultParser: 'string',
		classSortable: 'table-sortable',
		classHeadSort: 'table-th-sort',
		classHeadSortRev: 'table-th-sort-rev',
		classNoSort: 'table-th-nosort',
		classGroupHead: 'table-tr-group-head',
		classGroup: 'table-tr-group',
		classCellSort: 'table-td-sort',
		classSortSpan: 'table-th-sort-span',
		sortable: false
	},

	initialize: function () {
		this.previous.apply(this, arguments);
		if (this.occluded) return this.occluded;
		this.sorted = {index: null, dir: 1};
		this.bound = {
			headClick: this.headClick.bind(this)
		};
		this.sortSpans = new Elements();
		if (this.options.sortable) {
			this.enableSort();
			if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
		}
	},

	attachSorts: function(attach){
		this.element.removeEvents('click:relay(th)');
		this.element[$pick(attach, true) ? 'addEvent' : 'removeEvent']('click:relay(th)', this.bound.headClick);
	},

	setHeaders: function(){
		this.previous.apply(this, arguments);
		if (this.sortEnabled) this.detectParsers();
	},

	detectParsers: function(force){
		if (!this.head) return;
		var parsers = this.options.parsers,
				rows = this.body.rows;

		// auto-detect
		this.parsers = $$(this.head.cells).map(function(cell, index) {
			if (!force && (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser'))) return cell.retrieve('htmltable-parser');
			var thDiv = new Element('div');
			$each(cell.childNodes, function(node) {
				thDiv.adopt(node);
			});
			thDiv.inject(cell);
			var sortSpan = new Element('span', {'html': '&#160;', 'class': this.options.classSortSpan}).inject(thDiv, 'top');

			this.sortSpans.push(sortSpan);

			var parser = parsers[index],
					cancel;
			switch ($type(parser)) {
				case 'function': parser = {convert: parser}; cancel = true; break;
				case 'string': parser = parser; cancel = true; break;
			}
			if (!cancel) {
				HtmlTable.Parsers.some(function(current) {
					var match = current.match;
					if (!match) return false;
					for (var i = 0, j = rows.length; i < j; i++) {
						var cell = document.id(rows[i].cells[index]);
						var text = cell ? cell.get('html').clean() : '';
						if (text && match.test(text)) {
							parser = current;
							return true;
						}
					}
				});
			}

			if (!parser) parser = this.options.defaultParser;
			cell.store('htmltable-parser', parser);
			return parser;
		}, this);
	},

	headClick: function(event, el) {
		if (!this.head || el.hasClass(this.options.classNoSort)) return;
		var index = Array.indexOf(this.head.cells, el);
		this.sort(index);
		return false;
	},

	sort: function(index, reverse, pre) {
		if (!this.head) return;
		pre = !!(pre);
		var classCellSort = this.options.classCellSort;
		var classGroup = this.options.classGroup,
				classGroupHead = this.options.classGroupHead;

		if (!pre) {
			if (index != null) {
				if (this.sorted.index == index) {
					this.sorted.reverse = !(this.sorted.reverse);
				} else {
					if (this.sorted.index != null) {
						this.sorted.reverse = false;
						this.head.cells[this.sorted.index].removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
					} else {
						this.sorted.reverse = true;
					}
					this.sorted.index = index;
				}
			} else {
				index = this.sorted.index;
			}

			if (reverse != null) this.sorted.reverse = reverse;

			var head = document.id(this.head.cells[index]);
			if (head) {
				head.addClass(this.options.classHeadSort);
				if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
				else head.removeClass(this.options.classHeadSortRev);
			}

			this.body.getElements('td').removeClass(this.options.classCellSort);
		}

		var parser = this.parsers[index];
		if ($type(parser) == 'string') parser = HtmlTable.Parsers.get(parser);
		if (!parser) return;

		if (!Browser.Engine.trident) {
			var rel = this.body.getParent();
			this.body.dispose();
		}

		var data = Array.map(this.body.rows, function(row, i) {
			var value = parser.convert.call(document.id(row.cells[index]));

			return {
				position: i,
				value: value,
				toString:  function() {
					return value.toString();
				}
			};
		}, this);
		data.reverse(true);

		data.sort(function(a, b){
			if (a.value === b.value) return 0;
			return a.value > b.value ? 1 : -1;
		});

		if (!this.sorted.reverse) data.reverse(true);

		var i = data.length, body = this.body;
		var j, position, entry, group;

		while (i) {
			var item = data[--i];
			position = item.position;
			var row = body.rows[position];
			if (row.disabled) continue;

			if (!pre) {
				if (group === item.value) {
					row.removeClass(classGroupHead).addClass(classGroup);
				} else {
					group = item.value;
					row.removeClass(classGroup).addClass(classGroupHead);
				}
				if (this.options.zebra) this.zebra(row, i);

				row.cells[index].addClass(classCellSort);
			}

			body.appendChild(row);
			for (j = 0; j < i; j++) {
				if (data[j].position > position) data[j].position--;
			}
		};
		data = null;
		if (rel) rel.grab(body);

		return this.fireEvent('sort', [body, index]);
	},

	reSort: function(){
		if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
		return this;
	},

	enableSort: function(){
		this.element.addClass(this.options.classSortable);
		this.attachSorts(true);
		this.detectParsers();
		this.sortEnabled = true;
		return this;
	},

	disableSort: function(){
		this.element.removeClass(this.options.classSortable);
		this.attachSorts(false);
		this.sortSpans.each(function(span) { span.destroy(); });
		this.sortSpans.empty();
		this.sortEnabled = false;
		return this;
	}

});

HtmlTable.Parsers = new Hash({

	'date': {
		match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
		convert: function() {
			var d = Date.parse(this.get('text').stripTags());
			return $type(d) == 'date' ? d.format('db') : '';
		},
		type: 'date'
	},
	'input-checked': {
		match: / type="(radio|checkbox)" /,
		convert: function() {
			return this.getElement('input').checked;
		}
	},
	'input-value': {
		match: /<input/,
		convert: function() {
			return this.getElement('input').value;
		}
	},
	'number': {
		match: /^\d+[^\d.,]*$/,
		convert: function() {
			return this.get('text').stripTags().toInt();
		},
		number: true
	},
	'numberLax': {
		match: /^[^\d]+\d+$/,
		convert: function() {
			return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
		},
		number: true
	},
	'float': {
		match: /^[\d]+\.[\d]+/,
		convert: function() {
			return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
		},
		number: true
	},
	'floatLax': {
		match: /^[^\d]+[\d]+\.[\d]+$/,
		convert: function() {
			return this.get('text').replace(/[^-?^\d.]/, '').stripTags();
		},
		number: true
	},
	'string': {
		match: null,
		convert: function() {
			return this.get('text').stripTags();
		}
	},
	'title': {
		match: null,
		convert: function() {
			return this.title;
		}
	}

});


HtmlTable.defineParsers = function(parsers){
	HtmlTable.Parsers = new Hash(parsers).combine(HtmlTable.Parsers);
};


/*
---

script: HtmlTable.Select.js

name: HtmlTable.Select

description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.

license: MIT-style license

authors:
  - Harald Kirschner
  - Aaron Newton

requires:
  - /Keyboard.Extras
  - /HtmlTable
  - /Class.refactor
  - /Element.Delegation
  - /Element.Shortcuts

provides: [HtmlTable.Select]

...
*/

HtmlTable = Class.refactor(HtmlTable, {

	options: {
		/*onRowFocus: $empty,
		onRowUnfocus: $empty,*/
		useKeyboard: true,
		classRowSelected: 'table-tr-selected',
		classRowHovered: 'table-tr-hovered',
		classSelectable: 'table-selectable',
		shiftForMultiSelect: true,
		allowMultiSelect: true,
		selectable: false
	},

	initialize: function(){
		this.previous.apply(this, arguments);
		if (this.occluded) return this.occluded;
		this._selectedRows = new Elements();
		this._bound = {
			mouseleave: this._mouseleave.bind(this),
			clickRow: this._clickRow.bind(this)
		};
		if (this.options.selectable) this.enableSelect();
	},

	enableSelect: function(){
		this._selectEnabled = true;
		this._attachSelects();
		this.element.addClass(this.options.classSelectable);
	},

	disableSelect: function(){
		this._selectEnabled = false;
		this._attachSelects(false);
		this.element.removeClass(this.options.classSelectable);
	},

	push: function(){
		var ret = this.previous.apply(this, arguments);
		this._updateSelects();
		return ret;
	},

	toggleRow: function(row){
		return this.isSelected(row) ? this.deselectRow.apply(this, arguments) : this.selectRow.apply(this, arguments);
	},

	selectRow: function(row, _nocheck){
		//private variable _nocheck: boolean whether or not to confirm the row is in the table body
		//added here for optimization when selecting ranges
		if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
		if (!this.options.allowMultiSelect) this.selectNone();

		if (!this.isSelected(row)) {
			this._selectedRows.push(row);
			row.addClass(this.options.classRowSelected);
			this.fireEvent('rowFocus', [row, this._selectedRows]);
		}
		this._focused = row;
		document.clearSelection();
		return this;
	},

	isSelected: function(row){
		return this._selectedRows.contains(row);
	},

	deselectRow: function(row, _nocheck){
		if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
		this._selectedRows.erase(row);
		row.removeClass(this.options.classRowSelected);
		this.fireEvent('rowUnfocus', [row, this._selectedRows]);
		return this;
	},

	selectAll: function(selectNone){
		if (!selectNone && !this.options.allowMultiSelect) return;
		this.selectRange(0, this.body.rows.length, selectNone);
		return this;
	},

	selectNone: function(){
		return this.selectAll(true);
	},

	selectRange: function(startRow, endRow, _deselect){
		if (!this.options.allowMultiSelect && !_deselect) return;
		var method = _deselect ? 'deselectRow' : 'selectRow',
		    rows = $A(this.body.rows);

		if ($type(startRow) == 'element') startRow = rows.indexOf(startRow);
		if ($type(endRow) == 'element') endRow = rows.indexOf(endRow);
		endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;

		if (endRow < startRow) {
			var tmp = startRow;
			startRow = endRow;
			endRow = tmp;
		}

		for(var i = startRow; i <= endRow; i++) this[method](rows[i], true);

		return this;
	},

	deselectRange: function(startRow, endRow){
		this.selectRange(startRow, endRow, true);
	},
/*
	Private methods:
*/

	_enterRow: function(row){
		if (this._hovered) this._hovered = this._leaveRow(this._hovered);
		this._hovered = row.addClass(this.options.classRowHovered);
	},

	_leaveRow: function(row){
		row.removeClass(this.options.classRowHovered);
	},

	_updateSelects: function(){
		Array.each(this.body.rows, function(row){
			var binders = row.retrieve('binders');
			if ((binders && this._selectEnabled) || (!binders && !this._selectEnabled)) return;
			if (!binders){
				binders = {
					mouseenter: this._enterRow.bind(this, [row]),
					mouseleave: this._leaveRow.bind(this, [row])
				};
				row.store('binders', binders).addEvents(binders);
			} else {
				row.removeEvents(binders);
			}
		}, this);
	},

	_shiftFocus: function(offset, event){
		if (!this._focused) return this.selectRow(this.body.rows[0], event);
		var to = this._getRowByOffset(offset);
		if (to === null || this._focused == this.body.rows[to]) return this;
		this.toggleRow(this.body.rows[to], event);
	},

	_clickRow: function(event, row){
		var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
		if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
		if (event.rightClick) this.selectRow(row);
		else this.toggleRow(row);
		if (event.shift) {
			this.selectRange(this._rangeStart || this.body.rows[0], row, this._rangeStart ? !this.isSelected(row) : true);
			this._focused = row;
		}
		this._rangeStart = row;
	},

	_getRowByOffset: function(offset){
		if (!this._focused) return 0;
		var index = Array.indexOf(this.body.rows, this._focused) + offset;
		if (index < 0) index = null;
		if (index >= this.body.rows.length) index = null;
		return index;
	},

	_attachSelects: function(attach){
		attach = $pick(attach, true);
		var method = attach ? 'addEvents' : 'removeEvents';
		this.element[method]({
			mouseleave: this._bound.mouseleave
		});
		this.body[method]({
			'click:relay(tr)': this._bound.clickRow,
			'contextmenu:relay(tr)': this._bound.clickRow
		});
		if (this.options.useKeyboard || this.keyboard){
			if (!this.keyboard) {
				var timer, held;
				var move = function(offset){
					var mover = function(e){
						$clear(timer);
						e.preventDefault();
						var to = this.body.rows[this._getRowByOffset(offset)];
						if (e.shift && to && this.isSelected(to)) {
							this.deselectRow(this._focused);
							this._focused = to;
						} else {
							if (to && (!this.options.allowMultiSelect || !e.shift)) {
								this.selectNone();
							}
							this._shiftFocus(offset, e);
						}
						if (held) {
							timer = mover.delay(100, this, e);
						} else {
							timer = (function(){
								held = true;
								mover(e);
							}).delay(400);
						}
					}.bind(this);
					return mover;
				}.bind(this);

				var clear = function(){
					$clear(timer);
					held = false;
				};

				this.keyboard = new Keyboard({
					events: {
						'keydown:shift+up': move(-1),
						'keydown:shift+down': move(1),
						'keyup:shift+up': clear,
						'keyup:shift+down': clear,
						'keyup:up': clear,
						'keyup:down': clear
					},
					active: true
				});

				var shiftHint = '';
				if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard) {
					shiftHint = " (Shift multi-selects).";
				}

				this.keyboard.addShortcuts({
					'Select Previous Row': {
						keys: 'up',
						shortcut: 'up arrow',
						handler: move(-1),
						description: 'Select the previous row in the table.' + shiftHint
					},
					'Select Next Row': {
						keys: 'down',
						shortcut: 'down arrow',
						handler: move(1),
						description: 'Select the next row in the table.' + shiftHint
					}
				});
			}
			this.keyboard[attach ? 'activate' : 'deactivate']();
		}
		this._updateSelects();
	},

	_mouseleave: function(){
		if (this._hovered) this._leaveRow(this._hovered);
	}

});

/*
---

script: Keyboard.js

name: Keyboard

description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.

license: MIT-style license

authors:
  - Perrin Westrich
  - Aaron Newton
  - Scott Kyle

requires:
  - Core/Events
  - Core/Options
  - Core/Element.Event
  - /Log

provides: [Keyboard]

...
*/

(function(){

	var Keyboard = this.Keyboard = new Class({

		Extends: Events,

		Implements: [Options, Log],

		options: {
			/*
			onActivate: $empty,
			onDeactivate: $empty,
			*/
			defaultEventType: 'keydown',
			active: false,
			manager: null,
			events: {},
			nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
		},

		initialize: function(options){
			if (options && options.manager) {
				this.manager = options.manager;
				delete options.manager;
			}
			this.setOptions(options);
			this.setup();
		},
		setup: function(){
			this.addEvents(this.options.events);
			//if this is the root manager, nothing manages it
			if (Keyboard.manager && !this.manager) Keyboard.manager.manage(this);
			if (this.options.active) this.activate();
		},

		handle: function(event, type){
			//Keyboard.stop(event) prevents key propagation
			if (event.preventKeyboardPropagation) return;

			var bubbles = !!this.manager;
			if (bubbles && this.activeKB){
				this.activeKB.handle(event, type);
				if (event.preventKeyboardPropagation) return;
			}
			this.fireEvent(type, event);

			if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
		},

		addEvent: function(type, fn, internal){
			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
		},

		removeEvent: function(type, fn){
			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
		},

		toggleActive: function(){
			return this[this.active ? 'deactivate' : 'activate']();
		},

		activate: function(instance){
			if (instance) {
				if (instance.isActive()) return this;
				//if we're stealing focus, store the last keyboard to have it so the relinquish command works
				if (this.activeKB && instance != this.activeKB) {
					this.previous = this.activeKB;
					this.previous.fireEvent('deactivate');
				}
				//if we're enabling a child, assign it so that events are now passed to it
				this.activeKB = instance.fireEvent('activate');
				Keyboard.manager.fireEvent('changed');
			} else if (this.manager) {
				//else we're enabling ourselves, we must ask our parent to do it for us
				this.manager.activate(this);
			}
			return this;
		},

		isActive: function(){
			return this.manager ? this.manager.activeKB == this :  Keyboard.manager == this;
		},

		deactivate: function(instance){
			if (instance) {
				if(instance === this.activeKB) {
					this.activeKB = null;
					instance.fireEvent('deactivate');
					Keyboard.manager.fireEvent('changed');
				}
			} else if (this.manager) {
				this.manager.deactivate(this);
			}
			return this;
		},

		relinquish: function(){
			if (this.isActive() && this.manager && this.manager.previous) this.manager.activate(this.manager.previous);
		},

		//management logic
		manage: function(instance){
			if (instance.manager && instance.manager != Keyboard.manager && this != Keyboard.manager) instance.manager.drop(instance);
			this.instances.push(instance);
			instance.manager = this;
			if (!this.activeKB) this.activate(instance);
		},

		_disable: function(instance){
			if (this.activeKB == instance) this.activeKB = null;
		},

		drop: function(instance){
			this._disable(instance);
			this.instances.erase(instance);
			Keyboard.manager.manage(instance);
			if (this.activeKB == instance && this.previous && this.instances.contains(this.previous)) this.activate(this.previous);
		},

		instances: [],

		trace: function(){
			Keyboard.trace(this);
		},

		each: function(fn){
			Keyboard.each(this, fn);
		}

	});

	var parsed = {};
	var modifiers = ['shift', 'control', 'alt', 'meta'];
	var regex = /^(?:shift|control|ctrl|alt|meta)$/;

	Keyboard.parse = function(type, eventType, ignore){
		if (ignore && ignore.contains(type.toLowerCase())) return type;

		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
			eventType = $1;
			return '';
		});

		if (!parsed[type]){
			var key, mods = {};
			type.split('+').each(function(part){
				if (regex.test(part)) mods[part] = true;
				else key = part;
			});

			mods.control = mods.control || mods.ctrl; // allow both control and ctrl

			var keys = [];
			modifiers.each(function(mod){
				if (mods[mod]) keys.push(mod);
			});

			if (key) keys.push(key);
			parsed[type] = keys.join('+');
		}

		return eventType + ':' + parsed[type];
	};

	Keyboard.each = function(keyboard, fn){
		var current = keyboard || Keyboard.manager;
		while (current){
			fn.run(current);
			current = current.activeKB;
		}
	};

	Keyboard.stop = function(event){
		event.preventKeyboardPropagation = true;
	};

	Keyboard.manager = new Keyboard({
		active: true
	});

	Keyboard.trace = function(keyboard){
		keyboard = keyboard || Keyboard.manager;
		keyboard.enableLog();
		keyboard.log('the following items have focus: ');
		Keyboard.each(keyboard, function(current){
			keyboard.log(document.id(current.widget) || current.wiget || current);
		});
	};

	var handler = function(event){
		var keys = [];
		modifiers.each(function(mod){
			if (event[mod]) keys.push(mod);
		});

		if (!regex.test(event.key)) keys.push(event.key);
		Keyboard.manager.handle(event, event.type + ':' + keys.join('+'));
	};

	document.addEvents({
		'keyup': handler,
		'keydown': handler
	});

	Event.Keys.extend({
		'shift': 16,
		'control': 17,
		'alt': 18,
		'capslock': 20,
		'pageup': 33,
		'pagedown': 34,
		'end': 35,
		'home': 36,
		'numlock': 144,
		'scrolllock': 145,
		';': 186,
		'=': 187,
		',': 188,
		'-': Browser.Engine.gecko ? 109 : 189,
		'.': 190,
		'/': 191,
		'`': 192,
		'[': 219,
		'\\': 220,
		']': 221,
		"'": 222
	});

})();


/*
---

script: Keyboard.Extras.js

name: Keyboard.Extras

description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.

license: MIT-style license

authors:
  - Perrin Westrich

requires:
  - /Keyboard
  - /MooTools.More

provides: [Keyboard.Extras]

...
*/
Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);

Keyboard.implement({

	/*
		shortcut should be in the format of:
		{
			'keys': 'shift+s', // the default to add as an event.
			'description': 'blah blah blah', // a brief description of the functionality.
			'handler': function(){} // the event handler to run when keys are pressed.
		}
	*/
	addShortcut: function(name, shortcut) {
		this.shortcuts = this.shortcuts || [];
		this.shortcutIndex = this.shortcutIndex || {};

		shortcut.getKeyboard = $lambda(this);
		shortcut.name = name;
		this.shortcutIndex[name] = shortcut;
		this.shortcuts.push(shortcut);
		if(shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
		return this;
	},

	addShortcuts: function(obj){
		for(var name in obj) this.addShortcut(name, obj[name]);
		return this;
	},

	removeShortcut: function(name) {
		var shortcut = this.getShortcut(name);
		if (shortcut && shortcut.keys) {
			this.removeEvent(shortcut.keys, shortcut.handler);
			delete this.shortcutIndex[name];
			this.shortcuts.erase(shortcut);
		}
		return this;
	},

	removeShortcuts: function(names) {
		names.each(this.removeShortcut, this);
		return this;
	},

	getShortcuts: function(){
		return this.shortcuts || [];
	},

	getShortcut: function(name){
		return (this.shortcutIndex || {})[name];
	}

});

Keyboard.rebind = function(newKeys, shortcuts){
	$splat(shortcuts).each(function(shortcut){
		shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
		shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
		shortcut.keys = newKeys;
		shortcut.getKeyboard().fireEvent('rebound');
	});
};


Keyboard.getActiveShortcuts = function(keyboard) {
	var activeKBS = [], activeSCS = [];
	Keyboard.each(keyboard, [].push.bind(activeKBS));
	activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
	return activeSCS;
};

Keyboard.getShortcut = function(name, keyboard, opts){
	opts = opts || {};
	var shortcuts = opts.many ? [] : null,
		set = opts.many ? function(kb){
				var shortcut = kb.getShortcut(name);
				if(shortcut) shortcuts.push(shortcut);
			} : function(kb) {
				if(!shortcuts) shortcuts = kb.getShortcut(name);
			};
	Keyboard.each(keyboard, set);
	return shortcuts;
};

Keyboard.getShortcuts = function(name, keyboard) {
	return Keyboard.getShortcut(name, keyboard, { many: true });
};


/*
---

script: Mask.js

name: Mask

description: Creates a mask element to cover another.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Options
  - Core/Events
  - Core/Element.Event
  - /Class.Binds
  - /Element.Position
  - /IframeShim

provides: [Mask]

...
*/

var Mask = new Class({

	Implements: [Options, Events],

	Binds: ['position'],

	options: {
		// onShow: $empty,
		// onHide: $empty,
		// onDestroy: $empty,
		// onClick: $empty,
		//inject: {
		//  where: 'after',
		//  target: null,
		//},
		// hideOnClick: false,
		// id: null,
		// destroyOnHide: false,
		style: {},
		'class': 'mask',
		maskMargins: false,
		useIframeShim: true,
		iframeShimOptions: {}
	},

	initialize: function(target, options){
		this.target = document.id(target) || document.id(document.body);
		this.target.store('Mask', this);
		this.setOptions(options);
		this.render();
		this.inject();
	},

	render: function() {
		this.element = new Element('div', {
			'class': this.options['class'],
			id: this.options.id || 'mask-' + $time(),
			styles: $merge(this.options.style, {
				display: 'none'
			}),
			events: {
				click: function(){
					this.fireEvent('click');
					if (this.options.hideOnClick) this.hide();
				}.bind(this)
			}
		});
		this.hidden = true;
	},

	toElement: function(){
		return this.element;
	},

	inject: function(target, where){
		where = where || this.options.inject ? this.options.inject.where : '' || this.target == document.body ? 'inside' : 'after';
		target = target || this.options.inject ? this.options.inject.target : '' || this.target;
		this.element.inject(target, where);
		if (this.options.useIframeShim) {
			this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
			this.addEvents({
				show: this.shim.show.bind(this.shim),
				hide: this.shim.hide.bind(this.shim),
				destroy: this.shim.destroy.bind(this.shim)
			});
		}
	},

	position: function(){
		this.resize(this.options.width, this.options.height);
		this.element.position({
			relativeTo: this.target,
			position: 'topLeft',
			ignoreMargins: !this.options.maskMargins,
			ignoreScroll: this.target == document.body
		});
		return this;
	},

	resize: function(x, y){
		var opt = {
			styles: ['padding', 'border']
		};
		if (this.options.maskMargins) opt.styles.push('margin');
		var dim = this.target.getComputedSize(opt);
		if (this.target == document.body) {
			var win = window.getScrollSize();
			if (dim.totalHeight < win.y) dim.totalHeight = win.y;
			if (dim.totalWidth < win.x) dim.totalWidth = win.x;
		}
		this.element.setStyles({
			width: $pick(x, dim.totalWidth, dim.x),
			height: $pick(y, dim.totalHeight, dim.y)
		});
		return this;
	},

	show: function(){
		if (!this.hidden) return this;
		window.addEvent('resize', this.position);
		this.position();
		this.showMask.apply(this, arguments);
		return this;
	},

	showMask: function(){
		this.element.setStyle('display', 'block');
		this.hidden = false;
		this.fireEvent('show');
	},

	hide: function(){
		if (this.hidden) return this;
		window.removeEvent('resize', this.position);
		this.hideMask.apply(this, arguments);
		if (this.options.destroyOnHide) return this.destroy();
		return this;
	},

	hideMask: function(){
		this.element.setStyle('display', 'none');
		this.hidden = true;
		this.fireEvent('hide');
	},

	toggle: function(){
		this[this.hidden ? 'show' : 'hide']();
	},

	destroy: function(){
		this.hide();
		this.element.destroy();
		this.fireEvent('destroy');
		this.target.eliminate('mask');
	}

});

Element.Properties.mask = {

	set: function(options){
		var mask = this.retrieve('mask');
		return this.eliminate('mask').store('mask:options', options);
	},

	get: function(options){
		if (options || !this.retrieve('mask')){
			if (this.retrieve('mask')) this.retrieve('mask').destroy();
			if (options || !this.retrieve('mask:options')) this.set('mask', options);
			this.store('mask', new Mask(this, this.retrieve('mask:options')));
		}
		return this.retrieve('mask');
	}

};

Element.implement({

	mask: function(options){
		this.get('mask', options).show();
		return this;
	},

	unmask: function(){
		this.get('mask').hide();
		return this;
	}

});

/*
---

script: Scroller.js

name: Scroller

description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Events
  - Core/Options
  - Core/Element.Event
  - Core/Element.Dimensions

provides: [Scroller]

...
*/

var Scroller = new Class({

	Implements: [Events, Options],

	options: {
		area: 20,
		velocity: 1,
		onChange: function(x, y){
			this.element.scrollTo(x, y);
		},
		fps: 50
	},

	initialize: function(element, options){
		this.setOptions(options);
		this.element = document.id(element);
		this.docBody = document.id(this.element.getDocument().body);
		this.listener = ($type(this.element) != 'element') ?  this.docBody : this.element;
		this.timer = null;
		this.bound = {
			attach: this.attach.bind(this),
			detach: this.detach.bind(this),
			getCoords: this.getCoords.bind(this)
		};
	},

	start: function(){
		this.listener.addEvents({
			mouseenter: this.bound.attach,
			mouseleave: this.bound.detach
		});
	},

	stop: function(){
		this.listener.removeEvents({
			mouseenter: this.bound.attach,
			mouseleave: this.bound.detach
		});
		this.detach();
		this.timer = $clear(this.timer);
	},

	attach: function(){
		this.listener.addEvent('mousemove', this.bound.getCoords);
	},

	detach: function(){
		this.listener.removeEvent('mousemove', this.bound.getCoords);
		this.timer = $clear(this.timer);
	},

	getCoords: function(event){
		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
		if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
	},

	scroll: function(){
		var size = this.element.getSize(),
			scroll = this.element.getScroll(),
			pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0},
			scrollSize = this.element.getScrollSize(),
			change = {x: 0, y: 0},
			top = this.options.area.top || this.options.area,
		  bottom = this.options.area.bottom || this.options.area;
		for (var z in this.page){
			if (this.page[z] < (top + pos[z]) && scroll[z] != 0) {
				change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
			} else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]) {
				change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
			}
			change[z] = change[z].round();
		}
		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
	}

});


/*
---

script: Tips.js

name: Tips

description: Class for creating nice tips that follow the mouse cursor when hovering an element.

license: MIT-style license

authors:
  - Valerio Proietti
  - Christoph Pojer

requires:
  - Core/Options
  - Core/Events
  - Core/Element.Event
  - Core/Element.Style
  - Core/Element.Dimensions
  - /MooTools.More

provides: [Tips]

...
*/

(function(){

var read = function(option, element){
	return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
};

this.Tips = new Class({

	Implements: [Events, Options],

	options: {
		/*
		onAttach: $empty(element),
		onDetach: $empty(element),
		*/
		onShow: function(){
			this.tip.setStyle('display', 'block');
		},
		onHide: function(){
			this.tip.setStyle('display', 'none');
		},
		title: 'title',
		text: function(element){
			return element.get('rel') || element.get('href');
		},
		showDelay: 100,
		hideDelay: 100,
		className: 'tip-wrap',
		offset: {x: 16, y: 16},
		windowPadding: {x:0, y:0},
		fixed: false
	},

	initialize: function(){
		var params = Array.link(arguments, {options: Object.type, elements: $defined});
		this.setOptions(params.options);
		if (params.elements) this.attach(params.elements);
		this.container = new Element('div', {'class': 'tip'});
	},

	toElement: function(){
		if (this.tip) return this.tip;

		return this.tip = new Element('div', {
			'class': this.options.className,
			styles: {
				position: 'absolute',
				top: 0,
				left: 0
			}
		}).adopt(
			new Element('div', {'class': 'tip-top'}),
			this.container,
			new Element('div', {'class': 'tip-bottom'})
		);
	},

	attach: function(elements){
		$$(elements).each(function(element){
			var title = read(this.options.title, element),
				text = read(this.options.text, element);

			element.erase('title').store('tip:native', title).retrieve('tip:title', title);
			element.retrieve('tip:text', text);
			this.fireEvent('attach', [element]);

			var events = ['enter', 'leave'];
			if (!this.options.fixed) events.push('move');

			events.each(function(value){
				var event = element.retrieve('tip:' + value);
				if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);

				element.store('tip:' + value, event).addEvent('mouse' + value, event);
			}, this);
		}, this);

		return this;
	},

	detach: function(elements){
		$$(elements).each(function(element){
			['enter', 'leave', 'move'].each(function(value){
				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
			});

			this.fireEvent('detach', [element]);

			if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
				var original = element.retrieve('tip:native');
				if (original) element.set('title', original);
			}
		}, this);

		return this;
	},

	elementEnter: function(event, element){
		this.container.empty();

		['title', 'text'].each(function(value){
			var content = element.retrieve('tip:' + value);
			if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
		}, this);

		$clear(this.timer);
		this.timer = (function(){
			this.show(element);
			this.position((this.options.fixed) ? {page: element.getPosition()} : event);
		}).delay(this.options.showDelay, this);
	},

	elementLeave: function(event, element){
		$clear(this.timer);
		this.timer = this.hide.delay(this.options.hideDelay, this, element);
		this.fireForParent(event, element);
	},

	fireForParent: function(event, element){
		element = element.getParent();
		if (!element || element == document.body) return;
		if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
		else this.fireForParent(event, element);
	},

	elementMove: function(event, element){
		this.position(event);
	},

	position: function(event){
		if (!this.tip) document.id(this);

		var size = window.getSize(), scroll = window.getScroll(),
			tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
			props = {x: 'left', y: 'top'},
			obj = {};

		for (var z in props){
			obj[props[z]] = event.page[z] + this.options.offset[z];
			if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
		}

		this.tip.setStyles(obj);
	},

	fill: function(element, contents){
		if(typeof contents == 'string') element.set('html', contents);
		else element.adopt(contents);
	},

	show: function(element){
		if (!this.tip) document.id(this);
		if (!this.tip.getParent()) this.tip.inject(document.body);
		this.fireEvent('show', [this.tip, element]);
	},

	hide: function(element){
		if (!this.tip) document.id(this);
		this.fireEvent('hide', [this.tip, element]);
	}

});

})();


/*
---

script: Spinner.js

name: Spinner

description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - Core/Fx.Tween
  - Core/Request
  - /Class.refactor
  - /Mask

provides: [Spinner]

...
*/

var Spinner = new Class({

	Extends: Mask,

	options: {
		/*message: false,*/
		'class':'spinner',
		containerPosition: {},
		content: {
			'class':'spinner-content'
		},
		messageContainer: {
			'class':'spinner-msg'
		},
		img: {
			'class':'spinner-img'
		},
		fxOptions: {
			link: 'chain'
		}
	},

	initialize: function(){
		this.parent.apply(this, arguments);
		this.target.store('spinner', this);

		//add this to events for when noFx is true; parent methods handle hide/show
		var deactivate = function(){ this.active = false; }.bind(this);
		this.addEvents({
			hide: deactivate,
			show: deactivate
		});
	},

	render: function(){
		this.parent();
		this.element.set('id', this.options.id || 'spinner-'+$time());
		this.content = document.id(this.options.content) || new Element('div', this.options.content);
		this.content.inject(this.element);
		if (this.options.message) {
			this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
			this.msg.inject(this.content);
		}
		if (this.options.img) {
			this.img = document.id(this.options.img) || new Element('div', this.options.img);
			this.img.inject(this.content);
		}
		this.element.set('tween', this.options.fxOptions);
	},

	show: function(noFx){
		if (this.active) return this.chain(this.show.bind(this));
		if (!this.hidden) {
			this.callChain.delay(20, this);
			return this;
		}
		this.active = true;
		return this.parent(noFx);
	},

	showMask: function(noFx){
		var pos = function(){
			this.content.position($merge({
				relativeTo: this.element
			}, this.options.containerPosition));
		}.bind(this);
		if (noFx) {
			this.parent();
			pos();
		} else {
			this.element.setStyles({
				display: 'block',
				opacity: 0
			}).tween('opacity', this.options.style.opacity || 0.9);
			pos();
			this.hidden = false;
			this.fireEvent('show');
			this.callChain();
		}
	},

	hide: function(noFx){
		if (this.active) return this.chain(this.hide.bind(this));
		if (this.hidden) {
			this.callChain.delay(20, this);
			return this;
		}
		this.active = true;
		return this.parent(noFx);
	},

	hideMask: function(noFx){
		if (noFx) return this.parent();
		this.element.tween('opacity', 0).get('tween').chain(function(){
			this.element.setStyle('display', 'none');
			this.hidden = true;
			this.fireEvent('hide');
			this.callChain();
		}.bind(this));
	},

	destroy: function(){
		this.content.destroy();
		this.parent();
		this.target.eliminate('spinner');
	}

});

Spinner.implement(new Chain);

Request = Class.refactor(Request, {

	options: {
		useSpinner: false,
		spinnerOptions: {},
		spinnerTarget: false
	},

	initialize: function(options){
		this._send = this.send;
		this.send = function(options){
			var spinner = this.getSpinner();
			if (spinner) spinner.chain(this._send.bind(this, options)).show();
			else this._send(options);
			return this;
		};
		this.previous(options);
	},

	getSpinner: function(){
		if (!this.spinner) {
			var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
			if (this.options.useSpinner && update) {
				this.spinner = update.get('spinner', this.options.spinnerOptions);
				['onComplete', 'onException', 'onCancel'].each(function(event){
					this.addEvent(event, this.spinner.hide.bind(this.spinner));
				}, this);
			}
		}
		return this.spinner;
	}

});

Element.Properties.spinner = {

	set: function(options){
		var spinner = this.retrieve('spinner');
		return this.eliminate('spinner').store('spinner:options', options);
	},

	get: function(options){
		if (options || !this.retrieve('spinner')){
			if (this.retrieve('spinner')) this.retrieve('spinner').destroy();
			if (options || !this.retrieve('spinner:options')) this.set('spinner', options);
			new Spinner(this, this.retrieve('spinner:options'));
		}
		return this.retrieve('spinner');
	}

};

Element.implement({

	spin: function(options){
		this.get('spinner', options).show();
		return this;
	},

	unspin: function(){
		var opt = Array.link(arguments, {options: Object.type, callback: Function.type});
		this.get('spinner', opt.options).hide(opt.callback);
		return this;
	}

});

/*
---

script: Date.English.US.js

name: Date.English.US

description: Date messages for US English.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - /Lang

provides: [Date.English.US]

...
*/

MooTools.lang.set('en-US', 'Date', {

	months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],

	// Culture's date order: MM/DD/YYYY
	dateOrder: ['month', 'date', 'year'],
	shortDate: '%m/%d/%Y',
	shortTime: '%I:%M%p',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: function(dayOfMonth){
		// 1st, 2nd, 3rd, etc.
		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
	},

	lessThanMinuteAgo: 'less than a minute ago',
	minuteAgo: 'about a minute ago',
	minutesAgo: '{delta} minutes ago',
	hourAgo: 'about an hour ago',
	hoursAgo: 'about {delta} hours ago',
	dayAgo: '1 day ago',
	daysAgo: '{delta} days ago',
	weekAgo: '1 week ago',
	weeksAgo: '{delta} weeks ago',
	monthAgo: '1 month ago',
	monthsAgo: '{delta} months ago',
	yearAgo: '1 year ago',
	yearsAgo: '{delta} years ago',

	lessThanMinuteUntil: 'less than a minute from now',
	minuteUntil: 'about a minute from now',
	minutesUntil: '{delta} minutes from now',
	hourUntil: 'about an hour from now',
	hoursUntil: 'about {delta} hours from now',
	dayUntil: '1 day from now',
	daysUntil: '{delta} days from now',
	weekUntil: '1 week from now',
	weeksUntil: '{delta} weeks from now',
	monthUntil: '1 month from now',
	monthsUntil: '{delta} months from now',
	yearUntil: '1 year from now',
	yearsUntil: '{delta} years from now'

});


/*
---

script: Form.Validator.English.js

name: Form.Validator.English

description: Form Validator messages for English.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - /Lang

provides: [Form.Validator.English]

...
*/

MooTools.lang.set('en-US', 'Form.Validator', {

	required: 'This field is required.',
	minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
	maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
	integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
	numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
	digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
	alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
	alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
	dateSuchAs: 'Please enter a valid date such as {date}',
	dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
	email: 'Please enter a valid email address. For example "fred@domain.com".',
	url: 'Please enter a valid URL such as http://www.google.com.',
	currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
	oneRequired: 'Please enter something for at least one of these inputs.',
	errorPrefix: 'Error: ',
	warningPrefix: 'Warning: ',

	// Form.Validator.Extras
	noSpace: 'There can be no spaces in this input.',
	reqChkByNode: 'No items are selected.',
	requiredChk: 'This field is required.',
	reqChkByName: 'Please select a {label}.',
	match: 'This field needs to match the {matchName} field',
	startDate: 'the start date',
	endDate: 'the end date',
	currendDate: 'the current date',
	afterDate: 'The date should be the same or after {label}.',
	beforeDate: 'The date should be the same or before {label}.',
	startMonth: 'Please select a start month',
	sameMonth: 'These two dates must be in the same month - you must change one or the other.',
	creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'

});


/*
---

script: Date.Catalan.js

name: Date.Catalan

description: Date messages for Catalan.

license: MIT-style license

authors:
  - Ãlfons Sanchez

requires:
  - /Lang

provides: [Date.Catalan]

...
*/

MooTools.lang.set('ca-CA', 'Date', {

	months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
	days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],

	// Culture's date order: DD/MM/YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d/%m/%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: '',

	lessThanMinuteAgo: 'fa menys d`un minut',
	minuteAgo: 'fa un minut',
	minutesAgo: 'fa {delta} minuts',
	hourAgo: 'fa un hora',
	hoursAgo: 'fa unes {delta} hores',
	dayAgo: 'fa un dia',
	daysAgo: 'fa {delta} dies',

	lessThanMinuteUntil: 'menys d`un minut des d`ara',
	minuteUntil: 'un minut des d`ara',
	minutesUntil: '{delta} minuts des d`ara',
	hourUntil: 'un hora des d`ara',
	hoursUntil: 'unes {delta} hores des d`ara',
	dayUntil: '1 dia des d`ara',
	daysUntil: '{delta} dies des d`ara'

});


/*
---

script: Date.Czech.js

name: Date.Czech

description: Date messages for Czech.

license: MIT-style license

authors:
  - Jan Černý chemiX
  - Christopher Zukowski

requires:
  - /Lang

provides: [Date.Czech]

...
*/
(function(){

// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
// one -> n is 1;            1
// few -> n in 2..4;         2-4
// other -> everything else  0, 5-999, 1.31, 2.31, 5.31...
var pluralize = function (n, one, few, other) {
	if (n == 1) return one;
	else if (n == 2 || n == 3 || n == 4) return few;
	else return other;
};

MooTools.lang.set('cs-CZ', 'Date', {

	months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
	days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],

	// Culture's date order: DD.MM.YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d.%m.%Y',
	shortTime: '%H:%M',
	AM: 'dop.',
	PM: 'odp.',

	// Date.Extras
	ordinal: '.',

	lessThanMinuteAgo: 'před chvílí',
	minuteAgo: 'přibližně před minutou',
	minutesAgo: function(delta) { return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
	hourAgo: 'přibližně před hodinou',
	hoursAgo: function(delta) { return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
	dayAgo: 'před dnem',
	daysAgo: function(delta) { return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
	weekAgo: 'před týdnem',
	weeksAgo: function(delta) { return 'před {delta} ' + pluralize(delta, 'týdnem', 'týdny', 'týdny'); },
	monthAgo: 'před měsícem',
	monthsAgo: function(delta) { return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
	yearAgo: 'před rokem',
	yearsAgo: function(delta) { return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },

	lessThanMinuteUntil: 'za chvíli',
	minuteUntil: 'přibližně za minutu',
	minutesUntil: function(delta) { return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
	hourUntil: 'přibližně za hodinu',
	hoursUntil: function(delta) { return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
	dayUntil: 'za den',
	daysUntil: function(delta) { return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
	weekUntil: 'za týden',
	weeksUntil: function(delta) { return 'za {delta} ' + pluralize(delta, 'týden', 'týdny', 'týdnů'); },
	monthUntil: 'za měsíc',
	monthsUntil: function(delta) { return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
	yearUntil: 'za rok',
	yearsUntil: function(delta) { return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
});

})();


/*
---

script: Date.Danish.js

name: Date.Danish

description: Date messages for Danish.

license: MIT-style license

authors:
  - Martin Overgaard
  - Henrik Hansen

requires:
  - /Lang

provides: [Date.Danish]

...
*/

MooTools.lang.set('da-DK', 'Date', {

	months: ['Januar', 'Februa', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
	days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],

	// Culture's date order: DD-MM-YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d-%m-%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: '.',

	lessThanMinuteAgo: 'mindre end et minut siden',
	minuteAgo: 'omkring et minut siden',
	minutesAgo: '{delta} minutter siden',
	hourAgo: 'omkring en time siden',
	hoursAgo: 'omkring {delta} timer siden',
	dayAgo: '1 dag siden',
	daysAgo: '{delta} dage siden',
	weekAgo: '1 uge siden',
	weeksAgo: '{delta} uger siden',
	monthAgo: '1 måned siden',
	monthsAgo: '{delta} måneder siden',
	yearAgo: '1 år siden',
	yearsAgo: '{delta} år siden',

	lessThanMinuteUntil: 'mindre end et minut fra nu',
	minuteUntil: 'omkring et minut fra nu',
	minutesUntil: '{delta} minutter fra nu',
	hourUntil: 'omkring en time fra nu',
	hoursUntil: 'omkring {delta} timer fra nu',
	dayUntil: '1 dag fra nu',
	daysUntil: '{delta} dage fra nu',
	weekUntil: '1 uge fra nu',
	weeksUntil: '{delta} uger fra nu',
	monthUntil: '1 måned fra nu',
	monthsUntil: '{delta} måneder fra nu',
	yearUntil: '1 år fra nu',
	yearsUntil: '{delta} år fra nu'

});


/*
---

script: Date.Dutch.js

name: Date.Dutch

description: Date messages for Dutch.

license: MIT-style license

authors:
  - Lennart Pilon
  - Tim Wienk

requires:
  - /Lang

provides: [Date.Dutch]

...
*/

MooTools.lang.set('nl-NL', 'Date', {

	months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
	days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],

	// Culture's date order: DD-MM-YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d-%m-%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: 'e',

	lessThanMinuteAgo: 'minder dan een minuut geleden',
	minuteAgo: 'ongeveer een minuut geleden',
	minutesAgo: '{delta} minuten geleden',
	hourAgo: 'ongeveer een uur geleden',
	hoursAgo: 'ongeveer {delta} uur geleden',
	dayAgo: 'een dag geleden',
	daysAgo: '{delta} dagen geleden',
	weekAgo: 'een week geleden',
	weeksAgo: '{delta} weken geleden',
	monthAgo: 'een maand geleden',
	monthsAgo: '{delta} maanden geleden',
	yearAgo: 'een jaar geleden',
	yearsAgo: '{delta} jaar geleden',

	lessThanMinuteUntil: 'over minder dan een minuut',
	minuteUntil: 'over ongeveer een minuut',
	minutesUntil: 'over {delta} minuten',
	hourUntil: 'over ongeveer een uur',
	hoursUntil: 'over {delta} uur',
	dayUntil: 'over ongeveer een dag',
	daysUntil: 'over {delta} dagen',
	weekUntil: 'over een week',
	weeksUntil: 'over {delta} weken',
	monthUntil: 'over een maand',
	monthsUntil: 'over {delta} maanden',
	yearUntil: 'over een jaar',
	yearsUntil: 'over {delta} jaar'

});


/*
---

script: Date.English.GB.js

name: Date.English.GB

description: Date messages for British English.

license: MIT-style license

authors:
  - Aaron Newton

requires:
  - /Lang

provides: [Date.English.GB]

...
*/

MooTools.lang.set('en-GB', 'Date', {

	// Culture's date order: DD/MM/YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d/%m/%Y',
	shortTime: '%H:%M'

}).set('cascade', ['en-US']);


/*
---

script: Date.Estonian.js

name: Date.Estonian

description: Date messages for Estonian.

license: MIT-style license

authors:
  - Kevin Valdek

requires:
  - /Lang

provides: [Date.Estonian]

...
*/

MooTools.lang.set('et-EE', 'Date', {

	months: ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
	days: ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'],

	// Culture's date order: MM.DD.YYYY
	dateOrder: ['month', 'date', 'year'],
	shortDate: '%m.%d.%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: '',

	lessThanMinuteAgo: 'vähem kui minut aega tagasi',
	minuteAgo: 'umbes minut aega tagasi',
	minutesAgo: '{delta} minutit tagasi',
	hourAgo: 'umbes tund aega tagasi',
	hoursAgo: 'umbes {delta} tundi tagasi',
	dayAgo: '1 päev tagasi',
	daysAgo: '{delta} päeva tagasi',
	weekAgo: '1 nädal tagasi',
	weeksAgo: '{delta} nädalat tagasi',
	monthAgo: '1 kuu tagasi',
	monthsAgo: '{delta} kuud tagasi',
	yearAgo: '1 aasta tagasi',
	yearsAgo: '{delta} aastat tagasi',

	lessThanMinuteUntil: 'vähem kui minuti aja pärast',
	minuteUntil: 'umbes minuti aja pärast',
	minutesUntil: '{delta} minuti pärast',
	hourUntil: 'umbes tunni aja pärast',
	hoursUntil: 'umbes {delta} tunni pärast',
	dayUntil: '1 päeva pärast',
	daysUntil: '{delta} päeva pärast',
	weekUntil: '1 nädala pärast',
	weeksUntil: '{delta} nädala pärast',
	monthUntil: '1 kuu pärast',
	monthsUntil: '{delta} kuu pärast',
	yearUntil: '1 aasta pärast',
	yearsUntil: '{delta} aasta pärast'

});


/*
---

script: Date.German.js

name: Date.German

description: Date messages for German.

license: MIT-style license

authors:
  - Christoph Pojer
  - Frank Rossi
  - Ulrich Petri
  - Fabian Beiner

requires:
  - /Lang

provides: [Date.German]

...
*/

MooTools.lang.set('de-DE', 'Date', {

	months: ['Januar', 'Februar', 'M&auml;rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
	days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],

	// Culture's date order: DD.MM.YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d.%m.%Y',
	shortTime: '%H:%M',
	AM: 'vormittags',
	PM: 'nachmittags',

	// Date.Extras
	ordinal: '.',

	lessThanMinuteAgo: 'Vor weniger als einer Minute',
	minuteAgo: 'Vor einer Minute',
	minutesAgo: 'Vor {delta} Minuten',
	hourAgo: 'Vor einer Stunde',
	hoursAgo: 'Vor {delta} Stunden',
	dayAgo: 'Vor einem Tag',
	daysAgo: 'Vor {delta} Tagen',
	weekAgo: 'Vor einer Woche',
	weeksAgo: 'Vor {delta} Wochen',
	monthAgo: 'Vor einem Monat',
	monthsAgo: 'Vor {delta} Monaten',
	yearAgo: 'Vor einem Jahr',
	yearsAgo: 'Vor {delta} Jahren',

	lessThanMinuteUntil: 'In weniger als einer Minute',
	minuteUntil: 'In einer Minute',
	minutesUntil: 'In {delta} Minuten',
	hourUntil: 'In ca. einer Stunde',
	hoursUntil: 'In ca. {delta} Stunden',
	dayUntil: 'In einem Tag',
	daysUntil: 'In {delta} Tagen',
	weekUntil: 'In einer Woche',
	weeksUntil: 'In {delta} Wochen',
	monthUntil: 'In einem Monat',
	monthsUntil: 'In {delta} Monaten',
	yearUntil: 'In einem Jahr',
	yearsUntil: 'In {delta} Jahren'

});


/*
---

script: Date.German.CH.js

name: Date.German.CH

description: Date messages for German (Switzerland).

license: MIT-style license

authors:
  - Michael van der Weg

requires:
  - /Lang
  - /Date.German

provides: [Date.German.CH]

...
*/

MooTools.lang.set('de-CH', 'cascade', ['de-DE']);


/*
---

script: Date.French.js

name: Date.French

description: Date messages for French.

license: MIT-style license

authors:
  - Nicolas Sorosac
  - Antoine Abt

requires:
  - /Lang

provides: [Date.French]

...
*/

MooTools.lang.set('fr-FR', 'Date', {

	months: ['Janvier', 'F&eacute;vrier', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Ao&ucirc;t', 'Septembre', 'Octobre', 'Novembre', 'D&eacute;cembre'],
	days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],

	// Culture's date order: DD/MM/YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d/%m/%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: function(dayOfMonth){
		return (dayOfMonth > 1) ? '' : 'er';
	},

	lessThanMinuteAgo: "il y a moins d'une minute",
	minuteAgo: 'il y a une minute',
	minutesAgo: 'il y a {delta} minutes',
	hourAgo: 'il y a une heure',
	hoursAgo: 'il y a {delta} heures',
	dayAgo: 'il y a un jour',
	daysAgo: 'il y a {delta} jours',
	weekAgo: 'il y a une semaine',
	weeksAgo: 'il y a {delta} semaines',
	monthAgo: 'il y a 1 mois',
	monthsAgo: 'il y a {delta} mois',
	yearthAgo: 'il y a 1 an',
	yearsAgo: 'il y a {delta} ans',

	lessThanMinuteUntil: "dans moins d'une minute",
	minuteUntil: 'dans une minute',
	minutesUntil: 'dans {delta} minutes',
	hourUntil: 'dans une heure',
	hoursUntil: 'dans {delta} heures',
	dayUntil: 'dans un jour',
	daysUntil: 'dans {delta} jours',
	weekUntil: 'dans 1 semaine',
	weeksUntil: 'dans {delta} semaines',
	monthUntil: 'dans 1 mois',
	monthsUntil: 'dans {delta} mois',
	yearUntil: 'dans 1 an',
	yearsUntil: 'dans {delta} ans'

});


/*
---

script: Date.Italian.js

name: Date.Italian

description: Date messages for Italian.

license: MIT-style license.

authors:
  - Andrea Novero
  - Valerio Proietti

requires:
  - /Lang

provides: [Date.Italian]

...
*/

MooTools.lang.set('it-IT', 'Date', {

	months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
	days: ['Domenica', 'Luned&igrave;', 'Marted&igrave;', 'Mercoled&igrave;', 'Gioved&igrave;', 'Venerd&igrave;', 'Sabato'],

	// Culture's date order: DD/MM/YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d/%m/%Y',
	shortTime: '%H.%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: '&ordm;',

	lessThanMinuteAgo: 'meno di un minuto fa',
	minuteAgo: 'circa un minuto fa',
	minutesAgo: 'circa {delta} minuti fa',
	hourAgo: "circa un'ora fa",
	hoursAgo: 'circa {delta} ore fa',
	dayAgo: 'circa 1 giorno fa',
	daysAgo: 'circa {delta} giorni fa',

	lessThanMinuteUntil: 'tra meno di un minuto',
	minuteUntil: 'tra circa un minuto',
	minutesUntil: 'tra circa {delta} minuti',
	hourUntil: "tra circa un'ora",
	hoursUntil: 'tra circa {delta} ore',
	dayUntil: 'tra circa un giorno',
	daysUntil: 'tra circa {delta} giorni'

});


/*
---

script: Date.Norwegian.js

name: Date.Norwegian

description: Date messages for Norwegian.

license: MIT-style license

authors:
  - Espen 'Rexxars' Hovlandsdal

requires:
  - /Lang

provides: [Date.Norwegian]

...
*/

MooTools.lang.set('no-NO', 'Date', {

	// Culture's date order: DD.MM.YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d.%m.%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	lessThanMinuteAgo: 'kortere enn et minutt siden',
	minuteAgo: 'omtrent et minutt siden',
	minutesAgo: '{delta} minutter siden',
	hourAgo: 'omtrent en time siden',
	hoursAgo: 'omtrent {delta} timer siden',
	dayAgo: '{delta} dag siden',
	daysAgo: '{delta} dager siden'

});


/*
---

script: Date.Polish.js

name: Date.Polish

description: Date messages for Polish.

license: MIT-style license

authors:
  - Oskar Krawczyk

requires:
  - /Lang

provides: [Date.Polish]

...
*/

MooTools.lang.set('pl-PL', 'Date', {

	months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
	days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],

	// Culture's date order: YYYY-MM-DD
	dateOrder: ['year', 'month', 'date'],
	shortDate: '%Y-%m-%d',
	shortTime: '%H:%M',
	AM: 'nad ranem',
	PM: 'po południu',

	// Date.Extras
	ordinal: function(dayOfMonth){
		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
	},

	lessThanMinuteAgo: 'mniej niż minute temu',
	minuteAgo: 'około minutę temu',
	minutesAgo: '{delta} minut temu',
	hourAgo: 'około godzinę temu',
	hoursAgo: 'około {delta} godzin temu',
	dayAgo: 'Wczoraj',
	daysAgo: '{delta} dni temu',

	lessThanMinuteUntil: 'za niecałą minutę',
	minuteUntil: 'za około minutę',
	minutesUntil: 'za {delta} minut',
	hourUntil: 'za około godzinę',
	hoursUntil: 'za około {delta} godzin',
	dayUntil: 'za 1 dzień',
	daysUntil: 'za {delta} dni'

});


/*
---

script: Date.Portuguese.BR.js

name: Date.Portuguese.BR

description: Date messages for Portuguese (Brazil).

license: MIT-style license

authors:
  - Fabio Miranda Costa

requires:
  - /Lang

provides: [Date.Portuguese.BR]

...
*/

MooTools.lang.set('pt-BR', 'Date', {

	months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
	days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],

	// Culture's date order: DD/MM/YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d/%m/%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: '&ordm;',

	lessThanMinuteAgo: 'há menos de um minuto',
	minuteAgo: 'há cerca de um minuto',
	minutesAgo: 'há {delta} minutos',
	hourAgo: 'há cerca de uma hora',
	hoursAgo: 'há cerca de {delta} horas',
	dayAgo: 'há um dia',
	daysAgo: 'há {delta} dias',
	weekAgo: 'há uma semana',
	weeksAgo: 'há {delta} semanas',
	monthAgo: 'há um mês',
	monthsAgo: 'há {delta} meses',
	yearAgo: 'há um ano',
	yearsAgo: 'há {delta} anos',

	lessThanMinuteUntil: 'em menos de um minuto',
	minuteUntil: 'em um minuto',
	minutesUntil: 'em {delta} minutos',
	hourUntil: 'em uma hora',
	hoursUntil: 'em {delta} horas',
	dayUntil: 'em um dia',
	daysUntil: 'em {delta} dias',
	weekUntil: 'em uma semana',
	weeksUntil: 'em {delta} semanas',
	monthUntil: 'em um mês',
	monthsUntil: 'em {delta} meses',
	yearUntil: 'em um ano',
	yearsUntil: 'em {delta} anos'

});


/*
---

script: Date.Russian.js

name: Date.Russian

description: Date messages for Russian (utf-8).

license: MIT-style license

authors:
  - Evstigneev Pavel

requires:
  - /Lang

provides: [Date.Russian]

...
*/

(function(){

// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
// one -> n mod 10 is 1 and n mod 100 is not 11;
// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
// other -> everything else (example 3.14)
var pluralize = function (n, one, few, many, other){
	var modulo10 = n % 10,
		modulo100 = n % 100;

	if (modulo10 == 1 && modulo100 != 11){
		return one;
	} else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
		return few;
	} else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
		return many;
	} else {
		return other;
	}
};

MooTools.lang.set('ru-RU-unicode', 'Date', {

	months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
	days: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'],

	// Culture's date order: DD.MM.YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d.%m.%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: '',

	lessThanMinuteAgo: 'меньше минуты назад',
	minuteAgo: 'минута назад',
	minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'минута', 'минуты', 'минут') + ' назад'; },
	hourAgo: 'час назад',
	hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часов') + ' назад'; },
	dayAgo: 'вчера',
	daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'день', 'дня', 'дней') + ' назад'; },

	lessThanMinuteUntil: 'меньше минуты назад',
	minuteUntil: 'через минуту',
	minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часов') + ''; },
	hourUntil: 'через час',
	hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часов') + ''; },
	dayUntil: 'завтра',
	daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'день', 'дня', 'дней') + ''; }

});

})();


/*
---

script: Date.Spanish.js

name: Date.Spanish

description: Date messages for Spanish.

license: MIT-style license

authors:
  - Ãlfons Sanchez

requires:
  - /Lang

provides: [Date.Spanish]

...
*/

MooTools.lang.set('es-ES', 'Date', {

	months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
	days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],

	// Culture's date order: DD/MM/YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d/%m/%Y',
	shortTime: '%H:%M',
	AM: 'AM',
	PM: 'PM',

	// Date.Extras
	ordinal: '',

	lessThanMinuteAgo: 'hace menos de un minuto',
	minuteAgo: 'hace un minuto',
	minutesAgo: 'hace {delta} minutos',
	hourAgo: 'hace una hora',
	hoursAgo: 'hace unas {delta} horas',
	dayAgo: 'hace un día',
	daysAgo: 'hace {delta} días',
	weekAgo: 'hace una semana',
	weeksAgo: 'hace unas {delta} semanas',
	monthAgo: 'hace un mes',
	monthsAgo: 'hace {delta} meses',
	yearAgo: 'hace un año',
	yearsAgo: 'hace {delta} años',

	lessThanMinuteUntil: 'menos de un minuto desde ahora',
	minuteUntil: 'un minuto desde ahora',
	minutesUntil: '{delta} minutos desde ahora',
	hourUntil: 'una hora desde ahora',
	hoursUntil: 'unas {delta} horas desde ahora',
	dayUntil: 'un día desde ahora',
	daysUntil: '{delta} días desde ahora',
	weekUntil: 'una semana desde ahora',
	weeksUntil: 'unas {delta} semanas desde ahora',
	monthUntil: 'un mes desde ahora',
	monthsUntil: '{delta} meses desde ahora',
	yearUntil: 'un año desde ahora',
	yearsUntil: '{delta} años desde ahora'

});


/*
---

script: Date.Swedish.js

name: Date.Swedish

description: Date messages for Swedish.

license: MIT-style license

authors:
  - Martin Lundgren

requires:
  - /Lang

provides: [Date.Swedish]

...
*/

MooTools.lang.set('sv-SE', 'Date', {

	months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
	days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],

	// Culture's date order: YYYY-MM-DD
	dateOrder: ['year', 'month', 'date'],
	shortDate: '%Y-%m-%d',
	shortTime: '%H:%M',
	AM: '',
	PM: '',

	// Date.Extras
	ordinal: '',

	lessThanMinuteAgo: 'mindre än en minut sedan',
	minuteAgo: 'ungefär en minut sedan',
	minutesAgo: '{delta} minuter sedan',
	hourAgo: 'ungefär en timme sedan',
	hoursAgo: 'ungefär {delta} timmar sedan',
	dayAgo: '1 dag sedan',
	daysAgo: '{delta} dagar sedan',

	lessThanMinuteUntil: 'mindre än en minut sedan',
	minuteUntil: 'ungefär en minut sedan',
	minutesUntil: '{delta} minuter sedan',
	hourUntil: 'ungefär en timme sedan',
	hoursUntil: 'ungefär {delta} timmar sedan',
	dayUntil: '1 dag sedan',
	daysUntil: '{delta} dagar sedan'

});


/*
---

script: Date.Ukrainian.js

name: Date.Ukrainian

description: Date messages for Ukrainian.

license: MIT-style license

authors:
  - Slik

requires:
  - /Lang

provides: [Date.Ukrainian]

...
*/

(function(){

var pluralize = function(n, one, few, many, other){
	var d = (n / 10).toInt(),
		z = n % 10,
		s = (n / 100).toInt();

	if (d == 1 && n > 10) return many;
	if (z == 1) return one;
	if (z > 0 && z < 5) return few;
	return many;
};

MooTools.lang.set('uk-UA', 'Date', {

	months: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
	days: ['Неділя', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', "П'ятниця", 'Субота'],

	// Culture's date order: DD/MM/YYYY
	dateOrder: ['date', 'month', 'year'],
	shortDate: '%d/%m/%Y',
	shortTime: '%H:%M',
	AM: 'до полудня',
	PM: 'по полудню',

	// Date.Extras
	ordinal: '',

	lessThanMinuteAgo: 'меньше хвилини тому',
	minuteAgo: 'хвилину тому',
	minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин') + ' тому'; },
	hourAgo: 'годину тому',
	hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'годину', 'години', 'годин') + ' тому'; },
	dayAgo: 'вчора',
	daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'день', 'дня', 'днів') + ' тому'; },
	weekAgo: 'тиждень тому',
	weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів') + ' тому'; },
	monthAgo: 'місяць тому',
	monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'місяць', 'місяці', 'місяців') + ' тому'; },
	yearAgo: 'рік тому',
	yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'роки', 'років') + ' тому'; },

	lessThanMinuteUntil: 'за мить',
	minuteUntil: 'через хвилину',
	minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин'); },
	hourUntil: 'через годину',
	hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'годину', 'години', 'годин'); },
	dayUntil: 'завтра',
	daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'день', 'дня', 'днів'); },
	weekUntil: 'через тиждень',
	weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів'); },
	monthUntil: 'через місяць',
	monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'місяць', 'місяці', 'місяців'); },
	yearUntil: 'через рік',
	yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'роки', 'років'); }

});

})();


/*
---

script: Form.Validator.Arabic.js

name: Form.Validator.Arabic

description: Form Validator messages for Arabic.

license: MIT-style license

authors:
  - Chafik Barbar

requires:
  - /Lang

provides: [Form.Validator.Arabic]

...
*/

MooTools.lang.set('ar', 'Form.Validator', {

	required: 'هذا الحقل مطلوب.',
	minLength: 'رجاءً إدخال {minLength}  أحرف على الأقل (تم إدخال {length} أحرف).',
	maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
	integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر عشري أو مئوي (مثال 1.25 ) غير مسموح.',
	numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
	digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو شحطة)',
	alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
	alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
	dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
	dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
	email: 'الرجاء إدخال بريد إلكتروني صحيح.',
	url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.google.com',
	currencyDollar: 'الرجاء إدخال قيمة $ صحيحة.  مثال, 100.00$',
	oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
	errorPrefix: 'خطأ: ',
	warningPrefix: 'تحذير: '

});


/*
---

script: Form.Validator.Catalan.js

name: Form.Validator.Catalan

description: Form Validator messages for Catalan.

license: MIT-style license

authors:
  - Miquel Hudin
  - Ãlfons Sanchez

requires:
  - /Lang

provides: [Form.Validator.Catalan]

...
*/

MooTools.lang.set('ca-CA', 'Form.Validator', {

	required: 'Aquest camp es obligatori.',
	minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
	maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
	integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
	numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
	digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
	alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No s´admiteixen espais ni altres caracters.',
	alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No s´admiteixen espais ni altres caracters.',
	dateSuchAs: 'Per favor introdueix una data valida com {date}',
	dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
	email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple,  "fred@domain.com".',
	url: 'Per favor introdueix una URL valida com http://www.google.com.',
	currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
	oneRequired: 'Per favor introdueix alguna cosa per al menys una d´aquestes entrades.',
	errorPrefix: 'Error: ',
	warningPrefix: 'Avis: ',

	// Form.Validator.Extras
	noSpace: 'No poden haver espais en aquesta entrada.',
	reqChkByNode: 'No hi han elements seleccionats.',
	requiredChk: 'Aquest camp es obligatori.',
	reqChkByName: 'Per favor selecciona una {label}.',
	match: 'Aquest camp necessita coincidir amb el camp {matchName}',
	startDate: 'la data de inici',
	endDate: 'la data de fi',
	currendDate: 'la data actual',
	afterDate: 'La data deu ser igual o posterior a {label}.',
	beforeDate: 'La data deu ser igual o anterior a {label}.',
	startMonth: 'Per favor selecciona un mes d´orige',
	sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'

});


/*
---

script: Form.Validator.Czech.js

name: Form.Validator.Czech

description: Form Validator messages for Czech.

license: MIT-style license

authors:
  - Jan Černý chemiX

requires:
  - /Lang

provides: [Form.Validator.Czech]

...
*/

MooTools.lang.set('cs-CZ', 'Form.Validator', {

	required: 'Tato položka je povinná.',
	minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
	maxLength: 'Zadejte prosím méně než {maxLength} znaků (nápsáno {length} znaků).',
	integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
	numeric: 'Zadejte jen číselné hodnoty  (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
	digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
	alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
	alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
	dateSuchAs: 'Zadejte prosím platné datum jako {date}',
	dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
	email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
	url: 'Zadejte prosím platnou URL adresu jako http://www.google.com.',
	currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
	oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto položky.',
	errorPrefix: 'Chyba: ',
	warningPrefix: 'Upozornění: ',

	// Form.Validator.Extras
	noSpace: 'V této položce nejsou povoleny mezery',
	reqChkByNode: 'Nejsou vybrány žádné položky.',
	requiredChk: 'Tato položka je vyžadována.',
	reqChkByName: 'Prosím vyberte {label}.',
	match: 'Tato položka se musí shodovat s položkou {matchName}',
	startDate: 'datum zahájení',
	endDate: 'datum ukončení',
	currendDate: 'aktuální datum',
	afterDate: 'Datum by mělo být stejné nebo větší než {label}.',
	beforeDate: 'Datum by mělo být stejné nebo menší než {label}.',
	startMonth: 'Vyberte počáteční měsíc.',
	sameMonth: 'Tyto dva datumy musí být ve stejném měsíci - změňte jeden z nich.',
	creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'

});


/*
---

script: Form.Validator.Chinese.js

name: Form.Validator.Chinese

description: Form Validator messages for Chinese (simplified and traditional).

license: MIT-style license

authors:
  - YMind Chan

requires:
  - /Lang
  - /Form.Validator

provides: [Form.Validator.Chinese, Form.Validator.CurrencyYuanValidator]

...
*/

// Simplified Chinese
MooTools.lang.set('zh-CHS', 'Form.Validator', {

	required: '此项必填。',
	minLength: '请至少输入 {minLength} 个字符 (已输入 {length} 个)。',
	maxLength: '最多只能输入 {maxLength} 个字符 (已输入 {length} 个)。',
	integer: '请输入一个整数，不能包含小数点。例如："1", "200"。',
	numeric: '请输入一个数字，例如："1", "1.1", "-1", "-1.1"。',
	digits: '请输入由数字和标点符号组成的内容。例如电话号码。',
	alpha: '请输入 A-Z 的 26 个字母，不能包含空格或任何其他字符。',
	alphanum: '请输入 A-Z 的 26 个字母或 0-9 的 10 个数字，不能包含空格或任何其他字符。',
	dateSuchAs: '请输入合法的日期格式，如：{date}。',
	dateInFormatMDY: '请输入合法的日期格式，例如：YYYY-MM-DD ("2010-12-31")。',
	email: '请输入合法的电子信箱地址，例如："fred@domain.com"。',
	url: '请输入合法的 Url 地址，例如：http://www.google.com。',
	currencyDollar: '请输入合法的货币符号，例如：￥100.0',
	oneRequired: '请至少选择一项。',
	errorPrefix: '错误：',
	warningPrefix: '警告：',

	// Form.Validator.Extras
	noSpace: '不能包含空格。',
	reqChkByNode: '未选择任何内容。',
	requiredChk: '此项必填。',
	reqChkByName: '请选择 {label}.',
	match: '必须与{matchName}相匹配',
	startDate: '起始日期',
	endDate: '结束日期',
	currendDate: '当前日期',
	afterDate: '日期必须等于或晚于 {label}.',
	beforeDate: '日期必须早于或等于 {label}.',
	startMonth: '请选择起始月份',
	sameMonth: '您必须修改两个日期中的一个，以确保它们在同一月份。',
	creditcard: '您输入的信用卡号码不正确。当前已输入{length}个字符。'

});

// Traditional Chinese
MooTools.lang.set('zh-CHT', 'Form.Validator', {

	required: '此項必填。 ',
	minLength: '請至少輸入{minLength} 個字符(已輸入{length} 個)。 ',
	maxLength: '最多只能輸入{maxLength} 個字符(已輸入{length} 個)。 ',
	integer: '請輸入一個整數，不能包含小數點。例如："1", "200"。 ',
	numeric: '請輸入一個數字，例如："1", "1.1", "-1", "-1.1"。 ',
	digits: '請輸入由數字和標點符號組成的內容。例如電話號碼。 ',
	alpha: '請輸入AZ 的26 個字母，不能包含空格或任何其他字符。 ',
	alphanum: '請輸入AZ 的26 個字母或0-9 的10 個數字，不能包含空格或任何其他字符。 ',
	dateSuchAs: '請輸入合法的日期格式，如：{date}。 ',
	dateInFormatMDY: '請輸入合法的日期格式，例如：YYYY-MM-DD ("2010-12-31")。 ',
	email: '請輸入合法的電子信箱地址，例如："fred@domain.com"。 ',
	url: '請輸入合法的Url 地址，例如：http://www.google.com。 ',
	currencyDollar: '請輸入合法的貨幣符號，例如：￥100.0',
	oneRequired: '請至少選擇一項。 ',
	errorPrefix: '錯誤：',
	warningPrefix: '警告：',

	// Form.Validator.Extras
	noSpace: '不能包含空格。 ',
	reqChkByNode: '未選擇任何內容。 ',
	requiredChk: '此項必填。 ',
	reqChkByName: '請選擇 {label}.',
	match: '必須與{matchName}相匹配',
	startDate: '起始日期',
	endDate: '結束日期',
	currendDate: '當前日期',
	afterDate: '日期必須等於或晚於{label}.',
	beforeDate: '日期必須早於或等於{label}.',
	startMonth: '請選擇起始月份',
	sameMonth: '您必須修改兩個日期中的一個，以確保它們在同一月份。 ',
	creditcard: '您輸入的信用卡號碼不正確。當前已輸入{length}個字符。 '

});

Form.Validator.add('validate-currency-yuan', {

	errorMsg: function(){
		return Form.Validator.getMsg('currencyYuan');
	},

	test: function(element) {
		// [￥]1[##][,###]+[.##]
		// [￥]1###+[.##]
		// [￥]0.##
		// [￥].##
		return Form.Validator.getValidator('IsEmpty').test(element) || (/^￥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
	}

});


/*
---

script: Form.Validator.Dutch.js

name: Form.Validator.Dutch

description: Form Validator messages for Dutch.

license: MIT-style license

authors:
  - Lennart Pilon
  - Arian Stolwijk
  - Tim Wienk

requires:
  - /Lang

provides: [Form.Validator.Dutch]

...
*/

MooTools.lang.set('nl-NL', 'Form.Validator', {

	required: 'Dit veld is verplicht.',
	minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
	maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
	integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
	numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
	digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
	alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
	alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
	dateSuchAs: 'Vul een geldige datum in, zoals {date}',
	dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
	email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
	url: 'Vul een geldige URL in, zoals http://www.google.nl.',
	currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
	oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
	warningPrefix: 'Waarschuwing: ',
	errorPrefix: 'Fout: ',

	// Form.Validator.Extras
	noSpace: 'Spaties zijn niet toegestaan in dit veld.',
	reqChkByNode: 'Er zijn geen items geselecteerd.',
	requiredChk: 'Dit veld is verplicht.',
	reqChkByName: 'Selecteer een {label}.',
	match: 'Dit veld moet overeen komen met het {matchName} veld',
	startDate: 'de begin datum',
	endDate: 'de eind datum',
	currendDate: 'de huidige datum',
	afterDate: 'De datum moet hetzelfde of na {label} zijn.',
	beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
	startMonth: 'Selecteer een begin maand',
	sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
	creditcard: 'Het ingevulde creditcard nummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'

});


/*
---

script: Form.Validator.Estonian.js

name: Form.Validator.Estonian

description: Form Validator messages for Estonian.

license: MIT-style license

authors:
  - Kevin Valdek

requires:
  - /Lang

provides: [Form.Validator.Estonian]

...
*/

MooTools.lang.set('et-EE', 'Form.Validator', {

	required: 'Väli peab olema täidetud.',
	minLength: 'Palun sisestage vähemalt {minLength} tähte (te sisestasite {length} tähte).',
	maxLength: 'Palun ärge sisestage rohkem kui {maxLength} tähte (te sisestasite {length} tähte).',
	integer: 'Palun sisestage väljale täisarv. Kümnendarvud (näiteks 1.25) ei ole lubatud.',
	numeric: 'Palun sisestage ainult numbreid väljale (näiteks "1", "1.1", "-1" või "-1.1").',
	digits: 'Palun kasutage ainult numbreid ja kirjavahemärke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
	alpha: 'Palun kasutage ainult tähti (a-z). Tühikud ja teised sümbolid on keelatud.',
	alphanum: 'Palun kasutage ainult tähti (a-z) või numbreid (0-9). Tühikud ja teised sümbolid on keelatud.',
	dateSuchAs: 'Palun sisestage kehtiv kuupäev kujul {date}',
	dateInFormatMDY: 'Palun sisestage kehtiv kuupäev kujul MM.DD.YYYY (näiteks: "12.31.1999").',
	email: 'Palun sisestage kehtiv e-maili aadress (näiteks: "fred@domain.com").',
	url: 'Palun sisestage kehtiv URL (näiteks: http://www.google.com).',
	currencyDollar: 'Palun sisestage kehtiv $ summa (näiteks: $100.00).',
	oneRequired: 'Palun sisestage midagi vähemalt ühele antud väljadest.',
	errorPrefix: 'Viga: ',
	warningPrefix: 'Hoiatus: ',

	// Form.Validator.Extras
	noSpace: 'Väli ei tohi sisaldada tühikuid.',
	reqChkByNode: 'Ükski väljadest pole valitud.',
	requiredChk: 'Välja täitmine on vajalik.',
	reqChkByName: 'Palun valige üks {label}.',
	match: 'Väli peab sobima {matchName} väljaga',
	startDate: 'algkuupäev',
	endDate: 'lõppkuupäev',
	currendDate: 'praegune kuupäev',
	afterDate: 'Kuupäev peab olema võrdne või pärast {label}.',
	beforeDate: 'Kuupäev peab olema võrdne või enne {label}.',
	startMonth: 'Palun valige algkuupäev.',
	sameMonth: 'Antud kaks kuupäeva peavad olema samas kuus - peate muutma ühte kuupäeva.'

});


/*
---

script: Form.Validator.German.js

name: Form.Validator.German

description: Form Validator messages for German.

license: MIT-style license

authors:
  - Frank Rossi
  - Ulrich Petri
  - Fabian Beiner

requires:
  - /Lang

provides: [Form.Validator.German]

...
*/

MooTools.lang.set('de-DE', 'Form.Validator', {

	required: 'Dieses Eingabefeld muss ausgef&uuml;llt werden.',
	minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
	maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
	integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. &quot;1.25&quot;) sind nicht erlaubt.',
	numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;) ein.',
	digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
	alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
	alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
	dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein (z.B. &quot;{date}&quot;).',
	dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum im Format TT.MM.JJJJ ein (z.B. &quot;31.12.1999&quot;).',
	email: 'Geben Sie bitte eine g&uuml;ltige E-Mail-Adresse ein (z.B. &quot;max@mustermann.de&quot;).',
	url: 'Geben Sie bitte eine g&uuml;ltige URL ein (z.B. &quot;http://www.google.de&quot;).',
	currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in EURO ein (z.B. 100.00&#8364;).',
	oneRequired: 'Bitte f&uuml;llen Sie mindestens ein Eingabefeld aus.',
	errorPrefix: 'Fehler: ',
	warningPrefix: 'Warnung: ',

	// Form.Validator.Extras
	noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
	reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
	requiredChk: 'Dieses Feld muss ausgef&uuml;llt werden.',
	reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
	match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld &uuml;bereinstimmen.',
	startDate: 'Das Anfangsdatum',
	endDate: 'Das Enddatum',
	currendDate: 'Das aktuelle Datum',
	afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein als {label}.',
	beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein als {label}.',
	startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
	sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eines von beiden ver&auml;ndern.',
	creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'

});


/*
---

script: Form.Validator.German.CH.js

name: Form.Validator.German.CH

description: Form Validator messages for German (Switzerland).

license: MIT-style license

authors:
  - Michael van der Weg

requires:
  - /Lang

provides: [Form.Validator.German.CH]

...
*/

MooTools.lang.set('de-CH', 'Form.Validator', {

	required: 'Dieses Feld ist obligatorisch.',
	minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
	maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
	integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
	numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
	digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
	alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
	alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
	dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
	dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
	email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
	url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.google.ch.',
	currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
	oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
	errorPrefix: 'Fehler: ',
	warningPrefix: 'Warnung: ',

	// Form.Validator.Extras
	noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
	reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
	requiredChk: 'Dieses Feld ist obligatorisch.',
	reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
	match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
	startDate: 'Das Anfangsdatum',
	endDate: 'Das Enddatum',
	currendDate: 'Das aktuelle Datum',
	afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
	beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
	startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
	sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
	creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'

});


/*
---

script: Form.Validator.French.js

name: Form.Validator.French

description: Form Validator messages in French.

license: MIT-style license

authors:
  - Miquel Hudin
  - Nicolas Sorosac

requires:
  - /Lang

provides: [Form.Validator.French]

...
*/

MooTools.lang.set('fr-FR', 'Form.Validator', {

	required: 'Ce champ est obligatoire.',
	minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
	maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
	integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
	numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
	digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
	alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
	alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
	dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
	dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
	email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par example "fred@domaine.com".',
	url: 'Veuillez saisir une URL, comme http://www.google.com.',
	currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par example 100,00&euro;.',
	oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
	errorPrefix: 'Erreur : ',
	warningPrefix: 'Attention : ',

	// Form.Validator.Extras
	noSpace: "Ce champ n'accepte pas les espaces.",
	reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
	requiredChk: 'Ce champ est obligatoire.',
	reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
	match: 'Ce champ doit correspondre avec le champ {matchName}.',
	startDate: 'date de d&eacute;but',
	endDate: 'date de fin',
	currendDate: 'date actuelle',
	afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
	beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
	startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
	sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.'

});


/*
---

script: Form.Validator.Italian.js

name: Form.Validator.Italian

description: Form Validator messages in Italian.

license: MIT-style license

authors:
  - Leonardo Laureti
  - Andrea Novero

requires:
  - /Lang

provides: [Form.Validator.Italian]

...
*/

MooTools.lang.set('it-IT', 'Form.Validator', {

	required: 'Il campo &egrave; obbligatorio.',
	minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
	maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
	integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
	numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
	digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
	alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
	alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
	dateSuchAs: 'Inserire una data valida del tipo {date}',
	dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
	email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
	url: 'Inserire un indirizzo valido. Per esempio "http://www.dominio.com".',
	currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
	oneRequired: 'Completare almeno uno dei campi richiesti.',
	errorPrefix: 'Errore: ',
	warningPrefix: 'Attenzione: ',

	// Form.Validator.Extras
	noSpace: 'Non sono consentiti spazi.',
	reqChkByNode: 'Nessuna voce selezionata.',
	requiredChk: 'Il campo &egrave; obbligatorio.',
	reqChkByName: 'Selezionare un(a) {label}.',
	match: 'Il valore deve corrispondere al campo {matchName}',
	startDate: "data d'inizio",
	endDate: 'data di fine',
	currendDate: 'data attuale',
	afterDate: 'La data deve corrispondere o essere successiva al {label}.',
	beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
	startMonth: "Selezionare un mese d'inizio",
	sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'

});


/*
---

script: Form.Validator.Norwegian.js

name: Form.Validator.Norwegian

description: Form Validator messages in Norwegian.

license: MIT-style license

authors:
  - Espen 'Rexxars' Hovlandsdal

requires:
  - /Lang

provides: [Form.Validator.Norwegian]

...
*/

MooTools.lang.set('no-NO', 'Form.Validator', {

	required: 'Dette feltet er pÃ¥krevd.',
	minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
	maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
	integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
	numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
	digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
	alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
	alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
	dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
	dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
	email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
	url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.google.no.',
	currencyDollar: 'Vennligst fyll ut et gyldig $ belÃ¸p. For eksempel $100.00 .',
	oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
	errorPrefix: 'Feil: ',
	warningPrefix: 'Advarsel: '

});


/*
---

script: Form.Validator.Polish.js

name: Form.Validator.Polish

description: Form Validator messages for Polish.

license: MIT-style license

authors:
  - Oskar Krawczyk

requires:
  - /Lang

provides: [Form.Validator.Polish]

...
*/

MooTools.lang.set('pl-PL', 'Form.Validator', {

	required: 'To pole jest wymagane.',
	minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
	maxLength: 'Dozwolone jest nie więcej niż {maxLength} znaków (wpisanych zostało {length})',
	integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
	numeric: 'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
	digits: 'Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
	alpha: 'Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
	alphanum: 'Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
	dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
	dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
	email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
	url: 'Prosimy podać prawidłowy adres URL, np. http://www.google.pl.',
	currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
	oneRequired: 'Prosimy wypełnić chociaż jedno z pól.',
	errorPrefix: 'Błąd: ',
	warningPrefix: 'Uwaga: ',

	// Form.Validator.Extras
	noSpace: 'W tym polu nie mogą znajdować się spacje.',
	reqChkByNode: 'Brak zaznaczonych elementów.',
	requiredChk: 'To pole jest wymagane.',
	reqChkByName: 'Prosimy wybrać z {label}.',
	match: 'To pole musi być takie samo jak {matchName}',
	startDate: 'data początkowa',
	endDate: 'data końcowa',
	currendDate: 'aktualna data',
	afterDate: 'Podana data poinna być taka sama lub po {label}.',
	beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
	startMonth: 'Prosimy wybrać początkowy miesiąc.',
	sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'

});


/*
---

script: Form.Validator.Portuguese.js

name: Form.Validator.Portuguese

description: Form Validator messages in Portuguese.

license: MIT-style license

authors:
  - Miquel Hudin

requires:
  - /Lang

provides: [Form.Validator.Portuguese]

...
*/

MooTools.lang.set('pt-PT', 'Form.Validator', {

	required: 'Este campo é necessário.',
	minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
	maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
	integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
	numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
	digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
	alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
	alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
	dateSuchAs: 'Digite uma data válida, como {date}',
	dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
	email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
	url: 'Digite uma URL válida, como http://www.google.com.',
	currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
	oneRequired: 'Digite algo para pelo menos um desses insumos.',
	errorPrefix: 'Erro: ',
	warningPrefix: 'Aviso: '

});


/*
---

script: Form.Validator.Portuguese.BR.js

name: Form.Validator.Portuguese.BR

description: Form Validator messages in Portuguese-BR.

license: MIT-style license

authors:
  - Fábio Miranda Costa

requires:
  - /Lang

provides: [Form.Validator.Portuguese.BR]

...
*/

MooTools.lang.set('pt-BR', 'Form.Validator', {

	required: 'Este campo é obrigatório.',
	minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
	maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
	integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
	numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
	digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
	alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
	alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
	dateSuchAs: 'Digite uma data válida, como {date}',
	dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
	email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
	url: 'Digite uma URL válida. Exemplo: http://www.google.com.',
	currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
	oneRequired: 'Digite algo para pelo menos um desses campos.',
	errorPrefix: 'Erro: ',
	warningPrefix: 'Aviso: ',

	// Form.Validator.Extras
	noSpace: 'Não é possível digitar espaços neste campo.',
	reqChkByNode: 'Não foi selecionado nenhum item.',
	requiredChk: 'Este campo é obrigatório.',
	reqChkByName: 'Por favor digite um {label}.',
	match: 'Este campo deve ser igual ao campo {matchName}.',
	startDate: 'a data inicial',
	endDate: 'a data final',
	currendDate: 'a data atual',
	afterDate: 'A data deve ser igual ou posterior a {label}.',
	beforeDate: 'A data deve ser igual ou anterior a {label}.',
	startMonth: 'Por favor selecione uma data inicial.',
	sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
	creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'

});


/*
---

script: Form.Validator.Russian.js

name: Form.Validator.Russian

description: Form Validator messages for Russian (utf-8 and cp1251).

license: MIT-style license

authors:
  - Chernodarov Egor

requires:
  - /Lang

provides: [Form.Validator.Russian]

...
*/

MooTools.lang.set('ru-RU-unicode', 'Form.Validator', {

	required: 'Это поле обязательно к заполнению.',
	minLength: 'Пожалуйста, введите хотя бы {minLength} символов (Вы ввели {length}).',
	maxLength: 'Пожалуйста, введите не больше {maxLength} символов (Вы ввели {length}).',
	integer: 'Пожалуйста, введите в это поле число. Дробные числа (например 1.25) тут не разрешены.',
	numeric: 'Пожалуйста, введите в это поле число (например "1" или "1.1", или "-1", или "-1.1").',
	digits: 'В этом поле Вы можете использовать только цифры и знаки пунктуации (например, телефонный номер со знаками дефиса или с точками).',
	alpha: 'В этом поле можно использовать только латинские буквы (a-z). Пробелы и другие символы запрещены.',
	alphanum: 'В этом поле можно использовать только латинские буквы (a-z) и цифры (0-9). Пробелы и другие символы запрещены.',
	dateSuchAs: 'Пожалуйста, введите корректную дату {date}',
	dateInFormatMDY: 'Пожалуйста, введите дату в формате ММ/ДД/ГГГГ (например "12/31/1999")',
	email: 'Пожалуйста, введите корректный емейл-адрес. Для примера "fred@domain.com".',
	url: 'Пожалуйста, введите правильную ссылку вида http://www.google.com.',
	currencyDollar: 'Пожалуйста, введите сумму в долларах. Например: $100.00 .',
	oneRequired: 'Пожалуйста, выберите хоть что-нибудь в одном из этих полей.',
	errorPrefix: 'Ошибка: ',
	warningPrefix: 'Внимание: '

});

// Translated to windows-1251 codepage
MooTools.lang.set('ru-RU', 'Form.Validator', {

	required: 'Ýòî ïîëå îáÿçàòåëüíî ê çàïîëíåíèþ.',
	minLength: 'Ïîæàëóéñòà, ââåäèòå õîòÿ áû {minLength} ñèìâîëîâ (Âû ââåëè {length}).',
	maxLength: 'Ïîæàëóéñòà, ââåäèòå íå áîëüøå {maxLength} ñèìâîëîâ (Âû ââåëè {length}).',
	integer: 'Ïîæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî. Äðîáíûå ÷èñëà (íàïðèìåð 1.25) òóò íå ðàçðåøåíû.',
	numeric: 'Ïîæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî (íàïðèìåð "1" èëè "1.1", èëè "-1", èëè "-1.1").',
	digits: 'Â ýòîì ïîëå Âû ìîæåòå èñïîëüçîâàòü òîëüêî öèôðû è çíàêè ïóíêòóàöèè (íàïðèìåð, òåëåôîííûé íîìåð ñî çíàêàìè äåôèñà èëè ñ òî÷êàìè).',
	alpha: 'Â ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z). Ïðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
	alphanum: 'Â ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z) è öèôðû (0-9). Ïðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
	dateSuchAs: 'Ïîæàëóéñòà, ââåäèòå êîððåêòíóþ äàòó {date}',
	dateInFormatMDY: 'Ïîæàëóéñòà, ââåäèòå äàòó â ôîðìàòå ÌÌ/ÄÄ/ÃÃÃÃ (íàïðèìåð "12/31/1999")',
	email: 'Ïîæàëóéñòà, ââåäèòå êîððåêòíûé åìåéë-àäðåñ. Äëÿ ïðèìåðà "fred@domain.com".',
	url: 'Ïîæàëóéñòà, ââåäèòå ïðàâèëüíóþ ññûëêó âèäà http://www.google.com.',
	currencyDollar: 'Ïîæàëóéñòà, ââåäèòå ñóììó â äîëëàðàõ. Íàïðèìåð: $100.00 .',
	oneRequired: 'Ïîæàëóéñòà, âûáåðèòå õîòü ÷òî-íèáóäü â îäíîì èç ýòèõ ïîëåé.',
	errorPrefix: 'Îøèáêà: ',
	warningPrefix: 'Âíèìàíèå: '

});


/*
---

script: Form.Validator.Spanish.js

name: Form.Validator.Spanish

description: Form Validator messages for Spanish.

license: MIT-style license

authors:
  - Ãlfons Sanchez

requires:
  - /Lang

provides: [Form.Validator.Spanish]

...
*/

MooTools.lang.set('es-ES', 'Form.Validator', {

	required: 'Este campo es obligatorio.',
	minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
	maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
	integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
	numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
	digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
	alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
	alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
	dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
	dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
	email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo,  "fred@domain.com".',
	url: 'Por favor introduce una URL v&aacute;lida como http://www.google.com.',
	currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
	oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
	errorPrefix: 'Error: ',
	warningPrefix: 'Aviso: ',

	// Form.Validator.Extras
	noSpace: 'No pueden haber espacios en esta entrada.',
	reqChkByNode: 'No hay elementos seleccionados.',
	requiredChk: 'Este campo es obligatorio.',
	reqChkByName: 'Por favor selecciona una {label}.',
	match: 'Este campo necesita coincidir con el campo {matchName}',
	startDate: 'la fecha de inicio',
	endDate: 'la fecha de fin',
	currendDate: 'la fecha actual',
	afterDate: 'La fecha debe ser igual o posterior a {label}.',
	beforeDate: 'La fecha debe ser igual o anterior a {label}.',
	startMonth: 'Por favor selecciona un mes de origen',
	sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'

});


/*
---

script: Form.Validator.Swedish.js

name: Form.Validator.Swedish

description: Form Validator messages for Swedish.

license: MIT-style license

authors:
  - Martin Lundgren

requires:
  - /Lang

provides: [Form.Validator.Swedish]

...
*/

MooTools.lang.set('sv-SE', 'Form.Validator', {

	required: 'Fältet är obligatoriskt.',
	minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
	maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
	integer: 'Ange ett heltal i fältet. Tal med decimaler (t.ex. 1,25) är inte tillåtna.',
	numeric: 'Ange endast numeriska värden i detta fält (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
	digits: 'Använd endast siffror och skiljetecken i detta fält (till exempel ett telefonnummer med bindestreck tillåtet).',
	alpha: 'Använd endast bokstäver (a-ö) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
	alphanum: 'Använd endast bokstäver (a-ö) och siffror (0-9) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
	dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
	dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
	email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
	url: 'Ange en giltig webbadress som http://www.google.com.',
	currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
	oneRequired: 'Vänligen ange minst ett av dessa alternativ.',
	errorPrefix: 'Fel: ',
	warningPrefix: 'Varning: ',

	// Form.Validator.Extras
	noSpace: 'Det får inte finnas några mellanslag i detta fält.',
	reqChkByNode: 'Inga objekt är valda.',
	requiredChk: 'Detta är ett obligatoriskt fält.',
	reqChkByName: 'Välj en {label}.',
	match: 'Detta fält måste matcha {matchName}',
	startDate: 'startdatumet',
	endDate: 'slutdatum',
	currendDate: 'dagens datum',
	afterDate: 'Datumet bör vara samma eller senare än {label}.',
	beforeDate: 'Datumet bör vara samma eller tidigare än {label}.',
	startMonth: 'Välj en start månad',
	sameMonth: 'Dessa två datum måste vara i samma månad - du måste ändra det ena eller det andra.'

});


/*
---

script: Form.Validator.Ukrainian.js

name: Form.Validator.Ukrainian

description: Form Validator messages in Ukrainian (utf-8).

license: MIT-style license

authors:
  - Slik

requires:
  - /Lang

provides: [Form.Validator.Ukrainian]

...
*/

MooTools.lang.set('uk-UA', 'Form.Validator', {

	required: 'Це поле повинне бути заповненим.',
	minLength: 'Введіть хоча б {minLength} символів (Ви ввели {length}).',
	maxLength: 'Кількість символів не може бути більше {maxLength} (Ви ввели {length}).',
	integer: 'Введіть в це поле число. Дробові числа (наприклад 1.25) не дозволені.',
	numeric: 'Введіть в це поле число (наприклад "1" або "1.1", або "-1", або "-1.1").',
	digits: 'В цьому полі ви можете використовувати лише цифри і знаки пунктіації (наприклад, телефонний номер з знаками дефізу або з крапками).',
	alpha: 'В цьому полі можна використовувати лише латинські літери (a-z). Пробіли і інші символи заборонені.',
	alphanum: 'В цьому полі можна використовувати лише латинські літери (a-z) і цифри (0-9). Пробіли і інші символи заборонені.',
	dateSuchAs: 'Введіть коректну дату {date}.',
	dateInFormatMDY: 'Введіть дату в форматі ММ/ДД/РРРР (наприклад "12/31/2009").',
	email: 'Введіть коректну адресу електронної пошти (наприклад "name@domain.com").',
	url: 'Введіть коректне інтернет-посилання (наприклад http://www.google.com).',
	currencyDollar: 'Введіть суму в доларах (наприклад "$100.00").',
	oneRequired: 'Заповніть одне з полів.',
	errorPrefix: 'Помилка: ',
	warningPrefix: 'Увага: '

});

/*
---

name: Common

description: Jx namespace with methods and classes common to most Jx widgets

license: MIT-style license.

requires:
 - Core/Class
 - Core/Element
 - Core/Browser
 - Core/Element.Style
 - Core/Request
 - Core/Class.Extras
 - More/Class.Binds
 - Core/Array
 - Core/Element.Event
 - Core/Element.Dimensions
 - More/Element.Measure
 - More/Lang
 - Core/Selectors
 - Core/Slick.Finder
 - Core/Slick.Parser

provides: [Jx]

css:
 - license
 - reset
 - common

images:
 - a_pixel.png

...
 */
// $Id: common.js 999 2010-11-28 21:00:43Z jonlb@comcast.net $
/**
 * Function: $jx
 * dereferences a DOM Element to a JxLib object if possible and returns
 * a reference to the object, or null if not defined.
 */
function $jx(id) {
  var widget = null;
  id = document.id(id);
  if (id) {
    widget = id.retrieve('jxWidget');
    if (!widget && id != document.body) {
      widget = $jx(id.getParent());
    }
  }
  return widget;
}

/**
 * Class: Jx
 * Jx is a global singleton object that contains the entire Jx library
 * within it.  All Jx functions, attributes and classes are accessed
 * through the global Jx object.  Jx should not create any other
 * global variables, if you discover that it does then please report
 * it as a bug
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */

/* firebug console supressor for IE/Safari/Opera */
window.addEvent('load',
function() {
    if (! ("console" in window)) {
        var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
        "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"],
            i;

        window.console = {};
        for (i = 0; i < names.length; ++i) {
            window.console[names[i]] = function() {};
        }
    }
});


// add mutator that sets jxFamily when creating a class so we can check
// its type
Class.Mutators.Family = function(self, name) {
    if ($defined(name)) {
        self.jxFamily = name;
        return self;
    }
    else if(!$defined(this.prototype.jxFamily)) {
        this.implement({
            'jxFamily': self
        });
    }
};

// this replaces the mootools $unlink method with our own version that
// avoids infinite recursion on Jx objects.
function $unlink(object) {
    if (object && object.jxFamily) {
        return object;
    }
    var unlinked, p, i, l;
    switch ($type(object)) {
    case 'object':
        unlinked = {};
        for (p in object) unlinked[p] = $unlink(object[p]);
        break;
    case 'hash':
        unlinked = new Hash(object);
        break;
    case 'array':
        unlinked = [];
        for (i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
        break;
    default:
        return object;
    }
    return unlinked;
}

/**
 * Override of mootools-core 1.3's typeOf operator to prevent infinite recursion
 * when doing typeOf on JxLib objects.
 *
var typeOf = this.typeOf = function(item){
    if (item == null) return 'null';
    if (item.jxFamily) return item.jxFamily;
    if (item.$family) return item.$family();

    if (item.nodeName){
        if (item.nodeType == 1) return 'element';
        if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
    } else if (typeof item.length == 'number'){
        if (item.callee) return 'arguments';
        if ('item' in item) return 'collection';
    }

    return typeof item;
};

this.$type = function(object){
    var type = typeOf(object);
    if (type == 'elements') return 'array';
    return (type == 'null') ? false : type;
};
*/

/* Setup global namespace.  It is possible to set the global namespace
 * prior to including jxlib.  This would typically be required only if
 * the auto-detection of the jxlib base URL would fail.  For instance,
 * if you combine jxlib with other javascript libraries into a single file
 * build and call it something without jxlib in the file name, then the
 * detection of baseURL would fail.  If this happens to you, try adding
 * Jx = { baseURL: '/path/to/jxlib/'; }
 * where the path to jxlib contains a file called a_pixel.png (it doesn't
 * have to include jxlib, just the a_pixel.png file).
 */
if (typeof Jx === 'undefined') {
  var Jx = {};
}

/**
 * APIProperty: {String} baseURL
 * This is the URL that Jx was loaded from, it is
 * automatically calculated from the script tag
 * src property that included Jx.
 *
 * Note that this assumes that you are loading Jx
 * from a js/ or lib/ folder in parallel to the
 * images/ folder that contains the various images
 * needed by Jx components.  If you have a different
 * folder structure, you can define Jx's base
 * by including the following before including
 * the jxlib javascript file:
 *
 * (code)
 * Jx = {
 *    baseURL: 'some/path'
 * }
 * (end)
 */
if (!$defined(Jx.baseURL)) {
  (function() {
    var aScripts = document.getElementsByTagName('SCRIPT'),
        i, s, n, file;
    for (i = 0; i < aScripts.length; i++) {
      s = aScripts[i].src;
      n = s.lastIndexOf('/');
      file = s.slice(n+1,s.length-1);
      if (file.contains('jxlib')) {
        Jx.baseURL = s.slice(0,n);
        break;
      }
    }
  })();
}
/**
 * APIProperty: {Image} aPixel
 * aPixel is a single transparent pixel and is the only image we actually
 * use directly in JxLib code.  If you want to use your own transparent pixel
 * image or use it from a different location than the install of jxlib
 * javascript files, you can manually declare it before including jxlib code
 * (code)
 * Jx = {
 *   aPixel: new Element('img', {
 *     alt: '',
 *     title: '',
 *     width: 1,
 *     height: 1,
 *     src: 'path/to/a/transparent.png'
 *   });
 * }
 * (end)
 */
if (!$defined(Jx.aPixel)) {
  Jx.aPixel = new Element('img', {
    alt:'',
    title:'',
    src: Jx.baseURL +'/a_pixel.png'
  });
}

/**
 * APIProperty: {Boolean} isAir
 * indicates if JxLib is running in an Adobe Air environment.  This is
 * normally auto-detected but you can manually set it by declaring the Jx
 * namespace before including jxlib:
 * (code)
 * Jx = {
 *   isAir: true
 * }
 * (end)
 */
if (!$defined(Jx.isAir)) {
  (function() {
    /**
     * Determine if we're running in Adobe AIR.
     */
    var aScripts = document.getElementsByTagName('SCRIPT'),
        src = aScripts[0].src;
    if (src.contains('app:')) {
      Jx.isAir = true;
    } else {
      Jx.isAir = false;
    }
  })();
}

/**
 * APIMethod: setLanguage
 * set the current language to be used by Jx widgets.  This uses the MooTools
 * lang module.  If an invalid or missing language is requested, the default
 * rules of MooTools.lang will be used (revert to en-US at time of writing).
 *
 * Parameters:
 * {String} language identifier, the language to set.
 */
Jx.setLanguage = function(lang) {
  Jx.lang = lang;
  MooTools.lang.setLanguage(Jx.lang);
};

/**
 * APIProperty: {String} lang
 * Checks to see if Jx.lang is already set. If not, it sets it to the default
 * 'en-US'. We will then set the Motools.lang language to this setting
 * automatically.
 *
 * The language can be changed on the fly at anytime by calling
 * Jx.setLanguage().
 * By default all Jx.Widget subclasses will listen for the langChange event of
 * the Mootools.lang class. It will then call a method, changeText(), if it
 * exists on the particular widget. You will be able to disable listening for
 * these changes by setting the Jx.Widget option useLang to false.
 */
if (!$defined(Jx.lang)) {
  Jx.lang = 'en-US';
}

Jx.setLanguage(Jx.lang);

/**
 * APIMethod: applyPNGFilter
 *
 * Static method that applies the PNG Filter Hack for IE browsers
 * when showing 24bit PNG's.  Used automatically for img tags with
 * a class of png24.
 *
 * The filter is applied using a nifty feature of IE that allows javascript to
 * be executed as part of a CSS style rule - this ensures that the hack only
 * gets applied on IE browsers.
 *
 * The CSS that triggers this hack is only in the ie6.css files of the various
 * themes.
 *
 * Parameters:
 * object {Object} the object (img) to which the filter needs to be applied.
 */
Jx.applyPNGFilter = function(o) {
    var t = Jx.aPixel.src,
        s;
    if (o.src != t) {
        s = o.src;
        o.src = t;
        o.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + s + "',sizingMethod='scale')";
    }
};

/**
 * NOTE: We should consider moving the image loading code into a separate
 * class. Perhaps as Jx.Preloader which could extend Jx.Object
 */
Jx.imgQueue = [];
//The queue of images to be loaded
Jx.imgLoaded = {};
//a hash table of images that have been loaded and cached
Jx.imagesLoading = 0;
//counter for number of concurrent image loads
/**
 * APIMethod: addToImgQueue
 * Request that an image be set to a DOM IMG element src attribute.  This puts
 * the image into a queue and there are private methods to manage that queue
 * and limit image loading to 2 at a time.
 *
 * Parameters:
 * obj - {Object} an object containing an element and src
 * property, where element is the element to update and src
 * is the url to the image.
 */
Jx.addToImgQueue = function(obj) {
    if (Jx.imgLoaded[obj.src]) {
        //if this image was already requested (i.e. it's in cache) just set it directly
        obj.element.src = obj.src;
    } else {
        //otherwise stick it in the queue
        Jx.imgQueue.push(obj);
        Jx.imgLoaded[obj.src] = true;
    }
    //start the queue management process
    Jx.checkImgQueue();
};

/**
 * APIMethod: checkImgQueue
 *
 * An internal method that ensures no more than 2 images are loading at a
 * time.
 */
Jx.checkImgQueue = function() {
    while (Jx.imagesLoading < 2 && Jx.imgQueue.length > 0) {
        Jx.loadNextImg();
    }
};

/**
 * Method: loadNextImg
 *
 * An internal method actually populate the DOM element with the image source.
 */
Jx.loadNextImg = function() {
    var obj = Jx.imgQueue.shift();
    if (obj) {
        ++Jx.imagesLoading;
        obj.element.onload = function() {--Jx.imagesLoading;
            Jx.checkImgQueue();
        };
        obj.element.onerror = function() {--Jx.imagesLoading;
            Jx.checkImgQueue();
        };
        obj.element.src = obj.src;
    }
};

/**
 * APIMethod: getNumber
 * safely parse a number and return its integer value.  A NaN value
 * returns 0.  CSS size values are also parsed correctly.
 *
 * Parameters:
 * n - {Mixed} the string or object to parse.
 *
 * Returns:
 * {Integer} the integer value that the parameter represents
 */
Jx.getNumber = function(n, def) {
    var result = n === null || isNaN(parseInt(n, 10)) ? (def || 0) : parseInt(n, 10);
    return result;
};

/**
 * APIMethod: getPageDimensions
 * return the dimensions of the browser client area.
 *
 * Returns:
 * {Object} an object containing a width and height property
 * that represent the width and height of the browser client area.
 */
Jx.getPageDimensions = function() {
    return {
        width: window.getWidth(),
        height: window.getHeight()
    };
};

/**
 * APIMethod: type
 * safely return the type of an object using the mootools type system
 *
 * Returns:
 * {Object} an object containing a width and height property
 * that represent the width and height of the browser client area.
 */
Jx.type = function(obj) {
  return typeof obj == 'undefined' ? false : obj.jxFamily || $type(obj);
};

(function($) {
    // Wrapper for document.id

    /**
     * Class: Element
     *
     * Element is a global object provided by the mootools library.  The
     * functions documented here are extensions to the Element object provided
     * by Jx to make cross-browser compatibility easier to achieve.  Most of
     * the methods are measurement related.
     *
     * While the code in these methods has been converted to use MooTools
     * methods, there may be better MooTools methods to use to accomplish
     * these things.
     * Ultimately, it would be nice to eliminate most or all of these and find
     * the MooTools equivalent or convince MooTools to add them.
     *
     * NOTE: Many of these methods can be replaced with mootools-more's
     * Element.Measure
     */
    Element.implement({
        /**
         * APIMethod: getBoxSizing
         * return the box sizing of an element, one of 'content-box' or
         *'border-box'.
         *
         * Parameters:
         * elem - {Object} the element to get the box sizing of.
         *
         * Returns:
         * {String} the box sizing of the element.
         */
        getBoxSizing: function() {
            var result = 'content-box',
                cm,
                sizing;
            if (Browser.Engine.trident || Browser.Engine.presto) {
                cm = document["compatMode"];
                if (cm == "BackCompat" || cm == "QuirksMode") {
                    result = 'border-box';
                } else {
                    result = 'content-box';
                }
            } else {
                if (arguments.length === 0) {
                    node = document.documentElement;
                }
                sizing = this.getStyle("-moz-box-sizing");
                if (!sizing) {
                    sizing = this.getStyle("box-sizing");
                }
                result = (sizing ? sizing: 'content-box');
            }
            return result;
        },
        /**
         * APIMethod: getContentBoxSize
         * return the size of the content area of an element.  This is the
         * size of the element less margins, padding, and borders.
         *
         * Parameters:
         * elem - {Object} the element to get the content size of.
         *
         * Returns:
         * {Object} an object with two properties, width and height, that
         * are the size of the content area of the measured element.
         */
        getContentBoxSize: function() {
            var s = this.getSizes(['padding', 'border']);
            return {
                width: this.offsetWidth - s.padding.left - s.padding.right - s.border.left - s.border.right,
                height: this.offsetHeight - s.padding.bottom - s.padding.top - s.border.bottom - s.border.top
            };
        },
        /**
         * APIMethod: getBorderBoxSize
         * return the size of the border area of an element.  This is the size
         * of the element less margins.
         *
         * Parameters:
         * elem - {Object} the element to get the border sizing of.
         *
         * Returns:
         * {Object} an object with two properties, width and height, that
         * are the size of the border area of the measured element.
         */
        getBorderBoxSize: function() {
            return {
                width: this.offsetWidth,
                height: this.offsetHeight
            };
        },

        /**
         * APIMethod: getMarginBoxSize
         * return the size of the margin area of an element.  This is the size
         * of the element plus margins.
         *
         * Parameters:
         * elem - {Object} the element to get the margin sizing of.
         *
         * Returns:
         * {Object} an object with two properties, width and height, that
         * are the size of the margin area of the measured element.
         */
        getMarginBoxSize: function() {
            var s = this.getSizes(['margin']);
            return {
                width: this.offsetWidth + s.margin.left + s.margin.right,
                height: this.offsetHeight + s.margin.top + s.margin.bottom
            };
        },
        /**
         * APIMethod: getSizes
         * measure the size of various styles on various edges and return
         * the values.
         *
         * Parameters:
         * styles - array, the styles to compute.  By default, this is
         * ['padding', 'border','margin'].  If you don't need all the styles,
         * just request the ones you need to minimize compute time required.
         * edges - array, the edges to compute styles for.  By default,  this
         * is ['top','right','bottom','left'].  If you don't need all the
         * edges, then request the ones you need to minimize compute time.
         *
         * Returns:
         * {Object} an object with one member for each requested style.  Each
         * style member is an object containing members for each requested
         * edge. Values are the computed style for each edge in pixels.
         */
        getSizes: function(which, edges) {
            which = which || ['padding', 'border', 'margin'];
            edges = edges || ['left', 'top', 'right', 'bottom'];
            var result = {},
                e,
                n;
            which.each(function(style) {
                result[style] = {};
                edges.each(function(edge) {
                    e = (style == 'border') ? edge + '-width': edge;
                    n = this.getStyle(style + '-' + e);
                    result[style][edge] = n === null || isNaN(parseInt(n, 10)) ? 0: parseInt(n, 10);
                },
                this);
            },
            this);
            return result;
        },
        /**
         * APIMethod: setContentBoxSize
         * set either or both of the width and height of an element to
         * the provided size.  This function ensures that the content
         * area of the element is the requested size and the resulting
         * size of the element may be larger depending on padding and
         * borders.
         *
         * Parameters:
         * elem - {Object} the element to set the content area of.
         * size - {Object} an object with a width and/or height property that
         * is the size to set the content area of the element to.
         */
        setContentBoxSize: function(size) {
            var m,
                width,
                height;
            if (this.getBoxSizing() == 'border-box') {
                m = this.measure(function() {
                    return this.getSizes(['padding', 'border']);
                });
                if ($defined(size.width)) {
                    width = size.width + m.padding.left + m.padding.right + m.border.left + m.border.right;
                    if (width < 0) {
                        width = 0;
                    }
                    this.setStyle('width', width);
                }
                if ($defined(size.height)) {
                    height = size.height + m.padding.top + m.padding.bottom + m.border.top + m.border.bottom;
                    if (height < 0) {
                        height = 0;
                    }
                    this.setStyle('height', height);
                }
            } else {
                if ($defined(size.width) && size.width >= 0) {
                  this.setStyle('width', width);
                }
                if ($defined(size.height) && size.height >= 0) {
                  this.setStyle('height', height);
                }
            }
        },
        /**
         * APIMethod: setBorderBoxSize
         * set either or both of the width and height of an element to
         * the provided size.  This function ensures that the border
         * size of the element is the requested size and the resulting
         * content areaof the element may be larger depending on padding and
         * borders.
         *
         * Parameters:
         * elem - {Object} the element to set the border size of.
         * size - {Object} an object with a width and/or height property that
         * is the size to set the content area of the element to.
         */
        setBorderBoxSize: function(size) {
            var m,
                width,
                height;
            if (this.getBoxSizing() == 'content-box') {
                m = this.measure(function() {
                    return this.getSizes();
                });

                if ($defined(size.width)) {
                    width = size.width - m.padding.left - m.padding.right - m.border.left - m.border.right - m.margin.left - m.margin.right;
                    if (width < 0) {
                        width = 0;
                    }
                    this.setStyle('width', width);
                }
                if ($defined(size.height)) {
                    height = size.height - m.padding.top - m.padding.bottom - m.border.top - m.border.bottom - m.margin.top - m.margin.bottom;
                    if (height < 0) {
                        height = 0;
                    }
                    this.setStyle('height', height);
                }
            } else {
                if ($defined(size.width) && size.width >= 0) {
                  this.setStyle('width', width);
                }
                if ($defined(size.height) && size.height >= 0) {
                  this.setStyle('height', height);
                }
            }
        },

        /**
         * APIMethod: descendantOf
         * determines if the element is a descendent of the reference node.
         *
         * Parameters:
         * node - {HTMLElement} the reference node
         *
         * Returns:
         * {Boolean} true if the element is a descendent, false otherwise.
         */
        descendantOf: function(node) {
            var parent = document.id(this.parentNode);
            while (parent != node && parent && parent.parentNode && parent.parentNode != parent) {
                parent = document.id(parent.parentNode);
            }
            return parent == node;
        },

        /**
         * APIMethod: findElement
         * search the parentage of the element to find an element of the given
         * tag name.
         *
         * Parameters:
         * type - {String} the tag name of the element type to search for
         *
         * Returns:
         * {HTMLElement} the first node (this one or first parent) with the
         * requested tag name or false if none are found.
         */
        findElement: function(type) {
            var o = this,
                tagName = o.tagName;
            while (o.tagName != type && o && o.parentNode && o.parentNode != o) {
                o = document.id(o.parentNode);
            }
            return o.tagName == type ? o: false;
        }
    });
    /**
     * Class: Array
     * Extensions to the javascript array object
     */
    Array.implement({
        /**
         * APIMethod: swap
         * swaps 2 elements of an array
         *
         * Parameters:
         * a - the first position to swap
         * b - the second position to swap
         */
        'swap': function(a, b) {
            var temp = this[a];
            this[a] = this[b];
            this[b] = temp;
        }
    });
})(document.id || $);
// End Wrapper for document.id
/*
---

name: Jx.Styles

description: A singleton object useful for dynamically creating and manipulating CSS styles

license: MIT-style license.

requires:
 - Jx

provides: [Jx.Styles]

...
 */
/**
 * Class: Jx.Styles
 * Dynamic stylesheet class. Used for creating and manipulating dynamic
 * stylesheets.
 *
 * TBD: should we handle the case of putting the same selector in a stylesheet
 * twice?  Right now the code that stores the index of each rule on the
 * stylesheet is not really safe for that when combined with delete or get
 *
 * This is a singleton and should be called directly, like so:
 *
 * (code)
 *   // create a rule that turns all para text red and 15px.
 *   var rule = Jx.Styles.insertCssRule("p", "color: red;", "myStyle");
 *   rule.style.fontSize = "15px";
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 * Additional code by Paul Spencer
 *
 * This file is licensed under an MIT style license
 *
 * Inspired by dojox.html.styles, VisitSpy by nwhite,
 * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
 *
 */
Jx.Styles = new(new Class({
    /**
     * dynamicStyleMap - <Hash> used to keep a reference to dynamically
     * created style sheets for quick access
     */
    dynamicStyleMap: new Hash(),
    /**
     * APIMethod: getCssRule
     * retrieve a reference to a CSS rule in a specific style sheet based on
     * its selector.  If the rule does not exist, create it.
     *
     * Parameters:
     * selector - <String> the CSS selector for the rule
     * styleSheetName - <String> the name of the sheet to get the rule from
     *
     * Returns:
     * <CSSRule> - the requested rule
     */
    getCssRule: function(selector, styleSheetName) {
        var ss = this.getDynamicStyleSheet(styleSheetName),
            rule = null,
            i;
        if (ss.indicies) {
            i = ss.indicies.indexOf(selector);
            if (i == -1) {
                rule = this.insertCssRule(selector, '', styleSheetName);
            } else {
                if (Browser.Engine.trident) {
                    rule = ss.sheet.rules[i];
                } else {
                    rule = ss.sheet.cssRules[i];
                }
            }
        }
        return rule;
    },
    /**
     * APIMethod: insertCssRule
     * insert a new dynamic rule into the given stylesheet.  If no name is
     * given for the stylesheet then the default stylesheet is used.
     *
     * Parameters:
     * selector - <String> the CSS selector for the rule
     * declaration - <String> CSS-formatted rules to include.  May be empty,
     * in which case you may want to use the returned rule object to
     * manipulate styles
     * styleSheetName - <String> the name of the sheet to place the rules in,
     * or empty to put them in a default sheet.
     *
     * Returns:
     * <CSSRule> - a CSS Rule object with properties that are browser
     * dependent.  In general, you can use rule.styles to set any CSS
     * properties in the same way that you would set them on a DOM object.
     */
    insertCssRule: function (selector, declaration, styleSheetName) {
        var ss = this.getDynamicStyleSheet(styleSheetName),
            rule,
            text = selector + " {" + declaration + "}",
            index;
        if (Browser.Engine.trident) {
            if (declaration == '') {
                //IE requires SOME text for the declaration. Passing '{}' will
                //create an empty rule.
                declaration = '{}';
            }
            index = ss.styleSheet.addRule(selector,declaration);
            rule = ss.styleSheet.rules[index];
        } else {
            ss.sheet.insertRule(text, ss.indicies.length);
            rule = ss.sheet.cssRules[ss.indicies.length];
        }
        ss.indicies.push(selector);
        return rule;
    },
    /**
     * APIMethod: removeCssRule
     * removes a CSS rule from the named stylesheet.
     *
     * Parameters:
     * selector - <String> the CSS selector for the rule
     * styleSheetName - <String> the name of the sheet to remove the rule
     * from,  or empty to remove them from the default sheet.
     *
     * Returns:
     * <Boolean> true if the rule was removed, false if it was not.
     */
    removeCssRule: function (selector, styleSheetName) {
        var ss = this.getDynamicStyleSheet(styleSheetName),
            i = ss.indicies.indexOf(selector),
            result = false;
        ss.indicies.splice(i, 1);
        if (Browser.Engine.trident) {
            ss.removeRule(i);
            result = true;
        } else {
            ss.sheet.deleteRule(i);
            result = true;
        }
        return result;
    },
    /**
     * APIMethod: getDynamicStyleSheet
     * return a reference to a styleSheet based on its title.  If the sheet
     * does not already exist, it is created.
     *
     * Parameter:
     * name - <String> the title of the stylesheet to create or obtain
     *
     * Returns:
     * <StyleSheet> a StyleSheet object with browser dependent capabilities.
     */
    getDynamicStyleSheet: function (name) {
        name = (name) ? name : 'default';
        if (!this.dynamicStyleMap.has(name)) {
            var sheet = new Element('style').set('type', 'text/css').inject(document.head);
            sheet.indicies = [];
            this.dynamicStyleMap.set(name, sheet);
        }
        return this.dynamicStyleMap.get(name);
    },
    /**
     * APIMethod: enableStyleSheet
     * enable a style sheet
     *
     * Parameters:
     * name - <String> the title of the stylesheet to enable
     */
    enableStyleSheet: function (name) {
        this.getDynamicStyleSheet(name).disabled = false;
    },
    /**
     * APIMethod: disableStyleSheet
     * enable a style sheet
     *
     * Parameters:
     * name - <String> the title of the stylesheet to disable
     */
    disableStyleSheet: function (name) {
        this.getDynamicStyleSheet(name).disabled = true;
    },
    /**
     * APIMethod: removeStyleSheet
     * Removes a style sheet
     *
     * Parameters:
     * name = <String> the title of the stylesheet to remove
     */
    removeStyleSheet: function (name) {
      this.disableStyleSheet(name);
      this.getDynamicStyleSheet(name).dispose();
      this.dynamicStyleMap.erase(name);
    },
    /**
     * APIMethod: isStyleSheetDefined
     * Determined if the passed in name is a defined dynamic style sheet.
     *
     * Parameters:
     * name = <String> the title of the stylesheet to remove
     */
    isStyleSheetDefined: function (name) {
      return this.dynamicStyleMap.has(name);
    }
}))();/*
---

name: Jx.Object

description: Base class for all other object in the JxLib framework.

license: MIT-style license.

requires:
 - Jx

provides: [Jx.Object]

...
 */
// $Id: object.js 1000 2010-12-06 01:58:47Z jonlb@comcast.net $
/**
 * Class: Jx.Object
 * Base class for all other object in the JxLib framework. This class
 * implements both mootools mixins Events and Options so the rest of the
 * classes don't need to.
 *
 * The Initialization Pipeline:
 * Jx.Object provides a default initialize method to construct new instances
 * of objects that inherit from it.  No sub-class should override initialize
 * unless you know exactly what you're doing.  Instead, the initialization
 * pipeline provides an init() method that is intended to be overridden in
 * sub-classes to provide class-specific initialization as part of the
 * initialization pipeline.
 *
 * The basic initialization pipeline for a Jx.Object is to parse the
 * parameters provided to initialize(), separate out options from other formal
 * parameters based on the parameters property of the class, call init() and
 * initialize plugins.
 *
 * Parsing Parameters:
 * Because each sub-class no longer has an initialize method, it no longer has
 * direct access to parameters passed to the constructor.  Instead, a
 * sub-class is expected to provide a parameters attribute with an array of
 * parameter names in the order expected.  Jx.Object will enumerate the
 * attributes passed to its initialize method and automatically place them
 * in the options object under the appropriate key (the value from the
 * array).  Parameters not found will not be present or will be null.
 *
 * The default parameters are a single options object which is merged with
 * the options attribute of the class.
 *
 * Calling Init:
 * Jx.Object fires the event 'preInit' before calling the init() method,
 * calls the init() method, then fires the 'postInit' event.  It is expected
 * that most sub-class specific initialization will happen in the init()
 * method.  A sub-class may hook preInit and postInit events to perform tasks
 * in one of two ways.
 *
 * First, simply send onPreInit and onPostInit functions via the options
 * object as follows (they could be standalone functions or functions of
 * another object setup using .bind())
 *
 * (code)
 * var preInit = function () {}
 * var postInit = function () {}
 *
 * var options = {
 *   onPreInit: preInit,
 *   onPostInit: postInit,
 *   ...other options...
 * };
 *
 * var dialog = new Jx.Dialog(options);
 * (end)
 *
 * The second method you can use is to override the initialize method
 *
 * (code)
 * var MyClass = new Class({
 *   Family: 'MyClass',
 *   initialize: function() {
 *     this.addEvent('preInit', this.preInit.bind(this));
 *     this.addEvent('postInit', this.postInit.bind(this));
 *     this.parent.apply(this, arguments);
 *   },
 *   preInit: function() {
 *     // something just before init() is called
 *   },
 *   postInit: function() {
 *     // something just after init() is called
 *   },
 *   init: function() {
 *     this.parent();
 *     // initialization code here
 *   }
 * });
 * (end)
 *
 * When the object finishes initializing itself (including the plugin
 * initialization) it will fire off the initializeDone event. You can hook
 * into this event in the same way as the events mentioned above.
 *
 * Plugins:
 * Plugins provide pieces of additional, optional, functionality. They are not
 * necessary for the proper function of an object. All plugins should be
 * located in the Jx.Plugin namespace and they should be further segregated by
 * applicable object. While all objects can support plugins, not all of them
 * have the automatic instantiation of applicable plugins turned on. In order
 * to turn this feature on for an object you need to set the pluginNamespace
 * property of the object. The following is an example of setting the
 * property:
 *
 * (code)
 * var MyClass = new Class({
 *   Extends: Jx.Object,
 *   pluginNamespace: 'MyClass'
 * };
 * (end)
 *
 * The absence of this property does not mean you cannot attach a plugin to an
 * object. It simply means that you can't have Jx.Object create the
 * plugin for you.
 *
 * There are four ways to attach a plugin to an object. First, simply
 * instantiate the plugin yourself and call its attach() method (other class
 * options left out for the sake of simplicity):
 *
 * (code)
 * var MyGrid = new Jx.Grid();
 * var APlugin = new Jx.Plugin.Grid.Selector();
 * APlugin.attach(MyGrid);
 * (end)
 *
 * Second, you can instantiate the plugin first and pass it to the object
 * through the plugins array in the options object.
 *
 * (code)
 * var APlugin = new Jx.Plugin.Grid.Selector();
 * var MyGrid = new Jx.Grid({plugins: [APlugin]});
 * (end)
 *
 * The third way is to pass the information needed to instantiate the plugin
 * in the plugins array of the options object:
 *
 * (code)
 * var MyGrid = new Jx.Grid({
 *   plugins: [{
 *      name: 'Selector',
 *      options: {}    //options needed to create this plugin
 *   },{
 *      name: 'Sorter',
 *      options: {}
 *   }]
 * });
 * (end)
 *
 * The final way, if the plugin has no options, is to pass the name of the
 * plugin as a simple string in the plugins array.
 *
 * (code)
 * var MyGrid = new Jx.Grid({
 *   plugins: ['Selector','Sorter']
 * });
 * (end)
 *
 * Part of the process of initializing plugins is to call prePluginInit() and
 * postPluginInit(). These events provide you access to the object just before
 * and after the plugins are initialized and/or attached to the object using
 * methods 2 and 3 above. You can hook into these in the same way that you
 * hook into the preInit() and postInit() events.
 *
 * Destroying Jx.Object Instances:
 * Jx.Object provides a destroy method that cleans up potential memory leaks
 * when you no longer need an object.  Sub-classes are expected to implement
 * a cleanup() method that provides specific cleanup code for each
 * sub-class.  Remember to call this.parent() when providing a cleanup()
 * method. Destroy will also fire off 2 events: preDestroy and postDestroy.
 * You can hook into these methods in the same way as the init or plugin
 * events.
 *
 * The Family Attribute:
 * the Family attribute of a class is used internally by JxLib to identify Jx
 * objects within mootools.  The actual value of Family is unimportant to Jx.
 * If you do not provide a Family, a class will inherit it's base class family
 * up to Jx.Object.  Family is useful when debugging as you will be able to
 * identify the family in the firebug inspector, but is not as useful for
 * coding purposes as it does not allow for inheritance.
 *
 * Events:
 *
 * preInit
 * postInit
 * prePluginInit
 * postPluginInit
 * initializeDone
 * preDestroy
 * postDestroy
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Object = new Class({
    Family: "Jx.Object",
    Implements: [Options, Events],
    plugins: null,
    pluginNamespace: 'Other',
    /**
     * Constructor: Jx.Object
     * create a new instance of Jx.Object
     *
     * Parameters:
     * options - {Object} optional parameters for creating an object.
     */
    parameters: ['options'],

    options: {
      /**
       * Option: useLang
       * Turns on this widget's ability to react to changes in
       * the default language. Handy for changing text out on the fly.
       *
       * TODO: Should this be enabled or disabled by default?
       */
      useLang: true,
      /**
       * Option: plugins
       * {Array} an array of plugins to add to the object.
       */
      plugins: null
    },

    bound: null,

    initialize: function(){
        this.plugins = new Hash();
        this.bound = {};
        //normalize arguments
        var numArgs = arguments.length,
            options = {},
            parameters = this.parameters,
            numParams,
            index;

        if (numArgs > 0) {
            if (numArgs === 1
                    && (Jx.type(arguments[0])==='object' || Jx.type(arguments[0])==='Hash')
                    && parameters.length === 1
                    && parameters[0] === 'options') {
                options = arguments[0];
            } else {
                numParams = parameters.length;
                index;
                if (numParams <= numArgs) {
                    index = numParams;
                } else {
                    index = numArgs;
                }
                for (var i = 0; i < index; i++) {
                    if (parameters[i] === 'options') {
                        $extend(options, arguments[i]);
                    } else {
                        options[parameters[i]] = arguments[i];
                    }
                }
            }
        }

        this.setOptions(options);

        this.bound.changeText = this.changeText.bind(this);
        if (this.options.useLang) {
            MooTools.lang.addEvent('langChange', this.bound.changeText);
        }

        this.fireEvent('preInit');
        this.init();
        this.fireEvent('postInit');
        this.fireEvent('prePluginInit');
        this.initPlugins();
        this.fireEvent('postPluginInit');
        this.fireEvent('initializeDone');
    },

    /**
     * Method: initPlugins
     * internal function to initialize plugins on object creation
     */
    initPlugins: function () {
        var p;
        // pluginNamespace must be defined in order to pass plugins to the
        // object
        if ($defined(this.pluginNamespace)) {
            if ($defined(this.options.plugins)
                    && Jx.type(this.options.plugins) === 'array') {
                this.options.plugins.each(function (plugin) {
                    if (plugin instanceof Jx.Plugin) {
                        plugin.attach(this);
                        this.plugins.set(plugin.name, plugin);
                    } else if (Jx.type(plugin) === 'object') {
                        // All plugin-enabled objects should define a
                        // pluginNamespace member variable
                        // that is used for locating the plugins. The default
                        // namespace is 'Other' for
                        // now until we come up with a better idea
                      if ($defined(Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()])) {
                        p = new Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
                      } else {
                        p = new Jx.Adaptor[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
                      }
                        p.attach(this);
                    } else if (Jx.type(plugin) === 'string') {
                        //this is a name for a plugin.
                      if ($defined(Jx.Plugin[this.pluginNamespace][plugin.capitalize()])) {
                        p = new Jx.Plugin[this.pluginNamespace][plugin.capitalize()]();
                      } else {
                        p = new Jx.Adaptor[this.pluginNamespace][plugin.capitalize()]();
                      }
                        p.attach(this);
                    }
                }, this);
            }
        }
    },

    /**
     * APIMethod: destroy
     * destroy a Jx.Object, safely cleaning up any potential memory
     * leaks along the way.  Uses the cleanup method of an object to
     * actually do the cleanup.
     * Emits the preDestroy event before cleanup and the postDestroy event
     * after cleanup.
     */
    destroy: function () {
        this.fireEvent('preDestroy');
        this.cleanup();
        this.fireEvent('postDestroy');
    },

    /**
     * Method: cleanup
     * to be implemented by subclasses to do the actual work of destroying
     * an object.
     */
    cleanup: function () {
        //detach plugins
        if (this.plugins.getLength > 0) {
            this.plugins.each(function (plugin) {
                plugin.detach();
                plugin.destroy();
            }, this);
        }
        this.plugins.empty();
        if (this.options.useLang && $defined(this.bound.changeText)) {
            MooTools.lang.removeEvent('langChange', this.bound.changeText);
        }
        this.bound = null;
    },

    /**
     * Method: init
     * virtual initialization method to be implemented by sub-classes
     */
    init: $empty,

    /**
     * APIMethod: registerPlugin
     * This method is called by a plugin that has its attach method
     * called.
     *
     * Parameters:
     * plugin - the plugin to register with this object
     */
    registerPlugin: function (plugin) {
        if (!this.plugins.has(plugin.name)) {
            this.plugins.set(plugin.name,  plugin);
        }
    },
    /**
     * APIMethod: deregisterPlugin
     * his method is called by a plugin that has its detach method
     * called.
     *
     * Parameters:
     * plugin - the plugin to deregister.
     */
    deregisterPlugin: function (plugin) {
        if (this.plugins.has(plugin.name)) {
            this.plugins.erase(plugin.name);
        }
    },

    /**
     * APIMethod: getPlugin
     * Allows a developer to get a reference to a plugin with only the
     * name of the plugin.
     *
     * Parameters:
     * name - the name of the plugin as defined in the plugin's name property
     */
    getPlugin: function (name) {
        if (this.plugins.has(name)) {
            return this.plugins.get(name);
        }
    },

    /**
     * APIMethod: getText
     *
     * returns the text for a jx.widget used in a label.
     *
     * Parameters:
     * val - <String> || <Function> || <Object> = { set: '', key: ''[, value: ''] } for a MooTools.lang object
     */
    getText: function(val) {
      var result = '';
      if (Jx.type(val) == 'string' || Jx.type(val) == 'number') {
        result = val;
      } else if (Jx.type(val) == 'function') {
        result = val();
      } else if (Jx.type(val) == 'object' && $defined(val.set) && $defined(val.key)) {
        // COMMENT: just an idea how a localization object could be stored to the instance if needed somewhere else and options change?
        this.i18n = val;
        if($defined(val.value)) {
          result = MooTools.lang.get(val.set, val.key)[val.value];
        }else{
          result = MooTools.lang.get(val.set, val.key);
        }
      }
      return result;
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     *    translations changed.
     */
    changeText : $empty,

    /**
     * Method: generateId
     * Used to generate a unique ID for Jx Objects.
     */
    generateId: function(prefix){
        prefix = (prefix) ? prefix : 'jx-';
        var uid = $uid(this);
        delete this.uid;
        return prefix + uid;
    }
});
/*
---

name: Locale.English.US

description: Default translations of text strings used in JX for US english (en-US)

license: MIT-style license.

requires:
 - More/Lang

provides: [Locale.English.US]

...
 */
MooTools.lang.set('en-US', 'Jx', {

	'widget': {
		busyMessage: 'Working ...'
	},
	'colorpalette': {
		alphaLabel: 'alpha (%)'
	},
	notice: {
		closeTip: 'close this notice'
	},
	progressbar: {
		messageText: 'Loading...',
		progressText: '{progress} of {total}'
	},
	field: {
		requiredText: '*'
	},
	file: {
		browseLabel: 'Browse...'
	},
	'formatter.boolean': {
		'true': 'Yes',
		'false': 'No'
	},
	'formatter.currency': {
		sign: '$'
	},
	'formatter.number': {
		decimalSeparator: '.',
    thousandsSeparator: ','
	},
	splitter: {
		barToolTip: 'drag this bar to resize'
	},
  panelset: {
    barToolTip: 'drag this bar to resize'
  },
	panel: {
		collapseTooltip: 'Collapse/Expand Panel',
    collapseLabel: 'Collapse',
    expandLabel: 'Expand',
    maximizeTooltip: 'Maximize Panel',
    maximizeLabel: 'Maximize',
    restoreTooltip: 'Restore Panel',
    restoreLabel: 'Restore',
    closeTooltip: 'Close Panel',
    closeLabel: 'Close'
	},
	confirm: {
		affirmativeLabel: 'Yes',
    negativeLabel: 'No'
	},
	dialog: {
		resizeToolTip: 'Resize dialog'
	},
	message: {
		okButton: 'Ok'
	},
	prompt: {
		okButton: 'Ok',
		cancelButton: 'Cancel'
	},
	upload: {
		buttonText: 'Upload Files'
	},
	'plugin.resize': {
	  tooltip: 'Drag to resize, double click to auto-size.'
	},
  'plugin.editor': {
    submitButton: 'Save',
    cancelButton: 'Cancel'
  }
});/*
---

name: Jx.Widget

description: Base class for all widgets (visual classes) in the JxLib Framework.

license: MIT-style license.

requires:
 - Jx.Object
 - Jx.Stack
 - Locale.English.US

provides: [Jx.Widget]

css:
 - chrome

images:
 - spinner_16.gif
 - spinner_24.gif

optional:
 - More/Spinner

...
 */
// $Id: widget.js 1000 2010-12-06 01:58:47Z jonlb@comcast.net $
/**
 * Class: Jx.Widget
 * Base class for all widgets (visual classes) in the JxLib Framework. This
 * class extends <Jx.Object> and adds the Chrome, ContentLoader, Addable, and
 * AutoPosition mixins from the original framework.
 *
 * ContentLoader:
 *
 * ContentLoader functionality provides a consistent
 * mechanism for descendants of Jx.Widget to load content in one of
 * four different ways:
 *
 * o using an existing element, by id
 *
 * o using an existing element, by object reference
 *
 * o using an HTML string
 *
 * o using a URL to get the content remotely
 *
 * Chrome:
 *
 * Chrome is the extraneous visual element that provides the look and feel to
 * some elements i.e. dialogs.  Chrome is added inside the element specified
 * but may bleed outside the element to provide drop shadows etc.  This is
 * done by absolutely positioning the chrome objects in the container based on
 * calculations using the margins, borders, and padding of the jxChrome
 * class and the element it is added to.
 *
 * Chrome can consist of either pure CSS border and background colors, or
 * a background-image on the jxChrome class.  Using a background-image on
 * the jxChrome class creates four images inside the chrome container that
 * are positioned in the top-left, top-right, bottom-left and bottom-right
 * corners of the chrome container and are sized to fill 50% of the width
 * and height.  The images are positioned and clipped such that the
 * appropriate corners of the chrome image are displayed in those locations.
 *
 * Busy States:
 *
 * Any widget can be set as temporarily busy by calling the setBusy(true)
 * method and then as idle by calling setBusy(false).  By default, busy
 * widgets display an event mask that prevents them from being clicked and
 * a spinner image with a message.  By default, there are two configurations
 * for the spinner image and message, one for 'small' widgets like buttons
 * and inputs, and one for larger widgets like panels and dialogs.  The
 * framework automatically chooses the most appropriate configuration so you
 * don't need to worry about it unless you want to customize it.
 *
 * You can disable this behaviour entirely by setting busyMask: false in the
 * widget options when creating the widget.
 *
 * The mask and spinner functionality is provided by the MooTools Spinner
 * class.  You can use any options documented for Spinner or Mask by setting
 * the maskOptions option when creating a widget.
 *
 * Events:
 * Jx.Widget has several events called during it's lifetime (in addition to
 * the ones for its base class <Jx.Object>).
 *
 * preRender - called before rendering begins
 * postRender - called after rendering is done
 * deferRender - called when the deferRender option is set to true. The first
 *      two events (pre- and post- render will NOT be called if deferRender is
 *      set to true).
 * contentLoaded - called after content has been loaded successfully
 * contentLoadFailed - called if content can not be loaded for some reason
 * addTo - called when a widget is added to another element or widget
 * busy - called just before the busy mask is rendered/removed
 *
 * MooTools.Lang Keys:
 * widget.busyMessage - sets the message of the waiter component when used
 */
Jx.Widget = new Class({
    Family: "Jx.Widget",
    Extends: Jx.Object,

    options: {
        /* Option: id
         * (optional) {String} an HTML ID to assign to the widget
         */
        id: null,
        /**
         * Option: content
         * content may be an HTML element reference, the id of an HTML element
         * already in the DOM, or an HTML string that becomes the inner HTML
         * of the element.
         */
        content: null,
        /**
         * Option: contentURL
         * the URL to load content from
         */
        contentURL: null,
        /**
         * Option: loadOnDemand
         * {boolean} ajax content will only be loaded if the action is requested
         * (like loading the content into a tab when activated)
         */
        loadOnDemand : false,
        /**
         * Option: cacheContent
         * {boolean} determine whether the content should be loaded every time
         * or if it's being cached
         */
        cacheContent : true,
        /**
         * Option: template
         * the default HTML structure of this widget.  The default template
         * is just a div with a class of jxWidget in the base class
         */
        template: '<div class="jxWidget"></div>',
        /**
         * Option: busyClass
         * {String} a CSS class name to apply to busy mask when a widget is
         * set as busy.  The default is 'jxBusy'.
         */
        busyClass: 'jxBusy',
        /**
         * Option: busyMask
         * {Object} an object of options to pass to the MooTools Spinner
         * when masking a busy object.  Set to false if you do not want
         * to use the busy mask.
         */
        busyMask: {
          'class': 'jxSpinner jxSpinnerLarge',
          img: {'class':'jxSpinnerImage'},
          content: {'class':'jxSpinnerContent'},
          messageContainer: {'class':'jxSpinnerMessage'},
          useIframeShim: true,
          iframeShimOptions: {
            className: 'jxIframeShim'
          },
          fx: true
        },
        /**
         * Option: deferRender
         * Used to defer rendering of a widget to a later time. Useful when
         * we need data or other information not at hand at the moment
         * of Widget instantiation. If set to true, the user will need to call
         * render() at some later time. The only drawback to doing so will be
         * the loss of preRender and postRender events.
         */
        deferRender: false
    },

    /**
     * Property: classes
     * {<Hash>} a hash of object properties to CSS class names used to
     * automatically extract references to important DOM elements when
     * processing a widget template.  This allows developers to provide custom
     * HTML structures without affecting the functionality of widgets.
     */
    classes: new Hash({
        domObj: 'jxWidget'
    }),

    /**
     * Property: busy
     * {Boolean} is the widget currently busy?  This should be considered
     * an internal property, use the API methods <Jx.Widget::setBusy> and
     * <Jx.Widget::isBusy> to manage the busy state of a widget.
     */
    busy: false,

    /**
     * Property: domObj
     * The HTMLElement that represents this widget.
     */
    domObj: null,

    /**
     * Property: contentIsLoaded
     * {Boolean} tracks the load state of the content, specifically useful
     * in the case of remote content.
     */
    contentIsLoaded: false,

    /**
     * Property: chrome
     * the DOM element that contains the chrome
     */
    chrome: null,

    /**
     * Method: init
     * sets up the base widget code and runs the render function.  Called
     * by the Jx.Object framework for object initialization, should not be
     * called directly.
     */
    init: function(){
        if (!this.options.deferRender) {
            this.fireEvent('preRender');
            this.render();
            this.fireEvent('postRender');
        } else {
            this.fireEvent('deferRender');
        }
    },

    /**
     * APIMethod: loadContent
     *
     * triggers loading of content based on options set for the current
     * object.
     *
     * Parameters:
     * element - {Object} the element to insert the content into
     *
     * Events:
     *
     * ContentLoader adds the following events to an object.  You can
     * register for these events using the addEvent method or by providing
     * callback functions via the on{EventName} properties in the options
     * object
     *
     * contentLoaded - called when the content has been loaded.  If the
     *     content is not asynchronous then this is called before loadContent
     *     returns.
     * contentLoadFailed - called if the content fails to load, primarily
     *     useful when using the contentURL method of loading content.
     */
    loadContent: function(element) {
        var c,
            options = this.options,
            timeout;
        element = document.id(element);
        if (options.content) {
            if (options.content.domObj) {
                c = document.id(options.content.domObj);
            } else {
                c = document.id(options.content);
            }
            if (c) {
                if (options.content.addTo) {
                    options.content.addTo(element);
                } else {
                    element.appendChild(c);
                }
                this.contentIsLoaded = true;
            } else {
                element.innerHTML = options.content;
                this.contentIsLoaded = true;
            }
        } else if (options.contentURL) {
            this.contentIsLoaded = false;
            this.req = new Request({
                url: options.contentURL,
                method:'get',
                evalScripts:true,
                onRequest:(function() {
                  if(options.loadOnDemand) {
                      this.setBusy(true);
                  }
                }).bind(this),
                onSuccess:(function(html) {
                    element.innerHTML = html;
                    this.contentIsLoaded = true;
                    if (Jx.isAir){
                        $clear(this.reqTimeout);
                    }
                    this.setBusy(false);
                    this.fireEvent('contentLoaded', this);
                }).bind(this),
                onFailure: (function(){
                    this.contentIsLoaded = true;
                    this.fireEvent('contentLoadFailed', this);
                    this.setBusy(false);
                }).bind(this),
                headers: {'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT'}
            });
            this.req.send();
            if (Jx.isAir) {
                timeout = $defined(options.timeout) ? options.timeout : 10000;
                this.reqTimeout = this.checkRequest.delay(timeout, this);
            }
        } else {
            this.contentIsLoaded = true;
        }
        if (options.contentId) {
            element.id = this.options.contentId;
        }
        if (this.contentIsLoaded) {
            this.fireEvent('contentLoaded', this);
        }
    },

    /**
     * APIMethod: position
     * positions an element relative to another element
     * based on the provided options.  Positioning rules are
     * a string with two space-separated values.  The first value
     * references the parent element and the second value references
     * the thing being positioned.  In general, multiple rules can be
     * considered by passing an array of rules to the horizontal and
     * vertical options.  The position method will attempt to position
     * the element in relation to the relative element using the rules
     * specified in the options.  If the element does not fit in the
     * viewport using the rule, then the next rule is attempted.  If
     * all rules fail, the last rule is used and element may extend
     * outside the viewport.  Horizontal and vertical rules are
     * processed independently.
     *
     * Horizontal Positioning:
     * Horizontal values are 'left', 'center', 'right', and numeric values.
     * Some common rules are:
     * o 'left left' is interpreted as aligning the left
     * edge of the element to be positioned with the left edge of the
     * reference element.
     * o 'right right' aligns the two right edges.
     * o 'right left' aligns the left edge of the element to the right of
     * the reference element.
     * o 'left right' aligns the right edge of the element to the left
     * edge of the reference element.
     *
     * Vertical Positioning:
     * Vertical values are 'top', 'center', 'bottom', and numeric values.
     * Some common rules are:
     * o 'top top' is interpreted as aligning the top
     * edge of the element to be positioned with the top edge of the
     * reference element.
     * o 'bottom bottom' aligns the two bottom edges.
     * o 'bottom top' aligns the top edge of the element to the bottom of
     * the reference element.
     * o 'top bottom' aligns the bottom edge of the element to the top
     * edge of the reference element.
     *
     * Parameters:
     * element - the element to position
     * relative - the element to position relative to
     * options - the positioning options, see list below.
     *
     * Options:
     * horizontal - the horizontal positioning rule to use to position the
     *    element.  Valid values are 'left', 'center', 'right', and a numeric
     *    value.  The default value is 'center center'.
     * vertical - the vertical positioning rule to use to position the
     *    element.  Valid values are 'top', 'center', 'bottom', and a numeric
     *    value.  The default value is 'center center'.
     * offsets - an object containing numeric pixel offset values for the
     *    object being positioned as top, right, bottom and left properties.
     */
    position: function(element, relative, options) {
        element = document.id(element);
        relative = document.id(relative);
        var hor = $splat(options.horizontal || ['center center']),
            ver = $splat(options.vertical || ['center center']),
            offsets = $merge({top:0,right:0,bottom:0,left:0}, options.offsets || {}),
            coords = relative.getCoordinates(), //top, left, width, height,
            page,
            scroll,
            size,
            left,
            rigbht,
            top,
            bottom,
            n,
            parts;
        if (!document.id(element.parentNode) || element.parentNode ==  document.body) {
            page = Jx.getPageDimensions();
            scroll = document.id(document.body).getScroll();
        } else {
            page = document.id(element.parentNode).getContentBoxSize(); //width, height
            scroll = document.id(element.parentNode).getScroll();
        }
        if (relative == document.body) {
            // adjust coords for the scroll offsets to make the object
            // appear in the right part of the page.
            coords.left += scroll.x;
            coords.top += scroll.y;
        } else if (element.parentNode == relative) {
            // if the element is opening *inside* its relative, we want
            // it to position correctly within it so top/left becomes
            // the reference system.
            coords.left = 0;
            coords.top = 0;
        }
        size = element.getMarginBoxSize(); //width, height
        if (!hor.some(function(opt) {
            parts = opt.split(' ');
            if (parts.length != 2) {
                return false;
            }
            if (!isNaN(parseInt(parts[0],10))) {
                n = parseInt(parts[0],10);
                if (n>=0) {
                    left = n;
                } else {
                    left = coords.left + coords.width + n;
                }
            } else {
                switch(parts[0]) {
                    case 'right':
                        left = coords.left + coords.width;
                        break;
                    case 'center':
                        left = coords.left + Math.round(coords.width/2);
                        break;
                    case 'left':
                    default:
                        left = coords.left;
                        break;
                }
            }
            if (!isNaN(parseInt(parts[1],10))) {
                n = parseInt(parts[1],10);
                if (n<0) {
                    right = left + n;
                    left = right - size.width;
                } else {
                    left += n;
                    right = left + size.width;
                }
                right = coords.left + coords.width + parseInt(parts[1],10);
                left = right - size.width;
            } else {
                switch(parts[1]) {
                    case 'left':
                        left -= offsets.left;
                        right = left + size.width;
                        break;
                    case 'right':
                        left += offsets.right;
                        right = left;
                        left = left - size.width;
                        break;
                    case 'center':
                    default:
                        left = left - Math.round(size.width/2);
                        right = left + size.width;
                        break;
                }
            }
            return (left >= scroll.x && right <= scroll.x + page.width);
        })) {
            // all failed, snap the last position onto the page as best
            // we can - can't do anything if the element is wider than the
            // space available.
            if (right > page.width) {
                left = scroll.x + page.width - size.width;
            }
            if (left < 0) {
                left = 0;
            }
        }
        element.setStyle('left', left);

        if (!ver.some(function(opt) {
          parts = opt.split(' ');
          if (parts.length != 2) {
            return false;
          }
          if (!isNaN(parseInt(parts[0],10))) {
            top = parseInt(parts[0],10);
          } else {
            switch(parts[0]) {
              case 'bottom':
                top = coords.top + coords.height;
                break;
              case 'center':
                top = coords.top + Math.round(coords.height/2);
                break;
              case 'top':
              default:
                top = coords.top;
                break;
            }
          }
          if (!isNaN(parseInt(parts[1],10))) {
              var n = parseInt(parts[1],10);
              if (n>=0) {
                  top += n;
                  bottom = top + size.height;
              } else {
                  bottom = top + n;
                  top = bottom - size.height;
              }
          } else {
              switch(parts[1]) {
                  case 'top':
                      top -= offsets.top;
                      bottom = top + size.height;
                      break;
                  case 'bottom':
                      top += offsets.bottom;
                      bottom = top;
                      top = top - size.height;
                      break;
                  case 'center':
                  default:
                      top = top - Math.round(size.height/2);
                      bottom = top + size.height;
                      break;
              }
          }
          return (top >= scroll.y && bottom <= scroll.y + page.height);
      })) {
          // all failed, snap the last position onto the page as best
          // we can - can't do anything if the element is higher than the
          // space available.
          if (bottom > page.height) {
              top = scroll.y + page.height - size.height;
          }
          if (top < 0) {
              top = 0;
          }
      }
      element.setStyle('top', top);

      /* update the jx layout if necessary */
      var jxl = element.retrieve('jxLayout');
      if (jxl) {
          jxl.options.left = left;
          jxl.options.top = top;
      }
    },

    /**
     * Method: makeChrome
     * create chrome on an element.
     *
     * Parameters:
     * element - {HTMLElement} the element to put the chrome on.
     */
    makeChrome: function(element) {
        var c = new Element('div', {
                'class':'jxChrome',
                events: {
                  contextmenu: function(e) { e.stop(); }
                }
              }),
            src;

        /* add to element so we can get the background image style */
        element.adopt(c);

        /* pick up any offset because of chrome, set
         * through padding on the chrome object.  Other code can then
         * make use of these offset values to fix positioning.
         */
        this.chromeOffsets = c.measure(function() {
            return this.getSizes(['padding']).padding;
        });
        c.setStyle('padding', 0);

        /* get the chrome image from the background image of the element */
        /* the app: protocol check is for adobe air support */
        src = c.getStyle('backgroundImage');
        if (src != null) {
          if (!(src.contains('http://') || src.contains('https://') || src.contains('file://') || src.contains('app:/'))) {
              src = null;
          } else {
              src = src.slice(4,-1);
              /* this only seems to be IE and Opera, but they add quotes
               * around the url - yuck
               */
              if (src.charAt(0) == '"') {
                  src = src.slice(1,-1);
              }

              /* and remove the background image */
              c.setStyle('backgroundImage', 'none');

              /* make chrome */
              ['TR','TL','BL','BR'].each(function(s){
                  c.adopt(
                      new Element('div',{
                          'class':'jxChrome'+s
                      }).adopt(
                      new Element('img',{
                          'class':'png24',
                          src:src,
                          alt: '',
                          title: ''
                      })));
              }, this);
          }
        }
        /* create a shim so selects don't show through the chrome */
        if ($defined(window.IframeShim)) {
          this.shim = new IframeShim(c, {className: 'jxIframeShim'});
        }

        /* remove from DOM so the other resizing logic works as expected */
        c.dispose();
        this.chrome = c;
    },

    /**
     * APIMethod: showChrome
     * show the chrome on an element.  This creates the chrome if necessary.
     * If the chrome has been previously created and not removed, you can
     * call this without an element and it will just resize the chrome within
     * its existing element.  You can also pass in a different element from
     * which the chrome was previously attached to and it will move the chrome
     * to the new element.
     *
     * Parameters:
     * element - {HTMLElement} the element to show the chrome on.
     */
    showChrome: function(element) {
        element = document.id(element) || document.id(this);
        if (element) {
            if (!this.chrome) {
                this.makeChrome(element);
                element.addClass('jxHasChrome');
            }
            this.resizeChrome(element);
            if (this.shim) {
              this.shim.show();
            }
            if (element && this.chrome.parentNode !== element) {
                element.adopt(this.chrome);
                this.chrome.setStyle('z-index',-1);
            }
        }
    },

    /**
     * APIMethod: hideChrome
     * removes the chrome from the DOM.  If you do this, you can't
     * call showChrome with no arguments.
     */
    hideChrome: function() {
        if (this.chrome) {
            if (this.shim) {
              this.shim.hide();
            }
            this.chrome.parentNode.removeClass('jxHasChrome');
            this.chrome.dispose();
        }
    },

    /**
     * APIMethod: resizeChrome
     * manually resize the chrome on an element.
     *
     * Parameters:
     * element: {DOMElement} the element to resize the chrome for
     */
    resizeChrome: function(o) {
        if (this.chrome && Browser.Engine.trident4) {
            this.chrome.setContentBoxSize(document.id(o).getBorderBoxSize());
            if (this.shim) {
              this.shim.position();
            }
        }
    },

    /**
     * APIMethod: addTo
     * adds the object to the DOM relative to another element.  If you use
     * 'top' or 'bottom' then the element is added to the relative
     * element (becomes a child node).  If you use 'before' or 'after'
     * then the element is inserted adjacent to the reference node.
     *
     * Parameters:
     * reference - {Object} the DOM element or id of a DOM element
     * to append the object relative to
     * where - {String} where to append the element in relation to the
     * reference node.  Can be 'top', 'bottom', 'before' or 'after'.
     * The default is 'bottom'.
     *
     * Returns:
     * the object itself, which is useful for chaining calls together
     */
    addTo: function(reference, where) {
        var el = document.id(this.addable) || document.id(this.domObj);
        if (el) {
            if (reference instanceof Jx.Widget && $defined(reference.add)) {
                reference.add(el);
            } else {
                ref = document.id(reference);
                el.inject(ref,where);
            }
            this.fireEvent('addTo',this);
        }
        return this;
    },

    /**
     * APIMethod: toElement
     * return a DOM element reference for this widget, by default this
     * returns the local domObj reference.  This is used by the mootools
     * framework with the document.id() or $() methods allowing you to
     * manipulate a Jx.Widget sub class as if it were a DOM element.
     *
     * (code)
     * var button = new Jx.Button({label: 'test'});
     * $(button).inject('someElement');
     * (end)
     */
    toElement: function() {
        return this.domObj;
    },

    /**
     * APIMethod: processTemplate
     * This function pulls the needed elements from a provided template
     *
     * Parameters:
     * template - the template to use in grabbing elements
     * classes - an array of class names to use in grabbing elements
     * container - the container to add the template into
     *
     * Returns:
     * a hash object containing the requested Elements keyed by the class
     * names
     */
    processTemplate: function(template,classes,container){
        var h = new Hash(),
            element,
            el;
        if ($defined(container)){
            element = container.set('html',template);
        } else {
            element = new Element('div',{html:template});
        }
        classes.each(function(klass){
            el = element.getElement('.'+klass);
            if ($defined(el)){
                h.set(klass,el);
            }
        });
        return h;
    },

    /**
     * APIMethod: dispose
     * remove the widget from the DOM
     */
    dispose: function(){
        var el = document.id(this.addable) || document.id(this.domObj);
        if (el) {
            el.dispose();
        }
    },

    /**
     * Method: cleanup
     * destroy the widget and clean up any potential memory leaks
     */
    cleanup: function(){
        if ($defined(this.domObj)) {
            this.domObj.eliminate('jxWidget');
            this.domObj.destroy();
        }
        if ($defined(this.addable)) {
            this.addable.destroy();
        }
        if ($defined(this.domA)) {
            this.domA.destroy();
        }
        if ($defined(this.classes)) {
          this.classes.each(function(v, k) {
            this[k] = null;
          }, this);
        }
        this.elements.empty();
        this.elements = null;
        this.parent();
    },

    /**
     * Method: render
     * render the widget, internal function called by the framework.
     */
    render: function() {
        this.elements = this.processElements(this.options.template,
            this.classes);
        if ($defined(this.domObj)) {
          if ( $defined(this.options.id)) {
            this.domObj.set('id', this.options.id);
          }
          //TODO: Should we autogenerate an id when one is not provided? like so...
          // this.domObj.set('id',this.generateId());
          this.domObj.store('jxWidget', this);
        }
    },

    /**
     * Property: elements
     * a hash of elements extracted by processing the widget template
     */
    elements: null,

    /**
     * Method: processElements
     * process the template of the widget and populate the elements hash
     * with any objects.  Also set any object references based on the classes
     * hash.
     */
    processElements: function(template, classes) {
        var keys = classes.getValues();
        elements = this.processTemplate(template, keys);
        classes.each(function(value, key) {
            if (key != 'elements' && elements.get(value)) {
                this[key] = elements.get(value);
            }
        }, this);
        return elements;
    },

    /**
     * APIMethod: isBusy
     * indicate if the widget is currently busy or not
     *
     * Returns:
     * {Boolean} true if busy, false otherwise.
     */
    isBusy: function() {
      return this.busy;
    },

    /**
     * APIMethod: setBusy
     * set the busy state of the widget
     *
     * Parameters:
     * busy         - {Boolean} true to set the widget as busy, false to set it as idle.
     * message      - {String||Jx Localized Object} (Optional) set a custom message directly
     *                next to the loading icon. Default is {set:'Jx',key:'widget',value:'busyMessage'}
     * forceMessage - {Boolean} force displaying a message for larger areas than 60px of height
     */
    setBusy: function(state, message, forceMessage) {
      if (this.busy == state) {
        return;
      }
      var options = this.options,
          z,
          size,
          opts,
          domObj = this.domObj;
      message = $defined(message) ? message : {
        set:'Jx',
        key:'widget',
        value:'busyMessage'
      };
      forceMessage = $defined(forceMessage) ? forceMessage : false;
      this.busy = state;
      this.fireEvent('busy', state);
      if (state) {
        if (options.busyClass) {
          domObj.addClass(options.busyClass);
        }
        if (options.busyMask && domObj.spin) {
          /* put the spinner above the element in the z-index */
          z = Jx.getNumber(domObj.getStyle('z-index'));
          opts = {
            style: {
              'z-index': z+1
            }
          };
          /* switch to the small size if the element is less than
           * 60 pixels high
           */
          size = domObj.getBorderBoxSize();
          if (size.height < 60 || forceMessage) {
            opts['class'] = 'jxSpinner jxSpinnerSmall';
            opts.img = null;
            opts.message = new Element('p',{
              'class':'jxSpinnerMessage',
              html: '<span class="jxSpinnerImage"></span>'+this.getText(message)
            });
          }
          opts = $merge(options.busyMask, opts);
          domObj.get('spinner', opts).show(!options.busyMask.fx);
        }
      } else {
        if (options.busyClass) {
          domObj.removeClass(options.busyClass);
        }
        if (options.busyMask && this.domObj.unspin) {
          domObj.get('spinner').hide(!options.busyMask.fx);
        }
      }
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - {string} the language being changed to or that had it's data set of
     *    translations changed.
     */
    changeText: function (lang) {
        //if the mask is being used then recreate it. The code will pull
        //the new text automatically
        if (this.busy) {
            this.setBusy(false);
            this.setBusy(true);
        }
    },

    /**
     * APIMethod: stack
     * stack this widget in the z-index of the DOM relative to other stacked
     * objects.
     *
     * Parameters:
     * el - {DOMElement} optional, the element to stack.  By default, the
     * element to stack is the one returned by the toElement method which
     * is typically this.domObj unless the method has been overloaded.
     */
    stack: function(el) {
      Jx.Stack.stack(el || document.id(this));
    },

    /**
     * APIMethod: unstack
     * remove this widget from the stack.
     *
     * Parameters:
     * el - {DOMElement} optional, the element to unstack.  By default, the
     * element to unstack is the one returned by the toElement method which
     * is typically this.domObj unless the method has been overloaded.
     */
    unstack: function(el) {
      Jx.Stack.unstack(el = el || document.id(this));
    }
});


/**
 * It seems AIR never returns an XHR that "fails" by not finding the
 * appropriate file when run in the application sandbox and retrieving a local
 * file. This affects Jx.ContentLoader in that a "failed" event is never fired.
 *
 * To fix this, I've added a timeout that waits about 10 seconds or so in the code above
 * for the XHR to return, if it hasn't returned at the end of the timeout, we cancel the
 * XHR and fire the failure event.
 *
 * This code only gets added if we're in AIR.
 */
if (Jx.isAir){
    Jx.Widget.implement({
        /**
         * Method: checkRequest
         * Is fired after a delay to check the request to make sure it's not
         * failing in AIR.
         */
        checkRequest: function(){
            if (this.req.xhr.readyState === 1) {
                //we still haven't gotten the file. Cancel and fire the
                //failure
                $clear(this.reqTimeout);
                this.req.cancel();
                this.contentIsLoaded = true;
                this.fireEvent('contentLoadFailed', this);
            }
        }
    });
}/*
---

name: Jx.Selection

description: A class to manage selection across multiple list objects

license: MIT-style license.

requires:
 - Jx.Object

provides: [Jx.Selection]

...
 */
// $Id: selection.js 976 2010-09-02 18:57:12Z pagameba $
/**
 * Class: Jx.Selection
 *
 * Manage selection of objects.
 *
 * Example:
 * (code)
 * var selection = new Jx.Selection();
 * (end)
 *
 * Events:
 * select - fired when an item is added to the selection.  This event may be
 *    changed by passing the eventToFire option when creating the selection
 *    object.
 * unselect - fired when an item is removed from the selection.  This event
 *    may be changed by passing the eventToFire option when creating the
 *    selection object.
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */


Jx.Selection = new Class({
    Family: 'Jx.Selection',
    Extends: Jx.Object,
    options: {
        /**
         * Option: eventToFire
         * Allows the developer to change the event that is fired in case one
         * object is using multiple selectionManager instances.  The default
         * is to use 'select' and 'unselect'.  To modify the event names,
         * pass different values:
         * (code)
         * new Jx.Selection({
         *   eventToFire: {
         *     select: 'newSelect',
         *     unselect: 'newUnselect'
         *   }
         * });
         * (end)
         */
        eventToFire: {
            select: 'select',
            unselect: 'unselect'
        },
        /**
         * APIProperty: selectClass
         * the CSS class name to add to the wrapper element when it is
         * selected
         */
        selectClass: 'jxSelected',
        /**
         * Option: selectMode
         * {string} default single.  May be single or multiple.  In single
         * mode only one item may be selected.  Selecting a new item will
         * implicitly unselect the currently selected item.
         */
        selectMode: 'single',
        /**
         * Option: selectToggle
         * {Boolean} Default true.  Selection of a selected item will unselect
         * it.
         */
        selectToggle: true,
        /**
         * Option: minimumSelection
         * {Integer} Default 0.  The minimum number of items that must be
         * selected.  If set to a number higher than 0, items added to a list
         * are automatically selected until this minimum is met.  The user may
         * not unselect items if unselecting them will drop the total number
         * of items selected below the minimum.
         */
        minimumSelection: 0
    },

    /**
     * Property: selection
     * {Array} an array holding the current selection
     */
    selection: null,

    /**
     * Constructor: Jx.Selection
     * create a new instance of Jx.Selection
     *
     * Parameters:
     * options - {Object} options for the new instance
     */
    init: function () {
        this.selection = [];
        this.parent();
    },

    cleanup: function() {
      this.selection = null;
      this.parent();
    },

    /**
     * APIMethod: defaultSelect
     * select an item if the selection does not yet contain the minimum
     * number of selected items.  Uses <Jx.Selection::select> to select
     * the item, so the same criteria is applied to the item if it is
     * to be selected.
     */
    defaultSelect: function(item) {
        if (this.selection.length < this.options.minimumSelection) {
            this.select(item);
        }
    },

    /**
     * APIMethod: select
     * select an item.
     *
     * Parameters:
     * item - {DOMElement} a DOM element or an element ID.
     */
    select: function (item) {
        var options = this.options,
            selection = this.selection;
        item = document.id(item);
        if (options.selectMode === 'multiple') {
            if (selection.contains(item)) {
                this.unselect(item);
            } else {
                document.id(item).addClass(options.selectClass);
                selection.push(item);
                this.fireEvent(options.eventToFire.select, item);
            }
        } else if (options.selectMode == 'single') {
            if (!this.selection.contains(item)) {
                document.id(item).addClass(options.selectClass);
                selection.push(item);
                if (selection.length > 1) {
                    this.unselect(selection[0]);
                }
                this.fireEvent(options.eventToFire.select, item);
            } else {
                if (options.selectToggle) {
                  this.unselect(item);
                }
            }
        }
    },

    /**
     * APIMethod: unselect
     * remove an item from the selection.  The item must already be in the
     * selection.
     *
     * Parameters:
     * item - {DOMElement} a DOM element or an element ID.
     */
    unselect: function (item) {
        var selection = this.selection,
            options = this.options;
        if (selection.contains(item) &&
            selection.length > options.minimumSelection) {
            document.id(item).removeClass(options.selectClass);
            selection.erase(item);
            this.fireEvent(options.eventToFire.unselect, [item, this]);
        }
    },

    /**
     * APIMethod: selected
     * returns the items in the current selection.
     *
     * Returns:
     * {Array} an array of DOM elements in the current selection
     */
    selected: function () {
        return this.selection;
    },

    /**
     * APIMethod: isSelected
     * test if an item is in the current selection.
     *
     * Parameters:
     * item - {DOMElement} a DOM element or an element ID.
     *
     * Returns:
     * {Boolean} true if the current selection contains the item, false
     * otherwise
     */
    isSelected: function(item) {
        return this.selection.contains(item);
    }
});/*
---

name: Jx.List

description: A class that is used to manage lists of DOM elements

license: MIT-style license.

requires:
 - Jx.Object
 - Jx.Selection

provides: [Jx.List]

...
 */
// $Id: list.js 976 2010-09-02 18:57:12Z pagameba $
/**
 * Class: Jx.List
 *
 * Manage a list of DOM elements and provide an API and events for managing
 * those items within a container.  Works with Jx.Selection to manage
 * selection of items in the list.  You have two options for managing
 * selections.  The first, and default, option is to specify select: true
 * in the constructor options and any of the <Jx.Selection> options as well.
 * This will create a default Jx.Selection object to manage selections.  The
 * second option is to pass a Jx.Selection object as the third constructor
 * argument.  This allows sharing selection between multiple lists.
 *
 * Example:
 * (code)
 * var list = new Jx.List('container',{
 *   hover: true,
 *   select: true,
 *   onSelect: function(el) {
 *     alert(el.get('html'));
 *   }
 * });
 * list.add(new Element('li', {html:'1'}));
 * list.add(new Element('li', {html:'2'}));
 * list.add(new Element('li', {html:'3'}));
 *
 * (end)
 *
 * Events:
 * add - fired when an item is added
 * remove - fired when an item is removed
 * mouseenter - fired when the user mouses over an element
 * mouseleave - fired when the user mouses out of an element
 * select - fired when an item is selected
 * unselect - fired when an item is selected
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.List = new Class({
    Family: 'Jx.List',
    Extends: Jx.Object,
    /**
     * Constructor: Jx.List
     * create a new instance of Jx.List
     *
     * Parameters:
     * container - {Mixed} an element reference or id of an element that will
     * contain the items in the list
     * options - {Object} an object containing optional parameters
     * selection - {<Jx.Selection>} null or a Jx.Selection object. If the
     * select option is set to true, then list will use this selection object
     * to track selections or create its own if no selection object is
     * supplied.
     */
    parameters: ['container', 'options', 'selection'],
    /* does this object own the selection object (and should clean it up) */
    ownsSelection: false,
    /**
     * APIProperty: container
     * the element that will contain items as they are added
     */
    container: null,
    /**
     * APIProperty: selection
     * <Jx.Selection> a selection object if selection is enabled
     */
    selection: null,
    options: {
        /**
         * Option: items
         * an array of items to add to the list right away
         */
        items: null,
        /**
         * Option: hover
         * {Boolean} default false.  If set to true, the wrapper element will
         * obtain the defined hoverClass if set and mouseenter/mouseleave
         * events will be emitted when the user hovers over and out of elements
         */
        hover: false,
        /**
         * Option: hoverClass
         * the CSS class name to add to the wrapper element when the mouse is
         * over an item
         */
        hoverClass: 'jxHover',

        /**
         * Option: press
         * {Boolean} default false.  If set to true, the wrapper element will
         * obtain the defined pressClass if set and mousedown/mouseup
         * events will be emitted when the user clicks on elements
         */
        press: false,
        /**
         * Option: pressedClass
         * the CSS class name to add to the wrapper element when the mouse is
         * down on an item
         */
        pressClass: 'jxPressed',

        /**
         * Option: select
         * {Boolean} default false.  If set to true, the wrapper element will
         * obtain the defined selectClass if set and select/unselect events
         * will be emitted when items are selected and unselected.  For other
         * selection objects, see <Jx.Selection>
         */
        select: false
    },

    /**
     * Method: init
     * internal method to initialize this object
     */
    init: function() {
        this.container = document.id(this.options.container);
        this.container.store('jxList', this);

        var target = this,
            options = this.options,
            isEnabled = function(el) {
                var item = el.retrieve('jxListTargetItem') || el;
                return !item.hasClass('jxDisabled');
            },
            isSelectable = function(el) {
                var item = el.retrieve('jxListTargetItem') || el;
                return !item.hasClass('jxUnselectable');
            };
        this.bound = $merge(this.bound, {
            mousedown: function() {
                if (isEnabled(this)) {
                    this.addClass(options.pressClass);
                    target.fireEvent('mousedown', this, target);
                }
            },
            mouseup: function() {
                if (isEnabled(this)) {
                    this.removeClass(options.pressClass);
                    target.fireEvent('mouseup', this, target);
                }
            },
            mouseenter: function() {
                if (isEnabled(this)) {
                    this.addClass(options.hoverClass);
                    target.fireEvent('mouseenter', this, target);
                }
            },
            mouseleave: function() {
                if (isEnabled(this)) {
                    this.removeClass(options.hoverClass);
                    target.fireEvent('mouseleave', this, target);
                }
            },
            keydown: function(e) {
                if (e.key == 'enter' && isEnabled(this)) {
                    this.addClass('jxPressed');
                }
            },
            keyup: function(e) {
                if (e.key == 'enter' && isEnabled(this)) {
                    this.removeClass('jxPressed');
                }
            },
            click: function (e) {
                if (target.selection &&
                    isEnabled(this) &&
                    isSelectable(this)) {
                    target.selection.select(this, target);
                }
                target.fireEvent('click', this, target);
            },
            select: function(item) {
                if (isEnabled(item)) {
                    var itemTarget = item.retrieve('jxListTargetItem') || item;
                    target.fireEvent('select', itemTarget);
                }
            },
            unselect: function(item) {
                if (isEnabled(item)) {
                    var itemTarget = item.retrieve('jxListTargetItem') || item;
                    target.fireEvent('unselect', itemTarget);
                }
            },
            contextmenu: function(e) {
              var cm = this.retrieve('jxContextMenu');
              if (cm) {
                cm.show(e);
                this.removeClass(options.pressClass);
              }
              e.stop();
            }
        });

        if (options.selection) {
            this.setSelection(options.selection);
            options.select = true;
        } else if (options.select) {
            this.selection = new Jx.Selection(options);
            this.ownsSelection = true;
        }

        if ($defined(options.items)) {
            this.add(options.items);
        }
    },

    /**
     * Method: cleanup
     * destroy the list and release anything it references
     */
    cleanup: function() {
        this.container.getChildren().each(function(item){
            this.remove(item);
        }, this);
        if (this.selection && this.ownsSelection) {
            this.selection.removeEvents();
            this.selection.destroy();
        }
        this.setSelection(null);
        this.container.eliminate('jxList');
        var bound = this.bound;
        bound.mousedown=null;
        bound.mouseup=null;
        bound.mouseenter=null;
        bound.mouseleave=null;
        bound.keydown=null;
        bound.keyup=null;
        bound.click=null;
        bound.select=null;
        bound.unselect=null;
        bound.contextmenu=null;
        this.parent();
    },

    /**
     * APIMethod: add
     * add an item to the list of items at the specified position
     *
     * Parameters:
     * item - {mixed} the object to add, a DOM element or an
     * object that provides a getElement method.  An array of items may also
     * be provided.  All items are inserted sequentially at the indicated
     * position.
     * position - {mixed} optional, the position to add the element, either
     * an integer position in the list or another item to place this item
     * after
     */
    add: function(item, position) {
        if (Jx.type(item) == 'array') {
            item.each(function(what){
              this.add(what, position);
            }.bind(this) );
            return;
        }
        /* the element being wrapped */
        var el = document.id(item),
            target = el.retrieve('jxListTarget') || el,
            bound = this.bound,
            options = this.options,
            container = this.container;
        if (target) {
            target.store('jxListTargetItem', el);
            target.addEvents({
              contextmenu: this.bound.contextmenu
            });
            if (options.press && options.pressClass) {
                target.addEvents({
                    mousedown: bound.mousedown,
                    mouseup: bound.mouseup,
                    keyup: bound.keyup,
                    keydown: bound.keydown
                });
            }
            if (options.hover && options.hoverClass) {
                target.addEvents({
                    mouseenter: bound.mouseenter,
                    mouseleave: bound.mouseleave
                });
            }
            if (this.selection) {
                target.addEvents({
                    click: bound.click
                });
            }
            if ($defined(position)) {
                if ($type(position) == 'number') {
                    if (position < container.childNodes.length) {
                        el.inject(container.childNodes[position],'before');
                    } else {
                        el.inject(container, 'bottom');
                    }
                } else if (container.hasChild(position)) {
                    el.inject(position,'after');
                }
                this.fireEvent('add', item, this);
            } else {
                el.inject(container, 'bottom');
                this.fireEvent('add', item, this);
            }
            if (this.selection) {
                this.selection.defaultSelect(el);
            }
        }
    },
    /**
     * APIMethod: remove
     * remove an item from the list of items
     *
     * Parameters:
     * item - {mixed} the item to remove or the index of the item to remove.
     * An array of items may also be provided.
     *
     * Returns:
     * {mixed} the item that was removed or null if the item is not a member
     * of this list.
     */
    remove: function(item) {
        var el = document.id(item),
            target;
        if (el && this.container.hasChild(el)) {
            this.unselect(el, true);
            el.dispose();
            target = el.retrieve('jxListTarget') || el;
            target.removeEvents(this.bound);
            this.fireEvent('remove', item, this);
            return item;
        }
        return null;
    },
    /**
     * APIMethod: replace
     * replace one item with another
     *
     * Parameters:
     * item - {mixed} the item to replace or the index of the item to replace
     * withItem - {mixed} the object, DOM element, Jx.Object or an object
     * implementing getElement to add
     *
     * Returns:
     * {mixed} the item that was removed
     */
    replace: function(item, withItem) {
        if (this.container.hasChild(item)) {
            this.add(withItem, item);
            this.remove(item);
        }
    },
    /**
     * APIMethod: indexOf
     * find the index of an item in the list
     *
     * Parameters:
     * item - {mixed} the object, DOM element, Jx.Object or an object
     * implementing getElement to find the index of
     *
     * Returns:
     * {integer} the position of the item or -1 if not found
     */
    indexOf: function(item) {
        return $A(this.container.childNodes).indexOf(item);
    },
    /**
     * APIMethod: count
     * returns the number of items in the list
     */
    count: function() {
        return this.container.childNodes.length;
    },
    /**
     * APIMethod: items
     * returns an array of the items in the list
     */
    items: function() {
        return $A(this.container.childNodes);
    },
    /**
     * APIMethod: each
     * applies the supplied function to each item
     *
     * Parameters:
     * func - {function} the function to apply, it will receive the item and
     * index of the item as parameters
     * context - {object} the context to execute the function in, null by
     * default.
     */
    each: function(f, context) {
        $A(this.container.childNodes).each(f, context);
    },
    /**
     * APIMethod: select
     * select an item
     *
     * Parameters:
     * item - {mixed} the object to select, a DOM element, a Jx.Object, or an
     * object that provides a getElement method.  An array of items may also be
     * provided.
     */
    select: function(item) {
        if (this.selection) {
            this.selection.select(item);
        }
    },
    /**
     * APIMethod: unselect
     * unselect an item or items
     *
     * Parameters:
     * item - {mixed} the object to select, a DOM element, a Jx.Object, or an
     * object that provides a getElement method.  An array of elements may also
     * be provided.
     * force - {Boolean} force deselection even if this violates the minimum
     * selection constraint (used internally when removing items)
     */
    unselect: function(item, force) {
        if (this.selection) {
            this.selection.unselect(item);
        }
    },
    /**
     * APIMethod: selected
     * returns the selected item or items
     *
     * Returns:
     * {mixed} the selected item or an array of selected items
     */
    selected: function() {
        return this.selection ? this.selection.selected : [];
    },
    /**
     * APIMethod: empty
     * clears all of the items from the list
     */
    empty: function(){
        this.container.getChildren().each(function(item){
            this.remove(item);
        }, this);
    },
    /**
     * APIMethod: setSelection
     * sets the <Jx.Selection> object that this list will use for selection
     * events.
     *
     * Parameters:
     * {<Jx.Selection>} the selection object, or null to remove it.
     */
    setSelection: function(selection) {
        var sel = this.selection;
        if (sel == selection) return;

        if (sel) {
            sel.removeEvents(this.bound);
            if (this.ownsSelection) {
                sel.destroy();
                this.ownsSelection = false;
            }
        }

        this.selection = selection;
        if (selection) {
            selection.addEvents({
                select: this.bound.select,
                unselect: this.bound.unselect
            });
        }
    }

});/*
---

name: Jx.Stack

description: A singleton object for managing a global z-index stack for widgets that need to order themselves in the z-index of the page relative to other such widgets.

license: MIT-style license.

requires:
 - Jx

provides: [Jx.Stack]

...
 */
/**
 * Class: Jx.Stack
 * Manage the zIndex of widgets
 *
 * This is a singleton and should be called directly, like so:
 *
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2010 Paul Spencer
 *
 * This file is licensed under an MIT style license
 */
Jx.Stack = new(new Class({
  /**
   * Property: els
   * {Array} the elements in the stack
   */
  els: [],

  /**
   * Property: base
   * {Integer} the base z-index value of the first element in the stack
   */
  base: 1000,

  /**
   * Property: increment
   * {Integer} the amount to increment the z-index between elements of the
   * stack
   */
  increment: 100,

  /**
   * APIMethod: stack
   * push an element onto the stack and set its z-index appropriately
   *
   * Parameters:
   * el - {DOMElement} a DOM element to push on the stack
   */
  stack: function(el) {
    this.unstack(el);
    this.els.push(el);
    this.setZIndex(el, this.els.length-1);
  },

  /**
   * APIMethod: unstack
   * pull an element off the stack and reflow the z-index of the remaining
   * elements in the stack if necessary
   *
   * Parameters:
   * el - {DOMElement} the DOM element to pull off the stack
   */
  unstack: function(el) {
    var elements = this.els;
    if (elements.contains(el)) {
      el.setStyle('z-index', '');
      var idx = elements.indexOf(el);
      elements.erase(el);
      for (var i=idx; i<elements.length; i++) {
        this.setZIndex(elements[i], i);
      }
    }
  },

  /**
   * Method: setZIndex
   * set the z-index of an element based on its position in the stack
   *
   * Parameters:
   * el - {DOMElement} the element to set the z-index for
   * idx - {Integer} optional, the index to assume for this object
   */
  setZIndex: function(obj, idx) {
    idx = idx || this.els.indexOf(obj);
    if (idx !== false) {
      document.id(obj).setStyle('z-index', this.base + (idx*this.increment));
    }
  }

}))();/*
name: Locale.German

description: Default translations of text strings used in JX for German (Germany) (de-DE)

license: MIT-style license.

requires:
 - More/Lang

provides: [Locale.German]

...
 */

MooTools.lang.set('de-DE', 'Date', {
  // need to overwrite 'M&auml;rz' to 'MÃ¤rz' for jx.select fields
  months: ['Januar', 'Februar', 'MÃ¤rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
});

MooTools.lang.set('de-DE', 'Jx', {

	'widget': {
		busyMessage: 'Arbeite ...'
	},
	'colorpalette': {
		alphaLabel: 'alpha (%)'
	},
	notice: {
		closeTip: 'Notiz schlieÃŸen'
	},
	progressbar: {
		messageText: 'Lade...',
		progressText: '{progress} von {total}'
	},
	field: {
		requiredText: '*'
	},
	file: {
		browseLabel: 'Durchsuchen...'
	},
	'formatter.boolean': {
		'true': 'Ja',
		'false': 'Nein'
	},
	'formatter.currency': {
		sign: 'â‚¬'
	},
	'formatter.number': {
		decimalSeparator: ',',
    thousandsSeparator: '.'
	},
	splitter: {
		barToolTip: 'Ziehen Sie diese Leiste um die GrÃ¶ÃŸe zu verÃ¤ndern'
	},
	panelset: {
		barToolTip: 'Ziehen Sie diese Leiste um die GrÃ¶ÃŸe zu verÃ¤ndern'
	},
	panel: {
        collapseTooltip: 'Panel ein-/ausklappen', //colB
        collapseLabel: 'Einklappen',  //colM
        expandLabel: 'Ausklappen', //colM
        maximizeTooltip: 'Panel maximieren',
        maximizeLabel: 'maximieren',
        restoreTooltip: 'Panel wieder herstellen', //maxB
        restoreLabel: 'wieder herstellen', //maxM
        closeTooltip: 'Panel schlieÃŸen', //closeB
        closeLabel: 'SchlieÃŸen' //closeM
	},
	confirm: {
		affirmativeLabel: 'Ja',
    negativeLabel: 'Nein'
	},
	dialog: {
		label: 'Neues Fenster'
	},
	message: {
		okButton: 'Ok'
	},
	prompt: {
		okButton: 'Ok',
		cancelButton: 'Abbrechen'
	},
	upload: {
		buttonText: 'Dateien hochladen'
	},
	'plugin.resize': {
	  tooltip: 'Klicken um GrÃ¶ÃŸe zu verÃ¤ndern. Doppelklick fÃ¼r automatische Anpassung.'
	},
  'plugin.editor': {
    submitButton: 'Speichern',
    cancelButton: 'Abbrechen'
  }
});/*
---

name: Locale.Russian

description: Default translations of text strings used in JX for Russia (Russia) (ru-RU)

license: MIT-style license.

requires:
 - More/Lang

provides: [Locale.Russian]

...
 */
MooTools.lang.set('ru-RU-unicode', 'Jx', {

	'widget': {
		busyMessage: 'ÐžÐ±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ°...'
	},
	'colorpalette': {
		alphaLabel: 'alpha (%)'
	},
	notice: {
		closeTip: 'Ð·Ð°ÐºÑ€Ñ‹Ñ‚ÑŒ Ñ?Ñ‚Ð¾ Ñ?Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ðµ'
	},
	progressbar: {
		messageText: 'Ð—Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°...',
		progressText: '{progress} Ð¸Ð· {total}'
	},
	field: {
		requiredText: '*'
	},
	file: {
		browseLabel: 'Ð’Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ...'
	},
	'formatter.boolean': {
		'true': 'Ð”Ð°',
		'false': 'Ð?ÐµÑ‚'
	},
	'formatter.currency': {
		sign: 'Ñ€.'
	},
	'formatter.number': {
		decimalSeparator: ',',
    thousandsSeparator: ' '
	},
	splitter: {
		barToolTip: 'Ð¿Ð¾Ñ‚Ñ?Ð½Ð¸, Ñ‡Ñ‚Ð¾Ð±Ñ‹ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ Ñ€Ð°Ð·Ð¼ÐµÑ€'
	},
	panelset: {
		barToolTip: 'Ð¿Ð¾Ñ‚Ñ?Ð½Ð¸, Ñ‡Ñ‚Ð¾Ð±Ñ‹ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ Ñ€Ð°Ð·Ð¼ÐµÑ€'
	},
	panel: {
		collapseTooltip: 'Ð¡Ð²ÐµÑ€Ð½ÑƒÑ‚ÑŒ/Ð Ð°Ð·Ð²ÐµÑ€Ð½ÑƒÑ‚ÑŒ ÐŸÐ°Ð½ÐµÐ»ÑŒ',
    collapseLabel: 'Ð¡Ð²ÐµÑ€Ð½ÑƒÑ‚ÑŒ',
    expandLabel: 'Ð Ð°Ð·Ð²ÐµÑ€Ð½ÑƒÑ‚ÑŒ',
    maximizeTooltip: 'Ð£Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ñ‚ÑŒ ÐŸÐ°Ð½ÐµÐ»ÑŒ',
    maximizeLabel: 'Ð£Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ñ‚ÑŒ',
    restoreTooltip: 'Ð’Ð¾Ñ?Ñ?Ñ‚Ð°Ð½Ð¾Ð²Ð¸Ñ‚ÑŒ ÐŸÐ°Ð½ÐµÐ»ÑŒ',
    restoreLabel: 'Ð’Ð¾Ñ?Ñ?Ñ‚Ð°Ð½Ð¾Ð²Ð¸Ñ‚ÑŒ',
    closeTooltip: 'Ð—Ð°ÐºÑ€Ñ‹Ñ‚ÑŒ ÐŸÐ°Ð½ÐµÐ»ÑŒ',
    closeLabel: 'Ð—Ð°ÐºÑ€Ñ‹Ñ‚ÑŒ'
	},
	confirm: {
		affirmativeLabel: 'Ð”Ð°',
    negativeLabel: 'Ð?ÐµÑ‚'
	},
	dialog: {
		resizeToolTip: 'Ð˜Ð·Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ Ñ€Ð°Ð·Ð¼ÐµÑ€'
	},
	message: {
		okButton: 'ÐžÐº'
	},
	prompt: {
		okButton: 'ÐžÐº',
		cancelButton: 'ÐžÑ‚Ð¼ÐµÐ½Ð°'
	},
	upload: {
		buttonText: 'Ð—Ð°Ð³Ñ€ÑƒÐ·ÐºÐ° Ñ„Ð°Ð¹Ð»Ð°'
	},
	'plugin.resize': {
	  tooltip: 'ÐŸÐ¾Ñ‚Ñ?Ð½Ð¸, Ñ‡Ñ‚Ð¾Ð±Ñ‹ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ, Ð´Ð²Ð¾Ð¹Ð½Ð¾Ð¹ Ñ‰ÐµÐ»Ñ‡Ð¾Ðº Ð´Ð»Ñ? Ð°Ð²Ñ‚Ð¾ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð°.'
	},
  'plugin.editor': {
    submitButton: 'Ð¡Ð¾Ñ…Ñ€Ð°Ð½Ð¸Ñ‚ÑŒ',
    cancelButton: 'ÐžÑ‚Ð¼ÐµÐ½Ð°'
  }
});/*
---

name: Jx.Record

description: The basic record implementation. A store uses records to handle and manipulate data.

license: MIT-style license.

requires:
 - Jx.Object

provides: [Jx.Record]

...
 */
// $Id: record.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Record
 *
 * Extends: <Jx.Object>
 *
 * This class is used as a representation (or container) for a single row
 * of data in a <Jx.Store>. It is not usually directly instantiated by the
 * developer but rather by the store itself.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Record = new Class({

    Extends: Jx.Object,
    Family: 'Jx.Record',

    options: {
        /**
         * Option: separator
         * The separator to pass to the comparator
         * constructor (<Jx.Compare>) - defaults to '.'
         */
        separator : '.',

        primaryKey: null
    },
    /**
     * Property: data
     * The data for this record
     */
    data: null,
    /**
     * Property: state
     * used to determine the state of this record. When not null (meaning no
     * changes were made) this should be one of
     *
     * - Jx.Record.UPDATE
     * - Jx.Record.DELETE
     * - Jx.Record.INSERT
     */
    state: null,
    /**
     * Property: columns
     * Holds a reference to the columns for this record. These are usually
     * passed to the record from the store. This should be an array of objects
     * where the objects represent the columns. The object should take the form:
     *
     * (code)
     * {
     *     name: <column name>,
     *     type: <column type>,
     *     ..additional options required by the record implementation...
     * }
     * (end)
     *
     * The type of the column should be one of alphanumeric, numeric, date,
     * boolean, or currency.
     */
    columns: null,

    parameters: ['store', 'columns', 'data', 'options'],

    init: function () {
        this.parent();
        if ($defined(this.options.columns)) {
            this.columns = this.options.columns;
        }

        if ($defined(this.options.data)) {
            this.processData(this.options.data);
        } else {
            this.data = new Hash();
        }

        if ($defined(this.options.store)) {
            this.store = this.options.store;
        }

    },
    /**
     * APIMethod: get
     * returns the value of the requested column. Can be programmed to handle
     * pseudo-columns (such as the primaryKey column implemented in this base
     * record).
     *
     * Parameters:
     * column - the string, index, or object of the requested column
     */
    get: function (column) {
        var type = Jx.type(column);
        if (type !== 'object') {
            if (column === 'primaryKey') {
                column = this.resolveCol(this.options.primaryKey);
            } else {
                column = this.resolveCol(column);
            }
        }
        if (this.data.has(column.name)) {
            return this.data.get(column.name);
        } else {
            return null;
        }
    },
    /**
     * APIMethod: set
     * Sets a given value into the requested column.
     *
     *  Parameters:
     *  column - the object, index, or string name of the target column
     *  data - the data to add to the column
     */
    set: function (column, data) {
        var type = Jx.type(column),
            oldValue;
        if (type !== 'object') {
            column = this.resolveCol(column);
        }

        if (!$defined(this.data)) {
            this.data = new Hash();
        }

        oldValue = this.get(column);
        this.data.set(column.name, data);
        this.state = Jx.Record.UPDATE;
        return [column.name, oldValue, data];
        //this.store.fireEvent('storeColumnChanged', [this, column.name, oldValue, data]);

    },
    /**
     * APIMethod: equals
     * Compares the value of a particular column with a given value
     *
     * Parameters:
     * column - the column to compare with (either column name or index)
     * value - the value to compare to.
     *
     * Returns:
     * True | False depending on the outcome of the comparison.
     */
    equals: function (column, value) {
        if (column === 'primaryKey') {
            column = this.resolveCol(this.options.primaryKey);
        } else {
            column = this.resolveCol(column);
        }
        if (!this.data.has(column.name)) {
            return null;
        } else {
            if (!$defined(this.comparator)) {
                this.comparator = new Jx.Compare({
                    separator : this.options.separator
                });
            }
            var fn = this.comparator[column.type].bind(this.comparator);
            return (fn(this.get(column), value) === 0);
        }
    },
    /**
     * Method: processData
     * This method takes the data passed in and puts it into the form the
     * record needs it in. This default implementation does nothing but
     * assign the data to the data property but it can be overridden in
     * subclasses to massge the data in any way needed.
     *
     * Parameters:
     * data - the data to process
     */
    processData: function (data) {
        this.data = $H(data);
    },

    /**
     * Method: resolveCol
     * Determines which column is being asked for and returns it.
     *
     * Parameters:
     * col - a number referencing a column in the store
     *
     * Returns:
     * the column object referred to
     */
    resolveCol : function (col) {
        var t = Jx.type(col);
        if (t === 'number') {
            col = this.columns[col];
        } else if (t === 'string') {
            this.columns.each(function (column) {
                if (column.name === col) {
                    col = column;
                }
            }, this);
        }
        return col;
    },
    /**
     * APIMethod: asHash
     * Returns the data for this record as a Hash
     */
    asHash: function() {
        return this.data;
    }
});

Jx.Record.UPDATE = 1;
Jx.Record.DELETE = 2;
Jx.Record.INSERT = 3;/*
---

name: Jx.Store

description: An implementation of a basic data store.

license: MIT-style license.

requires:
 - Jx.Object
 - Jx.Record

provides: [Jx.Store]

...
 */
// $Id: store.js 995 2010-10-25 14:47:15Z pagameba $
/**
 * Class: Jx.Store
 *
 * Extends: <Jx.Object>
 *
 * This class is the  store. It keeps track of data. It
 * allows adding, deleting, iterating, sorting etc...
 *
 * For the most part the store is pretty "dumb" meaning it
 * starts with very limited functionality. Actually, it can't
 * even load data by itself. Instead, it needs to have protocols,
 * strategies, and a record class passed to it that it can use.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store = new Class({

    Family: 'Jx.Store',
    Extends: Jx.Object,

    options: {
        /**
         * Option: id
         * the identifier for this store
         */
        id : null,
        /**
         * Option: columns
         * an array listing the columns of the store in order of their
         * appearance in the data object formatted as an object
         *      {name: 'column name', type: 'column type'}
         * where type can be one of alphanumeric, numeric, date, boolean,
         * or currency.
         */
        columns : [],
        /**
         * Option: protocol
         * The protocol to use for communication in this store. The store
         * itself doesn't actually use it but it is accessed by the strategies
         * to do their work. This option is required and the store won't work
         * without it.
         */
        protocol: null,
        /**
         * Option: strategies
         * This is an array of instantiated strategy objects that will work
         * on this store. They provide many services such as loading data,
         * paging data, saving, and sorting (and anything else you may need
         * can be written). If none are passed in it will use the default
         * Jx.Store.Strategy.Full
         */
        strategies: null,
        /**
         * Option: record
         * This is a Jx.Store.Record instance or one of its subclasses. This is
         * the class that will be used to hold each individual record in the
         * store. Don't pass in a instance of the class but rather the class
         * name itself. If none is passed in it will default to Jx.Record
         */
        record: null,
        /**
         * Option: recordOptions
         * Options to pass to each record as it's created.
         */
        recordOptions: {
            primaryKey: null
        }
    },

    /**
     * Property: data
     * Holds the data for this store
     */
    data : null,
    /**
     * Property: index
     * Holds the current position of the store relative to the data and the pageIndex.
     * Zero-based index.
     */
    index : 0,
    /**
     * APIProperty: id
     * The id of this store.
     */
    id : null,
    /**
     * Property: loaded
     * Tells whether the store has been loaded or not
     */
    loaded: false,
    /**
     * Property: ready
     * Used to determine if the store is completely initialized.
     */
    ready: false,

    /**
     * Property: deleted
     * track deleted records before they are purged
     */
    deleted: null,

    /**
     * Method: init
     * initialize the store, should be called by sub-classes
     */
    init: function () {
        this.parent();

        this.deleted = [];

        if ($defined(this.options.id)) {
            this.id = this.options.id;
        }

        if (!$defined(this.options.protocol)) {
            this.ready = false;
            return;
        } else {
            this.protocol = this.options.protocol;
        }

        this.strategies = new Hash();

        if ($defined(this.options.strategies)) {
            this.options.strategies.each(function(strategy){
                this.addStrategy(strategy);
            },this);
        } else {
            var strategy = new Jx.Store.Strategy.Full();
            this.addStrategy(strategy);
        }

        if ($defined(this.options.record)) {
            this.record = this.options.record;
        } else {
            this.record = Jx.Record;
        }


    },

    /**
     * Method: cleanup
     * avoid memory leaks when a store is destroyed, should be called
     * by sub-classes if overridden
     */
    cleanup: function () {
        this.strategies.each(function(strategy){
            strategy.destroy();
        },this);
        this.strategies = null;
        this.protocol.destroy();
        this.protocol = null;
        this.record = null;
    },
    /**
     * APIMethod: getStrategy
     * returns the named strategy if it is present, null otherwise.
     *
     * Parameters:
     * name - the name of the strategy we're looking for
     */
    getStrategy: function (name) {
        if (this.strategies.has(name)) {
            return this.strategies.get(name);
        }
        return null;
    },
    /**
     * APIMethod: addStrategy
     * Allows the addition of strategies after store initialization. Handy to
     * have if some other class needs a strategy that is not present.
     *
     * Parameters:
     * strategy - the strategy to add to the store
     */
    addStrategy: function (strategy) {
        this.strategies.set(strategy.name, strategy);
        strategy.setStore(this);
        strategy.activate();
    },
    /**
     * APIMethod: load
     * used to load the store. It simply fires an event that the strategies
     * are listening for.
     *
     * Parameters:
     * params - a hash of parameters passed to the strategy for determining
     *     what records to load.
     */
    load: function (params) {
        this.fireEvent('storeLoad', params);
    },
    /**
     * APIMethod: empty
     * Clears the store of data
     */
    empty: function () {
        if ($defined(this.data)) {
            this.data.empty();
        }
    },

    /**
     * APIMethod: hasNext
     * Determines if there are more records past the current
     * one.
     *
     * Returns: true | false (Null if there's a problem)
     */
    hasNext : function () {
        if ($defined(this.data)) {
            return this.index < this.data.length - 1;
        }
        return null;
    },

    /**
     * APIMethod: hasPrevious
     * Determines if there are records before the current
     * one.
     *
     * Returns: true | false
     */
    hasPrevious : function () {
        if ($defined(this.data)) {
            return this.index > 0;
        }
        return null;
    },

    /**
     * APIMethod: valid
     * Tells us if the current index has any data (i.e. that the
     * index is valid).
     *
     * Returns: true | false
     */
    valid : function () {
        return ($defined(this.data) && $defined(this.data[this.index]));
    },

    /**
     * APIMethod: next
     * Moves the store to the next record
     *
     * Returns: nothing | null if error
     */
    next : function () {
        if ($defined(this.data)) {
            this.index++;
            if (this.index === this.data.length) {
                this.index = this.data.length - 1;
            }
            this.fireEvent('storeMove', this);
            return true;
        } else {
            return null;
        }
    },

    /**
     * APIMethod: previous
     * moves the store to the previous record
     *
     * Returns: nothing | null if error
     *
     */
    previous : function () {
        if ($defined(this.data)) {
            this.index--;
            if (this.index < 0) {
                this.index = 0;
            }
            this.fireEvent('storeMove', this);
            return true;
        } else {
            return null;
        }
    },

    /**
     * APIMethod: first
     * Moves the store to the first record
     *
     * Returns: nothing | null if error
     *
     */
    first : function () {
        if ($defined(this.data)) {
            this.index = 0;
            this.fireEvent('storeMove', this);
            return true;
        } else {
            return null;
        }
    },

    /**
     * APIMethod: last
     * Moves to the last record in the store
     *
     * Returns: nothing | null if error
     */
    last : function () {
        if ($defined(this.data)) {
            this.index = this.data.length - 1;
            this.fireEvent('storeMove', this);
            return true;
        } else {
            return null;
        }
    },

    /**
     * APIMethod: count
     * Returns the number of records in the store
     *
     * Returns: an integer indicating the number of records in the store or null
     * if there's an error
     */
    count : function () {
        if ($defined(this.data)) {
            return this.data.length;
        }
        return null;
    },

    /**
     * APIMethod: getPosition
     * Tells us where we are in the store
     *
     * Returns: an integer indicating the position in the store or null if
     * there's an error
     */
    getPosition : function () {
        if ($defined(this.data)) {
            return this.index;
        }
        return null;
    },

    /**
     * APIMethod: moveTo
     * Moves the index to a specific record in the store
     *
     * Parameters:
     * index - the record to move to
     *
     * Returns: true - if successful false - if not successful null - on error
     */
    moveTo : function (index) {
        if ($defined(this.data) && index >= 0 && index < this.data.length) {
            this.index = index;
            this.fireEvent('storeMove', this);
            return true;
        } else if (!$defined(this.data)) {
            return null;
        } else {
            return false;
        }
    },
    /**
     * APIMethod: each
     * allows iteration through the store's records.
     * NOTE: this function is untested
     *
     * Parameters:
     * fn - the function to execute for each record
     * bind - the scope of the function
     * ignoreDeleted - flag that tells the function whether to ignore records
     *                  marked as deleted.
     */
    each: function (fn, bind, ignoreDeleted) {
        if ($defined(this.data)) {
          var data;
          if (ignoreDeleted) {
              data = this.data.filter(function (record) {
                  return record.state !== Jx.Record.DELETE;
              }, this);
          } else {
              data = this.data;
          }
          data.each(fn, bind);
        }
    },
    /**
     * APIMethod: get
     * gets the data for the specified column
     *
     * Parameters:
     * column - indicator of the column to set. Either a string (the name of
     *          the column) or an integer (the index of the column in the
     *          record).
     * index - the index of the record in the internal array. Optional.
     *          defaults to the current index.
     */
    get: function (column, index) {
        if (!$defined(index)) {
            index = this.index;
        }
        return this.data[index].get(column);
    },
    /**
     * APIMethod: set
     * Sets the passed data for a particular column on the indicated record.
     *
     * Parameters:
     * column - indicator of the column to set. Either a string (the name of
     *          the column) or an integer (the index of the column in the
     *          record).
     * data - the data to set in the column of the record
     * index - the index of the record in the internal array. Optional.
     *          defaults to the current index.
     */
    set: function (column, data, index) {
        if (!$defined(index)) {
            index = this.index;
        }
        var ret = this.data[index].set(column, data);
        ret.reverse();
        ret.push(index);
        ret.reverse();
        //fire event with array [index, column, oldvalue, newValue]
        this.fireEvent('storeColumnChanged', ret);
    },
    /**
     * APIMethod: refresh
     * Simply fires the storeRefresh event for strategies to listen for.
     */
    refresh: function () {
        this.fireEvent('storeRefresh', this);
    },
    /**
     * APIMethod: addRecord
     * Adds given data to the end of the current store.
     *
     * Parameters:
     * data - The data to use in creating a record. This should be in whatever
     *        form Jx.Store.Record, or the current subclass, needs it in.
     * position - whether the record is added to the 'top' or 'bottom' of the
     *      store.
     * insert - flag whether this is an "insert"
     */
    addRecord: function (data, position, insert) {
        if (!$defined(this.data)) {
            this.data = [];
        }

        position = $defined(position)? position : 'bottom';

        var record = data;
        if (!(data instanceof Jx.Record)) {
            record = new (this.record)(this, this.options.columns, data, this.options.recordOptions);
        }
        if (insert) {
            record.state = Jx.Record.INSERT;
        }
        if (position === 'top') {
            //some literature claims that .shift() and .unshift() don't work reliably in IE
            //so we do it this way.
            this.data.reverse();
            this.data.push(record);
            this.data.reverse();
        } else {
            this.data.push(record);
        }
        this.fireEvent('storeRecordAdded', [this, record, position]);
    },
    /**
     * APIMethod: addRecords
     * Used to add multiple records to the store at one time.
     *
     * Parameters:
     * data - an array of data to add.
     * position - 'top' or 'bottom'. Indicates whether to add at the top or
     * the bottom of the store
     */
    addRecords: function (data, position) {
        var def = $defined(data),
            type = Jx.type(data);
        if (def && type === 'array') {
            this.fireEvent('storeBeginAddRecords', this);
            //if position is top, reverse the array or we'll add them in the
            // wrong order.
            if (position === 'top') {
                data.reverse();
            }
            data.each(function(d){
                this.addRecord(d, position);
            },this);
            this.fireEvent('storeEndAddRecords', this);
            return true;
        }
        return false;
    },

    /**
     * APIMethod: getRecord
     * Returns the record at the given index or the current store index
     *
     * Parameters:
     * index - the index from which to return the record. Optional. Defaults
     * to the current store index
     */
    getRecord: function (index) {
        if (!$defined(index)) {
            index = this.index;
        }

        if (Jx.type(index) === 'number') {
            if ($defined(this.data) && $defined(this.data[index])) {
                return this.data[index];
            }
        } else {
            //Not sure what the point of this part is. It compares the
            //record to the index directly as if we passed in the record which
            //means we already have the record... huh???
            var r;
            this.data.each(function(record){
                if (record === index) {
                    r = record;
                }
            },this);
            return r;
        }
        return null;
    },
    /**
     * APIMethod: replaceRecord
     * Replaces the record at an existing index with a new record containing
     * the passed in data.
     *
     * Parameters:
     * data - the data to use in creating the new record
     * index - the index at which to place the new record. Optional.
     *          defaults to the current store index.
     */
    replace: function(data, index) {
        if ($defined(data)) {
            if (!$defined(index)) {
                index = this.index;
            }
            var record = new this.record(this.options.columns,data),
            oldRecord = this.data[index];
            this.data[index] = record;
            this.fireEvent('storeRecordReplaced', [oldRecord, record]);
            return true;
        }
        return false;
    },
    /**
     * APIMethod: deleteRecord
     * Marks a record for deletion and removes it from the regular array of
     * records. It adds it to a special holding array so it can be disposed
     * of later.
     *
     * Parameters:
     * index - the index at which to place the new record. Optional.
     *          defaults to the current store index.
     */
    deleteRecord: function(index) {
        if (!$defined(index)) {
            index = this.index;
        }
        var record = this.data[index];
        record.state = Jx.Record.DELETE;
        // Set to Null or slice it out and compact the array???
        //this.data[index] = null;
        this.data.splice(index,1);
        // TODO: I moved this to a property that is always an array so I don't
        // get an error in the save strategy.
        // if (!$defined(this.deleted)) {
        //     this.deleted = [];
        // }
        this.deleted.push(record);
        this.fireEvent('storeRecordDeleted', [this, record]);
    },
    /**
     * APIMethod: insertRecord
     * Shortcut to addRecord which facilitates marking a record as inserted.
     *
     * Parameters:
     * data - the data to use in creating this inserted record. Should be in
     *          whatever form the current implementation of Jx.Record needs
     * position - where to place the record. Should be either 'top' or
     *    'bottom'.
     */
    insertRecord: function (data, position) {
        this.addRecord(data, position, true);
    },

    /**
     * APIMethod: getColumns
     * Allows retrieving the columns array
     */
    getColumns: function () {
        return this.options.columns;
    },

    /**
     * APIMethod: findByColumn
     * Used to find a specific record by the value in a specific column. This
     * is particularly useful for finding records by a unique id column. The
     * search will stop on the first instance of the value
     *
     * Parameters:
     * column - the name (or index) of the column to search by
     * value - the value to look for
     */
    findByColumn: function (column, value) {
        if (typeof StopIteration === "undefined") {
            StopIteration = new Error("StopIteration");
        }

        var index;
        try {
            this.data.each(function(record, idx){
                if (record.equals(column, value)) {
                    index = idx;
                    throw StopIteration;
                }
            },this);
        } catch (error) {
            if (error !== StopIteration) {
                throw error;
            }
            return index;
        }
        return null;
    },
    /**
     * APIMethod: removeRecord
     * removes (but does not mark for deletion) a record at the given index
     * or the current store index if none is passed in.
     *
     * Parameters:
     * index - Optional. The store index of the record to remove.
     */
    removeRecord: function (index) {
        if (!$defined(index)) {
            index = this.index;
        }
        this.data.splice(index,1);
        this.fireEvent('storeRecordRemoved', [this, index])
    },
    /**
     * APIMethod: removeRecords
     * Used to remove multiple contiguous records from a store.
     *
     * Parameters:
     * first - where to start removing records (zero-based)
     * last - where to stop removing records (zero-based, inclusive)
     */
    removeRecords: function (first, last) {
        for (var i = first; i <= last; i++) {
            this.removeRecord(first);
        }
        this.fireEvent('storeMultipleRecordsRemoved', [this, first, last]);
    },

    /**
   * APIMethod: parseTemplate
   * parses the provided template to determine which store columns are
   * required to complete it.
   *
   * Parameters:
   * template - the template to parse
   */
  parseTemplate: function (template) {
      //we parse the template based on the columns in the data store looking
      //for the pattern {column-name}. If it's in there we add it to the
      //array of ones to look fo
      var arr = [],
          s;
      this.options.columns.each(function (col) {
          s = '{' + col.name + '}';
          if (template.contains(s)) {
              arr.push(col.name);
          }
      }, this);
      return arr;
  },

  /**
   * APIMethod: fillTemplate
   * Actually does the work of getting the data from the store
   * and creating a single item based on the provided template
   *
   * Parameters:
   * index - the index of the data in the store to use in populating the
   *          template.
   * template - the template to fill
   * columnsNeeded - the array of columns needed by this template. should be
   *      obtained by calling parseTemplate().
     * obj - an object with some prefilled keys to use in substituting.
     *      Ones that are also in the store will be overwritten.
   */
  fillTemplate: function (index, template, columnsNeeded, obj) {
      var record = null,
          itemObj;
      if ($defined(index)) {
          if (index instanceof Jx.Record) {
              record = index;
          } else {
              record = this.getRecord(index);
          }
        } else {
            record = this.getRecord(this.index);
        }

      //create the item
      itemObj = $defined(obj) ? obj : {};
      columnsNeeded.each(function (col) {
          itemObj[col] = record.get(col);
      }, this);
      return template.substitute(itemObj);
  }
});/*
---

name: Jx.Compare

description: Class that provides functions for comparing various data types. Used by the Jx.Sort class and it's descendants

license: MIT-style license.

requires:
 - Jx.Object
 - More/Date.Extras

provides: [Jx.Compare]

...
 */
// $Id: compare.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Compare
 *
 * Extends: <Jx.Object>
 *
 * Class that holds functions for doing comparison operations.
 * This class requires the mootools-more Date() extensions.
 *
 * notes:
 * Each function that does a comparison returns
 *
 * 0 - if equal.
 * 1 - if the first value is greater that the second.
 * -1 - if the first value is less than the second.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */

Jx.Compare = new Class({
    Family: 'Jx.Compare',
    Extends: Jx.Object,

    options: { separator: '.' },

    /**
     * APIMethod: alphanumeric
     * Compare alphanumeric variables. This is case sensitive
     *
     * Parameters:
     * a - a value
     * b - another value
     */
    alphanumeric: function (a, b) {
        return (a === b) ? 0 :(a < b) ? -1 : 1;
    },

    /**
     * APIMethod: numeric
     * Compares numbers
     *
     * Parameters:
     * a - a number
     * b - another number
     */
    numeric: function (a, b) {
        return this.alphanumeric(this.convert(a), this.convert(b));
    },

    /**
     * Method: _convert
     * Normalizes numbers relative to the separator.
     *
     * Parameters:
     * val - the number to normalize
     *
     * Returns:
     * the normalized value
     */
    convert: function (val) {
        if (Jx.type(val) === 'string') {
            var neg = false;
            if (val.substr(0,1) == '-') {
                neg = true;
            }
            val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g, "$1").replace(new RegExp("[^\\\d" + this.options.separator + "]", "g"), '').replace(/,/, '.')) || 0;
            if (neg) {
                val = val * -1;
            }
        }
        return val || 0;
    },

    /**
     * APIMethod: ignorecase
     * Compares to alphanumeric strings without regard to case.
     *
     * Parameters:
     * a - a value
     * b - another value
     */
    ignorecase: function (a, b) {
        return this.alphanumeric(("" + a).toLowerCase(), ("" + b).toLowerCase());
    },

    /**
     * APIMethod: currency
     * Compares to currency values.
     *
     * Parameters:
     * a - a currency value without the $
     * b - another currency value without the $
     */
    currency: function (a, b) {
        return this.numeric(a, b);
    },

    /**
     * APIMethod: date
     * Compares 2 date values (either a string or an object)
     *
     * Parameters:
     * a - a date value
     * b - another date value
     */
    date: function (a, b) {
        var x = new Date().parse(a),
            y = new Date().parse(b);
        return (x < y) ? -1 : (x > y) ? 1 : 0;
    },
    /**
     * APIMethod: boolean
     * Compares 2 bolean values
     *
     * Parameters:
     * a - a boolean value
     * b - another boolean value
     */
    'boolean': function (a, b) {
        return (a === true && b === false) ? -1 : (a === b) ? 0 : 1;
    }

});/*
---

name: Jx.Sort

description: Base class for the sort algorithm implementations

license: MIT-style license.

requires:
 - Jx.Object
 - Jx.Compare

provides: [Jx.Sort]

...
 */
// $Id: sort.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Sort
 * Base class for all of the sorting algorithm classes.
 *
 * Extends: <Jx.Object>
 *
 * Events:
 * onStart() - called when the sort starts
 * onEnd() - called when the sort stops
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Sort = new Class({

    Family : 'Jx.Sort',

    Extends : Jx.Object,

    options : {
        /**
         * Option: timeIt
         * whether to time the sort
         */
        timeIt : false,
        /**
         * Event: onStart
         */
        onStart : $empty,
        /**
         * Event: onEnd
         */
        onEnd : $empty
    },

    /**
     * Property: timer
     * holds the timer instance
     */
    timer : null,
    /**
     * Property: data
     * The data to sort
     */
    data : null,
    /**
     * Property: Comparator
     * The comparator to use in sorting
     */
    comparator : $empty,
    /**
     * Property: col
     * The column to sort by
     */
    col : null,

    parameters: ['data','fn','col','options'],

    /**
     * APIMethod: init
     */
    init : function () {
        this.parent();
        if (this.options.timeIt) {
            this.addEvent('start', this.startTimer.bind(this));
            this.addEvent('stop', this.stopTimer.bind(this));
        }
        this.data = this.options.data;
        this.comparator = this.options.fn;
        this.col = this.options.col;
    },

    /**
     * APIMethod: sort
     * Actually does the sorting. Must be overridden by subclasses.
     */
    sort : $empty,

    /**
     * Method: startTimer
     * Saves the starting time of the sort
     */
    startTimer : function () {
        this.timer = new Date();
    },

    /**
     * Method: stopTimer
     * Determines the time the sort took.
     */
    stopTimer : function () {
        this.end = new Date();
        this.dif = this.timer.diff(this.end, 'ms');
    },

    /**
     * APIMethod: setData
     * sets the data to sort
     *
     * Parameters:
     * data - the data to sort
     */
    setData : function (data) {
        if ($defined(data)) {
            this.data = data;
        }
    },

    /**
     * APIMethod: setColumn
     * Sets the column to sort by
     *
     * Parameters:
     * col - the column to sort by
     */
    setColumn : function (col) {
        if ($defined(col)) {
            this.col = col;
        }
    },

    /**
     * APIMethod: setComparator
     * Sets the comparator to use in sorting
     *
     * Parameters:
     * fn - the function to use as the comparator
     */
    setComparator : function (fn) {
        this.comparator = fn;
    }
});
/*
---

name: Jx.Sort.Mergesort

description: An implementation of the merge sort algorithm

license: MIT-style license.

requires:
 - Jx.Sort

provides: [Jx.Sort.Mergesort]

...
 */
// $Id: mergesort.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * class: Jx.Sort.Mergesort
 *
 * Extends: <Jx.Sort>
 *
 * Implementation of a mergesort algorithm designed to
 * work on <Jx.Store> data.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Sort.Mergesort = new Class({
    Family: 'Jx.Sort.Mergesort',
    Extends : Jx.Sort,

    name : 'mergesort',

    /**
     * APIMethod: sort
     * Actually runs the sort on the data
     *
     * returns: the sorted data
     */
    sort : function () {
        this.fireEvent('start');
        var d = this.mergeSort(this.data);
        this.fireEvent('stop');
        return d;

    },

    /**
     * Method: mergeSort
     * Does the physical sorting. Called
     * recursively.
     *
     * Parameters:
     * arr - the array to sort
     *
     * returns: the sorted array
     */
    mergeSort : function (arr) {
        if (arr.length <= 1) {
            return arr;
        }

        var middle = (arr.length) / 2,
            left = arr.slice(0, middle),
            right = arr.slice(middle),
            result;
        left = this.mergeSort(left);
        right = this.mergeSort(right);
        result = this.merge(left, right);
        return result;
    },

    /**
     * Method: merge
     * Does the work of merging to arrays in order.
     *
     * parameters:
     * left - the left hand array
     * right - the right hand array
     *
     * returns: the merged array
     */
    merge : function (left, right) {
        var result = [];

        while (left.length > 0 && right.length > 0) {
            if (this.comparator((left[0]).get(this.col), (right[0])
                    .get(this.col)) <= 0) {
                result.push(left[0]);
                left = left.slice(1);
            } else {
                result.push(right[0]);
                right = right.slice(1);
            }
        }
        while (left.length > 0) {
            result.push(left[0]);
            left = left.slice(1);
        }
        while (right.length > 0) {
            result.push(right[0]);
            right = right.slice(1);
        }
        return result;
    }

});
/*
---

name: Jx.Sort.Heapsort

description: An implementation of the heap sort algorithm

license: MIT-style license.

requires:
 - Jx.Sort

provides: [Jx.Sort.Heapsort]

...
 */
// $Id: heapsort.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Sort.Heapsort
 *
 * Extends: <Jx.Sort>
 *
 * Implementation of a heapsort algorithm designed to
 * work on <Jx.Store> data.
 *
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Sort.Heapsort = new Class({
    Family: 'Jx.Sort.Heapsort',
    Extends : Jx.Sort,

    name : 'heapsort',

    /**
     * APIMethod: sort
     * Actually runs the sort on the data
     *
     * Returns: the sorted data
     */
    sort : function () {
        this.fireEvent('start');

        var count = this.data.length,
            end;

        if (count === 1) {
            return this.data;
        }

        if (count > 2) {
            this.heapify(count);

            end = count - 1;
            while (end > 1) {
                this.data.swap(end, 0);
                end = end - 1;
                this.siftDown(0, end);
            }
        } else {
            // check then order the two we have
            if ((this.comparator((this.data[0]).get(this.col), (this.data[1])
                    .get(this.col)) > 0)) {
                this.data.swap(0, 1);
            }
        }

        this.fireEvent('stop');
        return this.data;
    },

    /**
     * Method: heapify
     * Puts the data in Max-heap order
     *
     * Parameters: count - the number of records we're sorting
     */
    heapify : function (count) {
        var start = Math.round((count - 2) / 2);

        while (start >= 0) {
            this.siftDown(start, count - 1);
            start = start - 1;
        }
    },

    /**
     * Method: siftDown
     *
     * Parameters: start - the beginning of the sort range end - the end of the
     * sort range
     */
    siftDown : function (start, end) {
        var root = start,
            child;

        while (root * 2 <= end) {
            child = root * 2;
            if ((child + 1 < end) && (this.comparator((this.data[child]).get(this.col),
                            (this.data[child + 1]).get(this.col)) < 0)) {
                child = child + 1;
            }
            if ((this.comparator((this.data[root]).get(this.col),
                    (this.data[child]).get(this.col)) < 0)) {
                this.data.swap(root, child);
                root = child;
            } else {
                return;
            }
        }
    }

});
/*
---

name: Jx.Sort.Quicksort

description: An implementation of the quick sort algorithm.

license: MIT-style license.

requires:
 - Jx.Sort

provides: [Jx.Sort.Quicksort]

...
 */
// $Id: quicksort.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Sort.Quicksort
 *
 * Extends: <Jx.Sort>
 *
 * Implementation of a quicksort algorithm designed to
 * work on <Jx.Store> data.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Sort.Quicksort = new Class({
    Family: 'Jx.Sort.Quicksort',
    Extends : Jx.Sort,

    name : 'quicksort',

    /**
     * APIMethod: sort
     * Actually runs the sort on the data
     *
     * returns: the sorted data
     */
    sort : function (left, right) {
        this.fireEvent('start');

        if (!$defined(left)) {
            left = 0;
        }
        if (!$defined(right)) {
            right = this.data.length - 1;
        }

        this.quicksort(left, right);

        this.fireEvent('stop');

        return this.data;

    },

    /**
     * Method: quicksort
     * Initiates the sorting. Is
     * called recursively
     *
     * Parameters:
     * left - the left hand, or lower, bound of the sort
     * right - the right hand, or upper, bound of the sort
     */
    quicksort : function (left, right) {
        if (left >= right) {
            return;
        }

        var index = this.partition(left, right);
        this.quicksort(left, index - 1);
        this.quicksort(index + 1, right);
    },

    /**
     * Method: partition
     *
     * Parameters:
     * left - the left hand, or lower, bound of the sort
     * right - the right hand, or upper, bound of the sort
     */
    partition : function (left, right) {
        this.findMedianOfMedians(left, right);
        var pivotIndex = left,
            pivotValue = (this.data[pivotIndex]).get(this.col),
            index = left,
            i;

        this.data.swap(pivotIndex, right);
        for (i = left; i < right; i++) {
            if (this.comparator((this.data[i]).get(this.col),
                    pivotValue) < 0) {
                this.data.swap(i, index);
                index = index + 1;
            }
        }
        this.data.swap(right, index);

        return index;

    },

    /**
     * Method: findMedianOfMedians
     *
     * Parameters: l
     * eft - the left hand, or lower, bound of the sort
     * right - the right hand, or upper, bound of the sort
     */
    findMedianOfMedians : function (left, right) {
        if (left === right) {
            return this.data[left];
        }

        var i,
            shift = 1,
            endIndex,
            medianIndex;
        while (shift <= (right - left)) {
            for (i = left; i <= right; i += shift * 5) {
                endIndex = (i + shift * 5 - 1 < right) ? i + shift * 5 - 1 : right;
                medianIndex = this.findMedianIndex(i, endIndex,
                        shift);

                this.data.swap(i, medianIndex);
            }
            shift *= 5;
        }

        return this.data[left];
    },

    /**
     * Method: findMedianIndex
     *
     * Parameters:
     * left - the left hand, or lower, bound of the sort
     * right - the right hand, or upper, bound of the sort
     */
    findMedianIndex : function (left, right, shift) {
        var groups = Math.round((right - left) / shift + 1),
            k = Math.round(left + groups / 2 * shift),
            i,
            minIndex,
            v,
            minValue,
            j;
        if (k > this.data.length - 1) {
            k = this.data.length - 1;
        }
        for (i = left; i < k; i += shift) {
            minIndex = i;
            v = this.data[minIndex];
            minValue = v.get(this.col);

            for (j = i; j <= right; j += shift) {
                if (this.comparator((this.data[j]).get(this.col),
                        minValue) < 0) {
                    minIndex = j;
                    minValue = (this.data[minIndex]).get(this.col);
                }
            }
            this.data.swap(i, minIndex);
        }

        return k;
    }
});
/*
---

name: Jx.Sort.Nativesort

description: An implementation of the Javascript native sorting with the Jx.Sort interface

license: MIT-style license.

requires:
 - Jx.Sort

provides: [Jx.Sort.Nativesort]

...
 */
// $Id: nativesort.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Sort.Nativesort
 *
 * Extends: <Jx.Sort>
 *
 * Implementation of a native sort algorithm designed to work on <Jx.Store> data.
 *
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Sort.Nativesort = new Class({
    Family: 'Jx.Sort.Nativesort',
    Extends : Jx.Sort,

    name : 'nativesort',

    /**
     * Method: sort
     * Actually runs the sort on the data
     *
     * Returns:
     * the sorted data
     */
    sort : function () {
        this.fireEvent('start');

        var compare = function (a, b) {
            return this.comparator((this.data[a]).get(this.col), (this.data[b])
                    .get(this.col));
        };

        this.data.sort(compare);
        this.fireEvent('stop');
        return this.data;
    }

});
/*
---

name: Jx.Store.Response

description: The object used to return response information to strategies.

license: MIT-style license.

requires:
 - Jx.Store

provides: [Jx.Store.Response]

...
 */
// $Id: response.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Store.Response
 *
 * Extends: <Jx.Object>
 *
 * This class is used by the protocol to send information back to the calling
 * strategy (or other caller).
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Response = new Class({

    Family: 'Jx.Store.Response',
    Extends: Jx.Object,

    /**
     * Property: code
     * This is the success/failure code
     */
    code: null,
    /**
     * Property: data
     * The data passed received by the protocol.
     */
    data: null,
    /**
     * Property: meta
     * The metadata received by the protocol
     */
    meta: null,
    /**
     * Property: requestType
     * one of 'read', 'insert', 'delete', or 'update'
     */
    requestType: null,
    /**
     * Property: requestParams
     * The parameters passed to the method that created this response
     */
    requestParams: null,
    /**
     * Property: request
     * the mootools Request object used in this operation (if one is actually
     * used)
     */
    request: null,
    /**
     * Property: error
     * the error data received from the called page if any.
     */
    error: null,
    /**
     * APIMethod: success
     * determines if this response represents a successful response
     */
    success: function () {
        return this.code > 0;
    }
});

Jx.Store.Response.WAITING = 2;
Jx.Store.Response.SUCCESS = 1;
Jx.Store.Response.FAILURE = 0;
/*
---

name: Jx.Store.Protocol

description: Base class for all store protocols.

license: MIT-style license.

requires:
 - Jx.Store.Response

provides: [Jx.Store.Protocol]

...
 */
// $Id: protocol.js 995 2010-10-25 14:47:15Z pagameba $
/**
 * Class: Jx.Store.Protocol
 *
 * Extends: <Jx.Object>
 *
 * Base class for all protocols. Protocols are used for communication, primarily,
 * in Jx.Store. It may be possible to adapt them to be used in other places but
 * that is not their intended function.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Protocol = new Class({

    Extends: Jx.Object,
    Family: 'Jx.Store.Protocol',

    parser: null,

    options: {
      combine: {
        insert: false,
        update: false,
        'delete': false
      }
    },

    init: function () {
        this.parent();

        if ($defined(this.options.parser)) {
            this.parser = this.options.parser;
        }
    },

    cleanup: function () {
        this.parser = null;
        this.parent();
    },

    /**
     * APIMethod: read
     * Supports reading data from a location. Abstract method that subclasses
     * should implement.
     *
     * Parameters:
     * options - optional options for configuring the request
     */
    read: $empty,
    /**
     * APIMethod: insert
     * Supports inserting data from a location. Abstract method that subclasses
     * should implement.
     *
     * Parameters:
     * data - the data to use in creating the record in the form of one or more
     *        Jx.Store.Record instances
     * options - optional options for configuring the request
     */
    insert: $empty,
    /**
     * APIMethod: update
     * Supports updating data at a location. Abstract method that subclasses
     * should implement.
     *
     * Parameters:
     * data - the data to update (one or more Jx.Store.Record objects)
     * options - optional options for configuring the request
     */
    update: $empty,
    /**
     * APIMethod: delete
     * Supports deleting data from a location. Abstract method that subclasses
     * should implement.
     *
     * Parameters:
     * data - the data to update (one or more Jx.Store.Record objects)
     * options - optional options for configuring the request
     */
    "delete": $empty,
    /**
     * APIMethod: abort
     * used to abort any of the above methods (where practical). Abstract method
     * that subclasses should implement.
     */
    abort: $empty,
    /**
     * APIMethod: combineRequests
     * tests whether the protocol supports combining multiple records for a given operation
     *
     * Parameter:
     * operation - {String} the operation to test for multiple record support
     *
     * Returns {Boolean} true if the operation supports it, false otherwise
     */
    combineRequests: function(op) {
      return $defined(this.options.combine[op]) ? this.options.combine[op] : false;
    }
});/*
---

name: Jx.Store.Protocol.Local

description: Store protocol used to load data that is already present in a page as an object.

license: MIT-style license.

requires:
 - Jx.Store.Protocol

provides: [Jx.Store.Protocol.Local]

...
 */
// $Id: protocol.local.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Store.Protocol.Local
 *
 * Extends: Jx.Store.Protocol
 *
 * Based on the Protocol base class, the local protocol uses data that it is
 * handed upon instantiation to process requests.
 *
 * Constructor Parameters:
 * data - The data to use
 * options - any options for the base protocol class
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 * inspired by the openlayers.org implementation of a similar system
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Protocol.Local = new Class({

    Extends: Jx.Store.Protocol,

    parameters: ['data', 'options'],
    /**
     * Property: data
     * The data passed to the protocol
     */
    data: null,

    init: function () {
        this.parent();

        if ($defined(this.options.data)) {
            this.data = this.parser.parse(this.options.data);
        }
    },
    /**
     * APIMethod: read
     * process requests for data and sends the appropriate response via the
     * dataLoaded event.
     *
     * Parameters:
     * options - options to use in processing the request.
     */
    read: function (options) {
        var resp = new Jx.Store.Response(),
            page = options.data.page,
            itemsPerPage = options.data.itemsPerPage,
            start,
            end,
            data = this.data;

        resp.requestType = 'read';
        resp.requestParams = arguments;


        if ($defined(data)) {
            if (page <= 1 && itemsPerPage === -1) {
                //send them all
                resp.data = data;
                resp.meta = { count: data.length };
            } else {
                start = (page - 1) * itemsPerPage;
                end = start + itemsPerPage;
                resp.data = data.slice(start, end);
                resp.meta = {
                    page: page,
                    itemsPerPage: itemsPerPage,
                    totalItems: data.length,
                    totalPages: Math.ceil(data.length/itemsPerPage)
                };
            }
            resp.code = Jx.Store.Response.SUCCESS;
            this.fireEvent('dataLoaded', resp);
        } else {
            resp.code = Jx.Store.Response.SUCCESS;
            this.fireEvent('dataLoaded', resp);
        }
    }

    /**
     * The following methods are not implemented as they make no sense for a
     * local protocol:
     * - create
     * - update
     * - delete
     * - commit
     * - abort
     */
});/*
---

name: Jx.Store.Protocol.Ajax

description: Store protocol used to load data from a remote data source via Ajax.

license: MIT-style license.

requires:
 - Jx.Store.Protocol
 - more/Request.Queue

provides: [Jx.Store.Protocol.Ajax]

...
 */
// $Id: protocol.ajax.js 1006 2011-01-01 22:43:42Z jonlb@comcast.net $
/**
 * Class: Jx.Store.Protocol.Ajax
 *
 * Extends: <Jx.Store.Protocol>
 *
 * This protocol is used to send and receive data via AJAX. It also has the
 * capability to use a REST-style API.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Protocol.Ajax = new Class({

    Extends: Jx.Store.Protocol,

    options: {
        /**
         * Option: requestOptions
         * Options to pass to the mootools Request class
         */
        requestOptions: {
            method: 'get'
        },
        /**
         * Option: rest
         * Flag indicating whether this protocol is operating against a RESTful
         * web service
         */
        rest: false,
        /**
         * Option: urls
         * This is a hash of the urls to use for each method. If the rest
         * option is set to true the only one needed will be the urls.rest.
         * These can be overridden if needed by passing an options object into
         * the various methods with the appropriate urls.
         */
        urls: {
            rest: null,
            insert: null,
            read: null,
            update: null,
            'delete': null
        },
        /**
         * Option: queue
         * an object containing options suitable for <Request.Queue>.
         * By default, autoAdvance is set to true and concurrent is set to 1.
         */
        queue: {
          autoAdvance: true,
          concurrent: 1
        }
    },

    queue: null,

    init: function() {
        if (!$defined(Jx.Store.Protocol.Ajax.UniqueId)) {
          Jx.Store.Protocol.Ajax.UniqueId = 1;
        }

        this.queue = new Request.Queue({
          autoAdvance: this.options.queue.autoAdvance,
          concurrent: this.options.queue.concurrent
        });
        this.parent();
    },
    /**
     * APIMethod: read
     * Send a read request via AJAX
     *
     * Parameters:
     * options - the options to pass to the request.
     */
    read: function (options) {
        var resp = new Jx.Store.Response(),
            temp = {},
            opts,
            req,
            uniqueId = Jx.Store.Protocol.Ajax.UniqueId();
        resp.requestType = 'read';
        resp.requestParams = arguments;


        // set up options
        if (this.options.rest) {
            temp.url = this.options.urls.rest;
        } else {
            temp.url = this.options.urls.read;
        }

        opts = $merge(this.options.requestOptions, temp, options);
        opts.onSuccess = this.handleResponse.bind(this,resp);

        req = new Request(opts);
        resp.request = req;

        this.queue.addRequest(uniqueId, req);
        req.send();

        resp.code = Jx.Store.Response.WAITING;

        return resp;

    },
    /**
     * Method: handleResponse
     * Called as an event handler for a returning request. Parses the request's
     * response into the actual response object.
     *
     * Parameters:
     * response - the response related to teh returning request.
     */
    handleResponse: function (response) {
        var req = response.request,
            str = req.xhr.responseText,
            data = this.parser.parse(str);
        if ($defined(data)) {
            if ($defined(data.success) && data.success) {
                if ($defined(data.data)) {
                    response.data = data.data;
                }
                if ($defined(data.meta)) {
                    response.meta = data.meta;
                }
                response.code = Jx.Store.Response.SUCCESS;
            } else {
                response.code = Jx.Store.Response.FAILURE;
                response.error = $defined(data.error) ? data.error : null;
            }
        } else {
            response.code = Jx.Store.Response.FAILURE;
        }
        this.fireEvent('dataLoaded', response);
    },
    /**
     * APIMethod: insert
     * Takes a Jx.Record instance and saves it
     *
     * Parameters:
     * record - a Jx.Store.Record or array of them
     * options - options to pass to the request
     */
    insert: function (record, options) {
        if (this.options.rest) {
            options = $merge({url: this.options.urls.rest},options);
        } else {
            options = $merge({url: this.options.urls.insert},options);
        }
        this.options.requestOptions.method = 'POST';
        return this.run(record, options, "insert");
    },
    /**
     * APIMethod: update
     * Takes a Jx.Record and updates it via AJAX
     *
     * Parameters:
     * record - a Jx.Record instance
     * options - Options to pass to the request
     */
    update: function (record, options) {
        if (this.options.rest) {
            options = $merge({url: this.options.urls.rest},options);
            this.options.requestOptions.method = 'PUT';
        } else {
            options = $merge({url: this.options.urls.update},options);
            this.options.requestOptions.method = 'POST';
        }
        return this.run(record, options, "update");
    },
    /**
     * APIMethod: delete
     * Takes a Jx.Record and deletes it via AJAX
     *
     * Parameters:
     * record - a Jx.Record instance
     * options - Options to pass to the request
     */
    "delete": function (record, options) {
        if (this.options.rest) {
            options = $merge({url: this.options.urls.rest},options);
            this.options.requestOptions.method = 'DELETE';
        } else {
            options = $merge({url: this.options.urls['delete']},options);
            this.options.requestOptions.method = 'POST';
        }
        return this.run(record, options, "delete");
    },
    /**
     * APIMethod: abort
     * aborts the request related to the passed in response.
     *
     * Parameters:
     * response - the response with the request to abort
     */
    abort: function (response) {
        response.request.cancel();

    },
    /**
     * Method: run
     * called by update, delete, and insert methods that actually does the work
     * of kicking off the request.
     *
     * Parameters:
     * record - The Jx.Record to work with
     * options - Options to pass to the request
     * method - The name of the method calling this function
     */
    run: function (record, options, method) {
        var resp = new Jx.Store.Response(),
            opts,
            req,
            data,
            uniqueId = Jx.Store.Protocol.Ajax.UniqueId();

        if (Jx.type(record) == 'array') {
          if (!this.combineRequests(method)) {
            record.each(function(r) {
              this.run(r, options, method);
            }, this);
          } else {
            data = [];
            record.each(function(r) {
              data.push(this.parser.encode(r));
            }, this);
          }
        } else {
          data = this.parser.encode(record);
        }

        this.options.requestOptions.data = $merge(this.options.requestOptions.data, {
          data: data
        });

        resp.requestType = method;
        resp.requestParams = [record, options, method];

        //set up options
        opts = $merge(this.options.requestOptions, options);
        opts.onSuccess = this.handleResponse.bind(this,resp);
        req = new Request(opts);
        resp.request = req;
        this.queue.addRequest(uniqueId, req);
        req.send();

        resp.code = Jx.Store.Response.WAITING;

        return resp;
    }

});
/**
 * Method: uniqueId
 * returns a unique identifier to be used with queued requests
 */
Jx.Store.Protocol.Ajax.UniqueId = (function() {
  var uniqueId = 1;
  return function() {
    return 'req-'+(uniqueId++);
  };
})();
/*
---

name: Jx.Store.Strategy

description: Base class for all store strategies.

license: MIT-style license.

requires:
 - Jx.Store

provides: [Jx.Store.Strategy]


...
 */
// $Id: strategy.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Store.Strategy
 *
 * Extends: <Jx.Object>
 *
 * Base class for all Jx.Store strategies
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Strategy = new Class({

    Extends: Jx.Object,
    Family: 'Jx.Store.Strategy',
    /**
     * APIProperty: store
     * The store this strategy is associated with
     */
    store: null,
    /**
     * APIProperty: active
     * whether this strategy has been activated or not.
     */
    active: null,

    /**
     * Method: init
     * initialize the strategy, should be called by subclasses
     */
    init: function () {
        this.parent();
        this.active = false;
    },
    /**
     * APIMethod: setStore
     * Associates this strategy with a particular store.
     */
    setStore: function (store) {
        if (store instanceof Jx.Store) {
            this.store = store;
            return true;
        }
        return false;
    },

    /**
     * APIMethod: activate
     * activates the strategy if it isn't already active.
     */
    activate: function () {
        if (!this.active) {
            this.active = true;
            return true;
        }
        return false;
    },
    /**
     * APIMethod: deactivate
     * deactivates the strategy if it is already active.
     */
    deactivate: function () {
        if (this.active) {
            this.active = false;
            return true;
        }
        return false;
    }
});/*
---

name: Jx.Store.Strategy.Full

description: Strategy for loading the full data set from a source.

license: MIT-style license.

requires:
 - Jx.Store.Strategy

provides: [Jx.Store.Strategy.Full]

...
 */
// $Id: strategy.full.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Store.Strategy.Full
 *
 * Extends: <Jx.Store.Strategy>
 *
 * This is a strategy for loading all of the data from a source at one time.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */

Jx.Store.Strategy.Full = new Class({

    Extends: Jx.Store.Strategy,

    name: 'full',

    options:{},
    /**
     * Method: init
     * initialize this strategy
     */
    init: function () {
        this.parent();
        this.bound.load = this.load.bind(this);
        this.bound.loadStore = this.loadStore.bind(this);
    },

    /**
     * APIMethod: activate
     * activates the strategy if it isn't already active.
     */
    activate: function () {
        this.parent();
        this.store.addEvent('storeLoad', this.bound.load);

    },

    /**
     * APIMethod: deactivate
     * deactivates the strategy if it is already active.
     */
    deactivate: function () {
        this.parent();
        this.store.removeEvent('storeLoad', this.bound.load);

    },
    /**
     * APIMethod: load
     * Called as the eventhandler for the store load method. Can also
     * be called independently to load data into the current store.
     *
     * Parameters:
     * params - a hash of parameters to use in loading the data.
     */
    load: function (params) {
        this.store.fireEvent('storeBeginDataLoad', this.store);
        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
        var opts = {}
        if ($defined(params)) {
            opts.data = params;
        } else {
            opts.data = {};
        }
        opts.data.page = 0;
        opts.data.itemsPerPage = -1;
        this.store.protocol.read(opts);
    },

    /**
     * Method: loadStore
     * Called as the event handler for the protocol's dataLoaded event. Checks
     * the response for success and loads the data into the store if needed.
     *
     * Parameters:
     * resp - the response from the protocol
     */
    loadStore: function (resp) {
        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
        if (resp.success()) {
            this.store.empty();
            if ($defined(resp.meta)) {
                this.parseMetaData(resp.meta);
            }
            this.store.addRecords(resp.data);
            this.store.loaded = true;
            this.store.fireEvent('storeDataLoaded',this.store);
        } else {
            this.store.loaded = false;
            this.store.fireEvent('storeDataLoadFailed', [this.store, resp]);
        }
    },
    /**
     * Method: parseMetaData
     * Takes the meta property of the response object and puts the data
     * where it belongs.
     *
     * Parameters:
     * meta - the meta data object from the response.
     */
    parseMetaData: function (meta) {
        if ($defined(meta.columns)) {
            this.store.options.columns = meta.columns;
        }
        if ($defined(meta.primaryKey)) {
            this.store.options.recordOptions.primaryKey = meta.primaryKey;
        }
    }
});/*
---

name: Jx.Store.Strategy.Paginate

description: Strategy for loading data in pages and moving between them. This strategy makes sure the store only contains the current page's data.

license: MIT-style license.

requires:
 - Jx.Store.Strategy

provides: [Jx.Store.Strategy.Paginate]


...
 */
// $Id: strategy.paginate.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Store.Strategy.Paginate
 *
 * Extends: <Jx.Store.Strategy>
 *
 * Store strategy for paginating results in a store.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Strategy.Paginate = new Class({

    Extends: Jx.Store.Strategy,

    name: 'paginate',

    options: {
        /**
         * Option: getPaginationParams
         * a function that returns an object that holds the parameters
         * necessary for getting paginated data from a protocol.
         */
        getPaginationParams: function () {
            return {
                page: this.page,
                itemsPerPage: this.itemsPerPage
            };
        },
        /**
         * Option: startingItemsPerPage
         * Used to set the intial itemsPerPage for the strategy. the pageSize
         * can be changed using the setPageSize() method.
         */
        startingItemsPerPage: 25,
        /**
         * Option: startingPage
         * The page to start on. Defaults to 1 but can be set to any other
         * page.
         */
        startingPage: 1,
        /**
         * Option: expirationInterval
         * The interval, in milliseconds (1000 = 1 sec), to hold a page of
         * data before it expires. If the page is expired, the next time the
         * page is accessed it must be retrieved again. Default is 5 minutes
         * (1000 * 60 * 5)
         */
        expirationInterval: (1000 * 60 * 5),
        /**
         * Option: ignoreExpiration
         * Set to TRUE to ignore the expirationInterval setting and never
         * expire pages.
         */
        ignoreExpiration: false
    },
    /**
     * Property: data
     * holds the pages of data keyed by page number.
     */
    data: null,
    /**
     * property: cacheTimer
     * holds one or more cache timer ids - one per page. Each page is set to
     * expire after a certain amount of time.
     */
    cacheTimer: null,
    /**
     * Property: page
     * Tracks the page the store currently holds.
     */
    page: null,
    /**
     * Property: itemsPerPage
     * The number of items on each page
     */
    itemsPerPage: null,

    /**
     * Method: init
     * initialize this strategy
     */
    init: function () {
        this.parent();
        this.data = new Hash();
        this.cacheTimer = new Hash();
        //set up bindings that we need here
        this.bound.load = this.load.bind(this);
        this.bound.loadStore = this.loadStore.bind(this);
        this.itemsPerPage = this.options.startingItemsPerPage;
        this.page = this.options.startingPage;
    },

    /**
     * APIMethod: activate
     * activates the strategy if it isn't already active.
     */
    activate: function () {
        this.parent();
        this.store.addEvent('storeLoad', this.bound.load);
    },

    /**
     * APIMethod: deactivate
     * deactivates the strategy if it is already active.
     */
    deactivate: function () {
        this.parent();
        this.store.removeEvent('storeLoad', this.bound.load);
    },
    /**
     * APIMethod: load
     * Called to load data into the store
     *
     * Parameters:
     * params - a Hash of parameters to use in getting data from the protocol.
     */
    load: function (params) {
        this.store.fireEvent('storeBeginDataLoad', this.store);
        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
        this.params = params;
        var opts = {
            data: $merge(params, this.options.getPaginationParams.apply(this))
        };
        this.store.protocol.read(opts);
    },
    /**
     * Method: loadStore
     * Used to assist in the loading of data into the store. This is
     * called as a response to the protocol finishing.
     *
     *  Parameters:
     *  resp - the response object
     */
    loadStore: function (resp) {
        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
        if (resp.success()) {
            if ($defined(resp.meta)) {
                this.parseMetaData(resp.meta);
            }
            this.data.set(this.page,resp.data);
            this.loadData(resp.data);
        } else {
            this.store.fireEvent('storeDataLoadFailed', this.store);
        }
    },
    /**
     * Method: loadData
     * This method does the actual work of loading data to the store. It is
     * called when either the protocol finishes or setPage() has the data and
     * it's not expired.
     *
     * Parameters:
     * data - the data to load into the store.
     */
    loadData: function (data) {
        this.store.empty();
        this.store.loaded = false;
        if (!this.options.ignoreExpiration) {
            var id = this.expirePage.delay(this.options.expirationInterval, this, this.page);
            this.cacheTimer.set(this.page,id);
        }
        this.store.addRecords(data);
        this.store.loaded = true;
        this.store.fireEvent('storeDataLoaded',this.store);
    },
    /**
     * Method: parseMetaData
     * Takes the metadata returned from the protocol and places it in the
     * appropriate Vplaces.
     *
     * Parameters:
     * meta - the meta data object returned from the protocol.
     */
    parseMetaData: function (meta) {
        if ($defined(meta.columns)) {
            this.store.options.columns = meta.columns;
        }
        if ($defined(meta.totalItems)) {
            this.totalItems = meta.totalItems;
        }
        if ($defined(meta.totalPages)) {
            this.totalPages = meta.totalPages;
        }
        if ($defined(meta.primaryKey)) {
            this.store.options.recordOptions.primaryKey = meta.primaryKey;
        }

    },
    /**
     * Method: expirePage
     * Is called when a pages cache timer expires. Will expire the page by
     * erasing the page and timer. This will force a reload of the data the
     * next time the page is accessed.
     *
     * Parameters:
     * page - the page number to expire.
     */
    expirePage: function (page) {
        this.data.erase(page);
        this.cacheTimer.erase(page);
    },
    /**
     * APIMethod: setPage
     * Allows a caller (i.e. a paging toolbar) to move to a specific page.
     *
     * Parameters:
     * page - the page to move to. Can be any absolute page number, any number
     *        prefaced with '-' or '+' (i.e. '-1', '+3'), 'first', 'last',
     *        'next', or 'previous'
     */
    setPage: function (page) {
        if (Jx.type(page) === 'string') {
            switch (page) {
                case 'first':
                    this.page = 1;
                    break;
                case 'last':
                    this.page = this.totalPages;
                    break;
                case 'next':
                    this.page++;
                    break;
                case 'previous':
                    this.page--;
                    break;
                default:
                    this.page = this.page + Jx.getNumber(page);
                    break;
            }
        } else {
            this.page = page;
        }
        if (this.cacheTimer.has(this.page)) {
            $clear(this.cacheTimer.get(this.page));
            this.cacheTimer.erase(this.page);
        }
        if (this.data.has(this.page)){
            this.loadData(this.data.get(this.page));
        } else {
            this.load(this.params);
        }
    },
    /**
     * APIMethod: getPage
     * returns the current page
     */
    getPage: function () {
        return this.page;
    },
    /**
     * APIMethod: getNumberOfPages
     * returns the total number of pages.
     */
    getNumberOfPages: function () {
        return this.totalPages;
    },
    /**
     * APIMethod: setPageSize
     * sets the current size of the pages. Calling this will expire every page
     * and force the current one to reload with the new size.
     */
    setPageSize: function (size) {
        //set the page size
        this.itemsPerPage = size;
        //invalidate all pages cached and reload the current one only
        this.cacheTimer.each(function(val){
            $clear(val);
        },this);
        this.cacheTimer.empty();
        this.data.empty();
        this.load();
    },
    /**
     * APIMethod: getPageSize
     * returns the current page size
     */
    getPageSize: function () {
        return this.itemsPerPage;
    },
    /**
     * APIMethod: getTotalCount
     * returns the total number of items as received from the protocol.
     */
    getTotalCount: function () {
        return this.totalItems;
    }
});/*
---

name: Jx.Store.Strategy.Progressive

description: Strategy based on Strategy.Paginate but loads data progressively without removing old or curent data from the store.

license: MIT-style license.

requires:
 - Jx.Store.Strategy.Paginate

provides: [Jx.Store.Strategy.Progressive]

...
 */
/**
 * Class: Jx.Store.Strategy.Progressive
 *
 * Extends: <Jx.Store.Strategy.Paginate>
 *
 * Store strategy for progressively obtaining results in a store. You can
 * continually call nextPage() to get the next page and the store will retain
 * all current data. You can set a maximum number of records the store should
 * hold and whether it should dropRecords when that max is hit.
 *
 * License:
 * Copyright (c) 2010, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Strategy.Progressive = new Class({

    Extends: Jx.Store.Strategy.Paginate,

    name: 'progressive',

    options: {
        /**
         * Option: maxRecords
         * The maximum number of records we want in the store at any one time.
         */
        maxRecords: 1000,
        /**
         * Option: dropRecords
         * Whether the strategy should drop records when the maxRecords limit
         * is reached. if this is false then maxRecords is ignored and data is
         * always added to the bottom of the store.
         */
        dropRecords: true
    },
    /**
     * Property: startingPage
     */
    startingPage: 0,
    /**
     * Property: maxPages
     */
    maxPages: null,
    /**
     * Property: loadedPages
     */
    loadedPages: 0,
    /**
     * Property: loadAt
     * Options are 'top' or 'bottom'. Defaults to 'bottom'.
     */
    loadAt: 'bottom',

    /**
     * Method: init
     * initialize this strategy
     */
    init: function () {
        this.parent();
        if (this.options.dropPages) {
            this.maxPages = Math.ceil(this.options.maxRecords/this.itemsPerPage);
        }
    },

    /**
     * Method: loadStore
     * Used to assist in the loading of data into the store. This is
     * called as a response to the protocol finishing.
     *
     *  Parameters:
     *  resp - the response object
     */
    loadStore: function (resp) {
        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
        if (resp.success()) {
            if ($defined(resp.meta)) {
                this.parseMetaData(resp.meta);
            }
            this.loadData(resp.data);
        } else {
            this.store.fireEvent('storeDataLoadFailed', this.store);
        }
    },

    /**
     * Method: loadData
     * This method does the actual work of loading data to the store. It is
     * called when either the protocol finishes or setPage() has the data and
     * it's not expired.
     *
     * Parameters:
     * data - the data to load into the store.
     */
    loadData: function (data) {
        this.store.loaded = false;
        this.store.addRecords(data, this.loadAt);
        this.store.loaded = true;
        this.loadedPages++;
        this.store.fireEvent('storeDataLoaded',this.store);
    },

    /**
     * APIMethod: nextPage
     * Allows a caller (i.e. a paging toolbar) to load more data to the end of
     * the store
     *
     * Parameters:
     * params - a hash of parameters to pass to the request if needed.
     */
    nextPage: function (params) {
        if (!$defined(params)) {
            params = {};
        }
        if (this.options.dropRecords && this.totalPages > this.startingPage + this.loadedPages) {
            this.loadAt = 'bottom';
            if (this.loadedPages >= this.maxPages) {
                //drop records before getting more
                this.startingPage++;
                this.store.removeRecords(0,this.itemsPerPage - 1);
                this.loadedPages--;
            }
        }
        this.page = this.startingPage + this.loadedPages + 1;
        this.load($merge(this.params, params));
    },
    /**
     * APIMethod: previousPage
     * Allows a caller to move back to the previous page.
     *
     * Parameters:
     * params - a hash of parameters to pass to the request if needed.
     */
    previousPage: function (params) {
        //if we're not dropping pages there's nothing to do
        if (!this.options.dropRecords) {
            return;
        }

        if (!$defined(params)) {
            params = {};
        }
        if (this.startingPage > 0) {
            this.loadAt = 'top';
            if (this.loadedPages >= this.maxPages) {
                //drop off end before loading previous pages
                this.startingPage--;
                this.store.removeRecords(this.options.maxRecords - this.itemsPerPage, this.options.maxRecords);
                this.loadedPages--;
            }
            this.page = this.startingPage;
            this.load($merge(this.params, params));
        }
    }
});/*
---

name: Jx.Store.Strategy.Save

description: Strategy used for saving data back to a source. Can be called manually or setup to automatically save on every change.

license: MIT-style license.

requires:
 - Jx.Store.Strategy

provides: [Jx.Store.Strategy.Save]

...
 */
// $Id: strategy.save.js 995 2010-10-25 14:47:15Z pagameba $
/**
 * Class: Jx.Store.Strategy.Save
 *
 * Extends: <Jx.Store.Strategy>
 *
 * A Store strategy class for saving data via protocols
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Strategy.Save = new Class({

    Extends: Jx.Store.Strategy,

    name: 'save',

    options: {
        /**
         * Option: autoSave
         * Whether the strategy should be watching the store to save changes
         * automatically. Set to True to watch events, set it to a number of
         * milliseconds to have the strategy save every so many seconds
         */
        autoSave: false
    },
    /**
     * Property: failedChanges
     * an array holding all failed requests
     */
    failedChanges: [],
    /**
     * Property: successfulChanges
     * an array holding all successful requests
     */
    successfulChanges: [],
    /**
     * Property: totalChanges
     * The total number of changes being processed. Used to determine
     * when to fire off the storeChangesCompleted event on the store
     */
    totalChanges: 0,

    /**
     * Method: init
     * initialize this strategy
     */
    init: function () {
        this.bound.save = this.saveRecord.bind(this);
        this.bound.update = this.updateRecord.bind(this);
        this.bound.completed = this.onComplete.bind(this);
        this.parent();
    },

    /**
     * APIMethod: activate
     * activates the strategy if it isn't already active.
     */
    activate: function () {
        this.parent();
        if (Jx.type(this.options.autoSave) === 'number') {
            this.periodicalId = this.save.periodical(this.options.autoSave, this);
        } else if (this.options.autoSave) {
            this.store.addEvent('storeRecordAdded', this.bound.save);
            this.store.addEvent('storeColumnChanged', this.bound.update);
            this.store.addEvent('storeRecordDeleted', this.bound.save);
        }

    },

    /**
     * APIMethod: deactivate
     * deactivates the strategy if it is already active.
     */
    deactivate: function () {
        this.parent();
        if ($defined(this.periodicalId)) {
            $clear(this.periodicalId);
        } else if (this.options.autoSave) {
            this.store.removeEvent('storeRecordAdded', this.bound.save);
            this.store.removeEvent('storeColumnChanged', this.bound.update);
            this.store.removeEvent('storeRecordDeleted', this.bound.save);
        }

    },

    /**
     * APIMethod: updateRecord
     * called by event handlers when store data is updated
     *
     * Parameters:
     * index - {Integer} the row that was affected
     * column - {String} the column that was affected
     * oldValue - {Mixed} the previous value
     * newValue - {Mixed} the new value
     */
    updateRecord: function(index, column, oldValue, newValue) {
      var resp = this.saveRecord(this.store, this.store.getRecord(index));
      // no response if updating or record state not set
      if (resp) {
        resp.index = index;
      }
    },
    /**
     * APIMethod: saveRecord
     * Called by event handlers when a store record is added, or deleted.
     * If deleted, the record will be removed from the deleted array.
     *
     * Parameters:
     * record - The Jx.Record instance that was changed
     * store - The instance of the store
     */
    saveRecord: function (store, record) {
        //determine the status and route based on that
        if (!this.updating && $defined(record.state)) {
            if (this.totalChanges === 0) {
                store.protocol.addEvent('dataLoaded', this.bound.completed);
            }
            this.totalChanges++;
            var ret;
            switch (record.state) {
                case Jx.Record.UPDATE:
                    ret = store.protocol.update(record);
                    break;
                case Jx.Record.DELETE:
                    ret = store.protocol['delete'](record);
                    break;
                case Jx.Record.INSERT:
                    ret = store.protocol.insert(record);
                    break;
                default:
                  break;
            }
            return ret;
        }
    },
    /**
     * APIMethod: save
     * Called manually when the developer wants to save all data changes
     * in one shot. It will empty the deleted array and reset all other status
     * flags
     */
    save: function () {
        //go through all of the data and figure out what needs to be acted on
        if (this.store.loaded) {
            var records = [];
            records[Jx.Record.UPDATE] = [];
            records[Jx.Record.INSERT] = [];

            this.store.data.each(function (record) {
                if ($defined(record) && $defined(record.state)) {
                    records[record.state].push(record);
                }
            }, this);
            records[Jx.Record.DELETE] = this.store.deleted;

            if (!this.updating) {
              if (this.totalChanges === 0) {
                  store.protocol.addEvent('dataLoaded', this.bound.completed);
              }
              this.totalChanges += records[Jx.Record.UPDATE].length +
                                   records[Jx.Record.INSERT].length +
                                   records[Jx.Record.DELETE].length;
              if (records[Jx.Record.UPDATE].length) {
                this.store.protocol.update(records[Jx.Record.UPDATE]);
              }
              if (records[Jx.Record.INSERT].length) {
                this.store.protocol.insert(records[Jx.Record.INSERT]);
              }
              if (records[Jx.Record.DELETE].length) {
                this.store.protocol['delete'](records[Jx.Record.DELETE]);
              }
            }

            // records.flatten().each(function (record) {
            //     this.saveRecord(this.store, record);
            // }, this);
        }

    },
    /**
     * Method: onComplete
     * Handles processing of the response(s) from the protocol. Each
     * update/insert/delete will have an individual response. If any responses
     * come back failed we will hold that response and send it to the caller
     * via the fired event. This method is responsible for updating the status
     * of each record as it returns and on inserts, it updates the primary key
     * of the record. If it was a delete it will remove it permanently from
     * the store's deleted array (provided it returns successful - based on
     * the success attribute of the meta object). When all changes have been
     * accounted for the method fires a finished event and passes all of the
     * failed responses to the caller so they can be handled appropriately.
     *
     * Parameters:
     * response - the response returned from the protocol
     */
    onComplete: function (response) {
        if (!response.success() || ($defined(response.meta) && !response.meta.success)) {
            this.failedChanges.push(response);
        } else {
            //process the response
            var records = [response.requestParams[0]].flatten(),
                responseData = $defined(response.data) ? [response.data].flatten() : null;
            records.each(function(record, index) {
              if (response.requestType === 'delete') {
                  this.store.deleted.erase(record);
              } else {
                  if (response.requestType === 'insert' || response.requestType == 'update') {
                      if (responseData && $defined(responseData[index])) {
                          this.updating = true;
                          $H(responseData[index]).each(function (val, key) {
                              var d = record.set(key, val);
                              if (d[1] != val) {
                                d.unshift(index);
                                record.store.fireEvent('storeColumnChanged', d);
                              }
                          });
                          this.updating = false;
                      }
                  }
                  record.state = null;
              }
              this.totalChanges--;
          }, this);
          this.successfulChanges.push(response);
        }
        if (this.totalChanges === 0) {
            this.store.protocol.removeEvent('dataLoaded', this.bound.completed);
            this.store.fireEvent('storeChangesCompleted', {
                successful: this.successfulChanges,
                failed: this.failedChanges
            });
        }
    }
});/*
---

name: Jx.Store.Strategy.Sort

description: Strategy used for sorting results in a store after they are loaded.

license: MIT-style license.

requires:
 - Jx.Store.Strategy
 - Jx.Sort.Mergesort
 - Jx.Compare

provides: [Jx.Store.Strategy.Sort]
...
 */
// $Id: strategy.sort.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Store.Strategy.Sort
 *
 * Extends: <Jx.Store.Strategy>
 *
 * Strategy used for sorting stores. It can either be called manually or it
 * can listen for specific events from the store.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Strategy.Sort = new Class({

    Extends: Jx.Store.Strategy,

    name: 'sort',

    options: {
        /**
         * Option: sortOnStoreEvents
         * an array of events this strategy should listen for on the store and
         * sort when it sees them.
         */
        sortOnStoreEvents: ['storeColumnChanged','storeDataLoaded'],
        /**
         * Option: defaultSort
         * The default sorting type, currently set to merge but can be any of
         * the sorters available
         */
        defaultSort : 'merge',
        /**
         * Option: separator
         * The separator to pass to the comparator
         * constructor (<Jx.Compare>) - defaults to '.'
         */
        separator : '.',
        /**
         * Option: sortCols
         * An array of columns to sort by arranged in the order you want
         * them sorted.
         */
        sortCols : []
    },

    /**
     * Property: sorters
     * an object listing the different sorters available
     */
    sorters : {
        quick : "Quicksort",
        merge : "Mergesort",
        heap : "Heapsort",
        'native' : "Nativesort"
    },

    /**
     * Method: init
     * initialize this strategy
     */
    init: function () {
        this.parent();
        this.bound.sort = this.sort.bind(this);
    },

    /**
     * APIMethod: activate
     * activates the strategy if it isn't already active.
     */
    activate: function () {
        if ($defined(this.options.sortOnStoreEvents)) {
            this.options.sortOnStoreEvents.each(function (ev) {
                this.store.addEvent(ev, this.bound.sort);
            },this);
        }
    },

    /**
     * APIMethod: deactivate
     * deactivates the strategy if it is already active.
     */
    deactivate: function () {
        if ($defined(this.options.sortOnStoreEvents)) {
            this.options.sortOnStoreEvents.each(function (ev) {
                this.store.removeEvent(ev, this.bound.sort);
            },this);
        }
    },

    /**
     * APIMethod: sort
     * Runs the sorting and grouping
     *
     * Parameters:
     * cols - Optional. An array of columns to sort/group by
     * sort - the sort type (quick,heap,merge,native),defaults to
     *     options.defaultSort
     * dir - the direction to sort. Set to "desc" for descending,
     * anything else implies ascending (even null).
     */
    sort : function (cols, sort, dir) {
        if (this.store.count()) {
            this.store.fireEvent('sortStart', this);
            var c;
            if ($defined(cols) && Jx.type(cols) === 'array') {
                c = this.options.sortCols = cols;
            } else if ($defined(cols) && Jx.type(cols) === 'string') {
                this.options.sortCols = [];
                this.options.sortCols.push(cols);
                c = this.options.sortCols;
            } else if ($defined(this.options.sortCols)) {
                c = this.options.sortCols;
            } else {
                return null;
            }

            this.sortType = sort;
            // first sort on the first array item
            this.store.data = this.doSort(c[0], sort, this.store.data, true);

            if (c.length > 1) {
                this.store.data = this.subSort(this.store.data, 0, 1);
            }

            if ($defined(dir) && dir === 'desc') {
                this.store.data.reverse();
            }

            this.store.fireEvent('storeSortFinished', this);
        }
    },

    /**
     * Method: subSort
     * Does the actual group sorting.
     *
     * Parameters:
     * data - what to sort
     * groupByCol - the column that determines the groups
     * sortCol - the column to sort by
     *
     * returns: the result of the grouping/sorting
     */
    subSort : function (data, groupByCol, sortByCol) {

        if (sortByCol >= this.options.sortCols.length) {
            return data;
        }
        /**
         *  loop through the data array and create another array with just the
         *  items for each group. Sort that sub-array and then concat it
         *  to the result.
         */
        var result = [];
        var sub = [];

        var groupCol = this.options.sortCols[groupByCol];
        var sortCol = this.options.sortCols[sortByCol];

        var group = data[0].get(groupCol);
        this.sorter.setColumn(sortCol);
        for (var i = 0; i < data.length; i++) {
            if (group === (data[i]).get(groupCol)) {
                sub.push(data[i]);
            } else {
                // sort

                if (sub.length > 1) {
                    result = result.concat(this.subSort(this.doSort(sortCol, this.sortType, sub, true), groupByCol + 1, sortByCol + 1));
                } else {
                    result = result.concat(sub);
                }

                // change group
                group = (data[i]).get(groupCol);
                // clear sub
                sub.empty();
                // add to sub
                sub.push(data[i]);
            }
        }

        if (sub.length > 1) {
            this.sorter.setData(sub);
            result = result.concat(this.subSort(this.doSort(sortCol, this.sortType, sub, true), groupByCol + 1, sortByCol + 1));
        } else {
            result = result.concat(sub);
        }

        //this.data = result;

        return result;
    },

    /**
     * Method: doSort
     * Called to change the sorting of the data
     *
     * Parameters:
     * col - the column to sort by
     * sort - the kind of sort to use (see list above)
     * data - the data to sort (leave blank or pass null to sort data
     * existing in the store)
     * ret - flag that tells the function whether to pass
     * back the sorted data or store it in the store
     * options - any options needed to pass to the sorter upon creation
     *
     * returns: nothing or the data depending on the value of ret parameter.
     */
    doSort : function (col, sort, data, ret, options) {
        options = {} || options;

        sort = (sort) ? this.sorters[sort] : this.sorters[this.options.defaultSort];
        data = data ? data : this.data;
        ret = ret ? true : false;

        if (!$defined(this.comparator)) {
            this.comparator = new Jx.Compare({
                separator : this.options.separator
            });
        }

        this.col = col = this.resolveCol(col);

        var fn = this.comparator[col.type].bind(this.comparator);
        if (!$defined(this.sorter)) {
            this.sorter = new Jx.Sort[sort](data, fn, col.name, options);
        } else {
            this.sorter.setComparator(fn);
            this.sorter.setColumn(col.name);
            this.sorter.setData(data);
        }
        var d = this.sorter.sort();

        if (ret) {
            return d;
        } else {
            this.data = d;
        }
    },
    /**
     * Method: resolveCol
     * resolves the given column identifier and resolves it to the
     * actual column object in the store.
     *
     * Parameters:
     * col - the name or index of the required column.
     */
    resolveCol: function (col) {
        var t = Jx.type(col);
        if (t === 'number') {
            col = this.store.options.columns[col];
        } else if (t === 'string') {
            this.store.options.columns.each(function (column) {
                if (column.name === col) {
                    col = column;
                }
            }, this);
        }
        return col;
    }
});/*
---

name: Jx.Store.Parser

description: Base class for all data parsers. Parsers are used by protocols to get data received or sent in the proper formats.

license: MIT-style license.

requires:
 - Jx.Store

provides: [Jx.Store.Parser]

...
 */
// $Id: parser.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Store.Parser
 *
 * Extends: <Jx.Object>
 *
 * Base class for all parsers
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */

Jx.Store.Parser = new Class({

    Extends: Jx.Object,
    Family: 'Jx.Store.Parser',

    /**
     * APIMethod: parse
     * Reads data passed to it by a protocol and parses it into a specific
     * format needed by the store/record.
     *
     * Parameters:
     * data - string of data to parse
     */
    parse: $empty,
    /**
     * APIMethod: encode
     * Takes an Jx.Record object and encodes it into a format that can be transmitted
     * by a protocol.
     *
     * Parameters:
     * object - an object to encode
     */
    encode: $empty
});/*
---

name: Jx.Store.Parser.JSON

description: Parser for reading and writting JSON formatted data.

license: MIT-style license.

requires:
 - Jx.Store.Parser
 - Core/JSON

provides: [Jx.Store.Parser.JSON]

...
 */
// $Id: parser.json.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Store.Parser.JSON
 *
 * Extends: <Jx.Store.Parser>
 *
 * A Parser that handles encoding and decoding JSON strings
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Store.Parser.JSON = new Class({

    Extends: Jx.Store.Parser,

    options: {
        /**
         * Option: secure
         * Whether to use secure decoding. When using secure decoding the
         * parser will return null if any invalid JSON characters are in the
         * passed in string. Defaults to false.
         */
        secure: false
    },
    /**
     * APIMethod: parse
     * Turns a string into a JSON object if possible.
     *
     * Parameters:
     * data - the string representation of the data we're parsing
     */
    parse: function (data) {
        var type = Jx.type(data);

        if (type === 'string') {
            return JSON.decode(data, this.options.secure);
        }
        //otherwise just return the data object
        return data;
    },

    /**
     * APIMethod: encode
     * Takes an object and turns it into JSON.
     *
     * Parameters:
     * object - the object to encode
     */
    encode: function (object) {
        var data;
        if (object instanceof Jx.Record) {
            data = object.asHash();
        } else {
            data = object;
        }

        return JSON.encode(data);
    }
});/*
---

name: Jx.Button

description: Jx.Button creates a clickable element that can be added to a web page.

license: MIT-style license.

requires:
 - Jx.Widget

optional:
 - Core/Drag

provides: [Jx.Button]

css:
 - button

images:
 - button.png

...
 */
// $Id: button.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Button
 *
 * Extends: <Jx.Widget>
 *
 * Jx.Button creates a clickable element that can be added to a web page.
 * When the button is clicked, it fires a 'click' event.
 *
 * When you construct a new instance of Jx.Button, the button does not
 * automatically get inserted into the web page.  Typically a button
 * is used as part of building another capability such as a Jx.Toolbar.
 * However, if you want to manually insert the button into your application,
 * you may use the <Jx.Button::addTo> method to append or insert the button into the
 * page.
 *
 * There are two modes for a button, normal and toggle.  A toggle button
 * has an active state analogous to a checkbox.  A toggle button generates
 * different events (down and up) from a normal button (click).  To create
 * a toggle button, pass toggle: true to the Jx.Button constructor.
 *
 * To use a Jx.Button in an application, you should to register for the
 * 'click' event.  You can pass a function in the 'onClick' option when
 * constructing a button or you can call the addEvent('click', myFunction)
 * method.  The addEvent method can be called several times, allowing more
 * than one function to be called when a button is clicked.  You can use the
 * removeEvent('click', myFunction) method to stop receiving click events.
 *
 * Example:
 *
 * (code)
 * var button = new Jx.Button(options);
 * button.addTo('myListItem'); // the id of an LI in the page.
 * (end)
 *
 * (code)
 * Example:
 * var options = {
 *     imgPath: 'images/mybutton.png',
 *     tooltip: 'click me!',
 *     label: 'click me',
 *     onClick: function() {
 *         alert('you clicked me');
 *     }
 * };
 * var button = new Jx.Button(options);
 * button.addEvent('click', anotherFunction);
 *
 * function anotherFunction() {
 *   alert('a second alert for a single click');
 * }
 * (end)
 *
 * Events:
 * click - the button was pressed and released (only if type is not 'toggle').
 * down - the button is down (only if type is 'toggle')
 * up - the button is up (only if the type is 'toggle').
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Button = new Class({
    Family: 'Jx.Button',
    Extends: Jx.Widget,

    options: {
        /* Option: image
         * optional.  A string value that is the url to load the image to
         * display in this button.  The default styles size this image to 16 x
         * 16.  If not provided, then the button will have no icon.
         */
        image: '',
        /* Option: tooltip
         * optional.  A string value to use as the alt/title attribute of the
         * <A> tag that wraps the button, resulting in a tooltip that appears
         * when the user hovers the mouse over a button in most browsers.  If
         * not provided, the button will have no tooltip.
         */
        tooltip: '',
        /* Option: label
         * optional, default is no label.  A string value that is used as a
         * label on the button. - use an object for localization: { set: 'Examples', key: 'lanKey', value: 'langValue' }
         * see widget.js for details
         */
        label: '',
        /* Option: toggle
         * default true, whether the button is a toggle button or not.
         */
        toggle: false,
        /* Option: toggleClass
         * A class to apply to the button if it is a toggle button,
         * 'jxButtonToggle' by default.
         */
        toggleClass: 'jxButtonToggle',
        /* Option: pressedClass
         * A class to apply to the button when it is pressed,
         * 'jxButtonPressed' by default.
         */
        pressedClass: 'jxButtonPressed',
        /* Option: activeClass
         * A class to apply to the buttonwhen it is active,
         * 'jxButtonActive' by default.
         */
        activeClass: 'jxButtonActive',

        /* Option: active
         * optional, default false.  Controls the initial state of toggle
         * buttons.
         */
        active: false,
        /* Option: enabled
         * whether the button is enabled or not.
         */
        enabled: true,
        /* Option: href
         * set an href on the button's action object, typically an <a> tag.
         * Default is javascript:void(0) and use onClick.
         */
        href: 'javascript:void(0);',
        /* Option: target
         * for buttons that have an href, allow setting the target
         */
        target: '',
        /* Option: template
         * the HTML structure of the button.  As a minimum, there must be a
         * containing element with a class of jxButtonContainer and an
         * internal element with a class of jxButton.  jxButtonIcon and
         * jxButtonLabel are used if present to put the image and label into
         * the button.
         */
        template: '<span class="jxButtonContainer"><a class="jxButton"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'
    },

    /**
     * Property: classes
     * used to auto-populate this object with element references when
     * processing templates
     */
    classes: new Hash({
        domObj: 'jxButtonContainer',
        domA: 'jxButton',
        domImg: 'jxButtonIcon',
        domLabel: 'jxButtonLabel'
    }),

    /**
     * Method: render
     * create a new button.
     */
    render: function() {
        this.parent();
        var options = this.options,
            hasFocus,
            mouseDown;
        /* is the button toggle-able? */
        if (options.toggle) {
            this.domObj.addClass(options.toggleClass);
        }

        // the clickable part of the button
        if (this.domA) {
            this.domA.set({
                target: options.target,
                href: options.href,
                title: this.getText(options.tooltip),
                alt: this.getText(options.tooltip)
            });
            this.domA.addEvents({
                click: this.clicked.bindWithEvent(this),
                drag: (function(e) {e.stop();}).bindWithEvent(this),
                mousedown: (function(e) {
                    this.domA.addClass(options.pressedClass);
                    hasFocus = true;
                    mouseDown = true;
                    this.focus();
                }).bindWithEvent(this),
                mouseup: (function(e) {
                    this.domA.removeClass(options.pressedClass);
                    if (!this.options.active) //MG Fusion #549
                        this.blur();
                    mouseDown = false;
                }).bindWithEvent(this),
                mouseleave: (function(e) {
                    this.domA.removeClass(options.pressedClass);
                }).bindWithEvent(this),
                mouseenter: (function(e) {
                    if (hasFocus && mouseDown) {
                        this.domA.addClass(options.pressedClass);
                    }
                }).bindWithEvent(this),
                keydown: (function(e) {
                    if (e.key == 'enter') {
                        this.domA.addClass(options.pressedClass);
                    }
                }).bindWithEvent(this),
                keyup: (function(e) {
                    if (e.key == 'enter') {
                        this.domA.removeClass(options.pressedClass);
                    }
                }).bindWithEvent(this),
                blur: function() { hasFocus = false; }
            });

            if (typeof Drag != 'undefined') {
                new Drag(this.domA, {
                    onStart: function() {this.stop();}
                });
            }
        }

        if (this.domImg) {
            if (options.image || !options.label) {
                this.domImg.set({
                    title: this.getText(options.tooltip),
                    alt: this.getText(options.tooltip)
                });
                if (options.image && options.image.indexOf(Jx.aPixel.src) == -1) {
                    this.domImg.setStyle('backgroundImage',"url("+options.image+")");
                }
                if (options.imageClass) {
                    this.domImg.addClass(options.imageClass);
                }
            } else {
                //remove the image if we don't need it
                this.domImg.setStyle('display','none');
            }
        }

        if (this.domLabel) {
            if (options.label || this.domA.hasClass('jxDiscloser')) {
                this.setLabel(options.label);
            } else {
                //this.domLabel.removeClass('jx'+this.type+'Label');
                this.domLabel.setStyle('display','none');
            }
        }

        if (options.id) {
            this.domObj.set('id', options.id);
        }

        //update the enabled state
        this.setEnabled(options.enabled);

        //update the active state if necessary
        if (options.active) {
            options.active = false;
            this.setActive(true);
        }
    },
    /**
     * APIMethod: clicked
     * triggered when the user clicks the button, processes the
     * actionPerformed event
     *
     * Parameters:
     * evt - {Event} the user click event
     */
    clicked : function(evt) {
        var options = this.options;
        if (options.enabled && !this.isBusy()) {
            if (options.toggle) {
                this.setActive(!options.active);
            } else {
                this.fireEvent('click', {obj: this, event: evt});
            }
        }
        //return false;
    },
    /**
     * APIMethod: isEnabled
     * This returns true if the button is enabled, false otherwise
     *
     * Returns:
     * {Boolean} whether the button is enabled or not
     */
    isEnabled: function() {
        return this.options.enabled;
    },

    /**
     * APIMethod: setEnabled
     * enable or disable the button.
     *
     * Parameters:
     * enabled - {Boolean} the new enabled state of the button
     */
    setEnabled: function(enabled) {
        this.options.enabled = enabled;
        if (enabled) {
            this.domObj.removeClass('jxDisabled');
        } else {
            this.domObj.addClass('jxDisabled');
        }
    },
    /**
     * APIMethod: isActive
     * For toggle buttons, this returns true if the toggle button is
     * currently active and false otherwise.
     *
     * Returns:
     * {Boolean} the active state of a toggle button
     */
    isActive: function() {
        return this.options.active;
    },
    /**
     * APIMethod: setActive
     * Set the active state of the button
     *
     * Parameters:
     * active - {Boolean} the new active state of the button
     */
    setActive: function(active) {
        var options = this.options;
        if (options.enabled && !this.isBusy()) {
          if (options.active == active) {
              return;
          }
          options.active = active;
          if (this.domA) {
              if (options.active) {
                  this.domA.addClass(options.activeClass);
              } else {
                  this.domA.removeClass(options.activeClass);
                  this.blur(); //MG Fusion 549
              }
          }
          this.fireEvent(active ? 'down':'up', this);
        }
    },
    /**
     * APIMethod: setImage
     * set the image of this button to a new image URL
     *
     * Parameters:
     * path - {String} the new url to use as the image for this button
     */
    setImage: function(path) {
        this.options.image = path;
        if (this.domImg) {
            this.domImg.setStyle('backgroundImage',
                                 "url("+path+")");
            this.domImg.setStyle('display', path ? null : 'none');
        }
    },
    /**
     * APIMethod: setLabel
     * sets the text of the button.
     *
     * Parameters:
     * label - {String} the new label for the button
     */
    setLabel: function(label) {
        this.options.label = label;
        if (this.domLabel) {
            this.domLabel.set('html', this.getText(label));
            this.domLabel.setStyle('display', label || this.domA.hasClass('jxDiscloser') ? null : 'none');
        }
    },
    /**
     * APIMethod: getLabel
     * returns the text of the button.
     */
    getLabel: function() {
        return this.options.label;
    },
    /**
     * APIMethod: setTooltip
     * sets the tooltip displayed by the button
     *
     * Parameters:
     * tooltip - {String} the new tooltip
     */
    setTooltip: function(tooltip) {
        if (this.domA) {
            this.domA.set({
                'title':this.getText(tooltip),
                'alt':this.getText(tooltip)
            });
        }
        //need to account for the tooltip on the image as well
        if (this.domImg) {
            //check if title and alt are set...
            var t = this.domImg.get('title');
            if ($defined(t)) {
                //change it...
                this.domImg.set({
                    'title':this.getText(tooltip),
                    'alt':this.getText(tooltip)
                });
            }
        }
    },
    /**
     * APIMethod: focus
     * capture the keyboard focus on this button
     */
    focus: function() {
        if (this.domA) {
            this.domA.focus();
        }
    },
    /**
     * APIMethod: blur
     * remove the keyboard focus from this button
     */
    blur: function() {
        if (this.domA) {
            this.domA.blur();
        }
    },

    /**
     * APIMethod: changeText
     *
     * updates the label of the button on langChange Event for
     * Internationalization
     */
    changeText : function(lang) {
        this.parent();
        this.setLabel(this.options.label);
        this.setTooltip(this.options.tooltip);
    }
});
/*
---

name: Jx.Button.Flyout

description: Flyout buttons expose a panel when the user clicks the button.

license: MIT-style license.

requires:
 - Jx.Button

provides: [Jx.Button.Flyout]

images:
 - flyout_chrome.png
 - emblems.png

...
 */
// $Id: flyout.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Button.Flyout
 *
 * Extends: <Jx.Button>
 *
 * Flyout buttons expose a panel when the user clicks the button.  The
 * panel can have arbitrary content.  You must provide any necessary
 * code to hook up elements in the panel to your application.
 *
 * When the panel is opened, the 'open' event is fired.  When the panel is
 * closed, the 'close' event is fired.  You can register functions to handle
 * these events in the options passed to the constructor (onOpen, onClose).
 *
 * The user can close the flyout panel by clicking the button again, by
 * clicking anywhere outside the panel and other buttons, or by pressing the
 * 'esc' key.
 *
 * Flyout buttons implement <Jx.ContentLoader> which provides the hooks to
 * insert content into the Flyout element.  Note that the Flyout element
 * is not appended to the DOM until the first time it is opened, and it is
 * removed from the DOM when closed.
 *
 * It is generally best to specify a width and height for your flyout content
 * area through CSS to ensure that it works correctly across all browsers.
 * You can do this for all flyouts using the .jxFlyout CSS selector, or you
 * can apply specific styles to your content elements.
 *
 * A flyout closes other flyouts when it is opened.  It is possible to embed
 * flyout buttons inside the content area of another flyout button.  In this
 * case, opening the inner flyout will not close the outer flyout but it will
 * close any other flyouts that are siblings.
 *
 * Example:
 * (code)
 * var flyout = new Jx.Button.Flyout({
 *      label: 'flyout',
 *      content: 'flyoutContent',
 *      onOpen: function(flyout) {
 *          console.log('flyout opened');
 *      },
 *      onClose: function(flyout) {
 *          console.log('flyout closed');
 *      }
 * });
 * (end)
 *
 * Events:
 * open - this event is triggered when the flyout is opened.
 * close - this event is triggered when the flyout is closed.
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Button.Flyout = new Class({
    Family: 'Jx.Button.Flyout',
    Extends: Jx.Button,
    Binds: ['keypressHandler', 'clickHandler'],
    options: {
        /* Option: template
         * the HTML structure of the flyout button
         */
        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel "></span></a></span>',
        /* Option: contentTemplate
         * the HTML structure of the flyout content area
         */
        contentTemplate: '<div class="jxFlyout"><div class="jxFlyoutContent"></div></div>',
        /* Option: position
         * where to position the flyout, see Jx.Widget::position
         * for details on how to specify this option
         */
        position: {
          horizontal: ['left left', 'right right'],
          vertical: ['bottom top', 'top bottom']
        },
        /* Option: positionElement
         * the element to position the flyout relative to, by default
         * it is the domObj of this button and should only be changed
         * if you really know what you are doing
         */
        positionElement: null
    },

    /**
     * Property: contentClasses
     * the classes array for processing the contentTemplate
     */
    contentClasses: new Hash({
        contentContainer: 'jxFlyout',
        content: 'jxFlyoutContent'
    }),

    /**
     * Property: content
     * the HTML element that contains the flyout content
     */
    content: null,
    /**
     * Method: render
     * construct a new instance of a flyout button.
     */
    render: function() {
        var options = this.options;
        if (!Jx.Button.Flyout.Stack) {
            Jx.Button.Flyout.Stack = [];
        }
        this.parent();
        this.processElements(options.contentTemplate, this.contentClasses);

        if (options.contentClass) {
            this.content.addClass(options.contentClass);
        }

        this.content.store('jxFlyout', this);
        if(!options.loadOnDemand || options.active) {
          this.loadContent(this.content);
        }else{
          this.addEvent('contentLoaded', function(ev) {
            this.show(ev);
          }.bind(this));
        }
    },
    cleanup: function() {
      this.content.eliminate('jxFlyout');
      this.content.destroy();
      this.contentClasses.each(function(v,k){
        this[k] = null;
      }, this);
      this.parent();
    },
    /**
     * APIMethod: clicked
     * Override <Jx.Button::clicked> to hide/show the content area of the
     * flyout.
     *
     * Parameters:
     * e - {Event} the user event
     */
    clicked: function(e) {
        var options = this.options;
        if (!options.enabled) {
            return;
        }
        if (this.contentIsLoaded && options.cacheContent) {
          this.show(e);
        // load on demand or reload content if caching is disabled
        } else if (options.loadOnDemand || !options.cacheContent) {
          this.loadContent(this.content);
        } else {
          this.show(e);
        }
    },
   /**
    * Private Method: show
    * Shows the Flyout after the content is loaded asynchronously
    *
    * Parameters:
    * e - {Event} - the user or contentLoaded event
    */
    show: function(e) {
        var node,
            flyout,
            owner = this.owner,
            stack = Jx.Button.Flyout.Stack,
            options = this.options;
       /* find out what we are contained by if we don't already know */
        if (!owner) {
            this.owner = owner = document.body;
            var node = document.id(this.domObj.parentNode);
            while (node != document.body && owner == document.body) {
                var flyout = node.retrieve('jxFlyout');
                if (flyout) {
                    this.owner = owner = flyout;
                    break;
                } else {
                    node = document.id(node.parentNode);
                }
            }
        }
        if (stack[stack.length - 1] == this) {
            this.hide();
            return;
        } else if (owner != document.body) {
            /* if we are part of another flyout, close any open flyouts
             * inside the parent and register this as the current flyout
             */
            if (owner.currentFlyout == this) {
                /* if the flyout to close is this flyout,
                 * hide this and return */
                this.hide();
                return;
            } else if (owner.currentFlyout) {
                owner.currentFlyout.hide();
            }
            owner.currentFlyout = this;
        } else {
            /* if we are at the top level, close the entire stack before
             * we open
             */
            while (stack.length) {
                stack[stack.length - 1].hide();
            }
        }
        // now we go on the stack.
        stack.push(this);
        this.fireEvent('beforeOpen');

        options.active = true;
        this.domA.addClass(options.activeClass);
        this.contentContainer.setStyle('visibility','hidden');
        document.id(document.body).adopt(this.contentContainer);
        this.content.getChildren().each(function(child) {
            if (child.resize) {
                child.resize();
            }
        });
        this.showChrome(this.contentContainer);

        var rel = options.positionElement || this.domObj;
        var pos = $merge(options.position, {
          offsets: this.chromeOffsets
        });
        this.position(this.contentContainer, rel, pos);

        /* we have to size the container for IE to render the chrome correctly
         * there is some horrible peekaboo bug in IE 6
         */
        this.contentContainer.setContentBoxSize(document.id(this.content).getMarginBoxSize());

        this.stack(this.contentContainer);
        this.contentContainer.setStyle('visibility','');

        document.addEvent('keydown', this.keypressHandler);
        document.addEvent('click', this.clickHandler);
        this.fireEvent('open', this);
    },

    /**
     * APIMethod: hide
     * Closes the flyout if open
     */
    hide: function() {
        if (this.owner != document.body) {
            this.owner.currentFlyout = null;
        }
        Jx.Button.Flyout.Stack.pop();
        this.setActive(false);
        this.contentContainer.dispose();
        this.unstack(this.contentContainer);
        document.removeEvent('keydown', this.keypressHandler);
        document.removeEvent('click', this.clickHandler);
        this.fireEvent('close', this);
    },
    /**
     * Method: clickHandler
     * hide flyout if the user clicks outside of the flyout
     */
    clickHandler: function(e) {
        e = new Event(e);
        var elm = document.id(e.target),
            flyout = Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1];
        if (!elm.descendantOf(flyout.content) &&
            !elm.descendantOf(flyout.domObj)) {
            flyout.hide();
        }
    },
    /**
     * Method: keypressHandler
     * hide flyout if the user presses the ESC key
     */
    keypressHandler: function(e) {
        e = new Event(e);
        if (e.key == 'esc') {
            Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1].hide();
        }
    }
});/*
---

name: Jx.ColorPalette

description: A Jx.ColorPalette presents a user interface for selecting colors.  This is typically combined with a Jx.Button.Color which embeds the color palette in a flyout.

license: MIT-style license.

requires:
 - Jx.Widget

provides: [Jx.ColorPalette]

css:
 - color

images:
 - grid.png

...
 */
// $Id: colorpalette.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.ColorPalette
 *
 * Extends: <Jx.Widget>
 *
 * A Jx.ColorPalette presents a user interface for selecting colors.
 * Currently, the user can either enter a HEX colour value or select from a
 * palette of web-safe colours.  The user can also enter an opacity value.
 *
 * A Jx.ColorPalette can be embedded anywhere in a web page using its addTo
 * method.  However, a <Jx.Button> suJx.Tooltipbclass is provided
 * (<Jx.Button.Color>) that embeds a colour panel inside a button for easy use
 * in toolbars.
 *
 * Colour changes are propogated via a change event.  To be notified
 * of changes in a Jx.ColorPalette, use the addEvent method.
 *
 * Example:
 * (code)
 * (end)
 *
 * Events:
 * change - triggered when the color changes.
 * click - the user clicked on a color swatch (emitted after a change event)
 *
 * MooTools.lang keys:
 * - colorpalette.alphaLabel
 *
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.ColorPalette = new Class({
    Family: 'Jx.ColorPalette',
    Extends: Jx.Widget,
    /**
     * Property: {HTMLElement} domObj
     * the HTML element representing the color panel
     */
    domObj: null,
    options: {
        /* Option: parent
         * default null, the DOM element to add the palette to.
         */
        parent: null,
        /* Option: color
         * default #000000, the initially selected color
         */
        color: '#000000',
        /* Option: alpha
         * default 100, the initial alpha value
         */
        alpha: 1,
        /* Option: hexColors
         * an array of hex colors for creating the palette, defaults to a
         * set of web safe colors.
         */
        hexColors: ['00', '33', '66', '99', 'CC', 'FF']
    },
    /**
     * Method: render
     * initialize a new instance of Jx.ColorPalette
     */
    render: function() {
        this.domObj = new Element('div', {
            id: this.options.id,
            'class':'jxColorPalette'
        });

        var top = new Element('div', {'class':'jxColorBar'});
        var d = new Element('div', {'class':'jxColorPreview'});

        this.selectedSwatch = new Element('div', {'class':'jxColorSelected'});
        this.previewSwatch = new Element('div', {'class':'jxColorHover'});
        d.adopt(this.selectedSwatch);
        d.adopt(this.previewSwatch);

        top.adopt(d);

        this.colorInputLabel = new Element('label', {
          'class':'jxColorLabel',
          html:'#'
        });
        top.adopt(this.colorInputLabel);

        var cc = this.changed.bind(this);
        this.colorInput = new Element('input', {
            'class':'jxHexInput',
            'type':'text',
            'maxLength':6,
            events: {
                'keyup':cc,
                'blur':cc,
                'change':cc
            }
        });

        top.adopt(this.colorInput);

        this.alphaLabel = new Element('label', {'class':'jxAlphaLabel', 'html':this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}) });
        top.adopt(this.alphaLabel);

        this.alphaInput = new Element('input', {
            'class':'jxAlphaInput',
            'type':'text',
            'maxLength':3,
            events: {
                'keyup': this.alphaChanged.bind(this)
            }
        });
        top.adopt(this.alphaInput);

        this.domObj.adopt(top);

        var swatchClick = this.swatchClick.bindWithEvent(this);
        var swatchOver = this.swatchOver.bindWithEvent(this);

        var table = new Element('table', {'class':'jxColorGrid'});
        var tbody = new Element('tbody');
        table.adopt(tbody);
        for (var i=0; i<12; i++) {
            var tr = new Element('tr');
            for (var j=-3; j<18; j++) {
                var bSkip = false;
                var r, g, b;
                /* hacky approach to building first three columns
                 * because I couldn't find a good way to do it
                 * programmatically
                 */

                if (j < 0) {
                    if (j == -3 || j == -1) {
                        r = g = b = 0;
                        bSkip = true;
                    } else {
                        if (i<6) {
                            r = g = b = i;
                        } else {
                            if (i == 6) {
                                r = 5; g = 0; b = 0;
                            } else if (i == 7) {
                                r = 0; g = 5; b = 0;
                            } else if (i == 8) {
                                r = 0; g = 0; b = 5;
                            } else if (i == 9) {
                                r = 5; g = 5; b = 0;
                            } else if (i == 10) {
                                r = 0; g = 5; b = 5;
                            } else if (i == 11) {
                                r = 5; g = 0; b = 5;
                            }
                        }
                    }
                } else {
                    /* remainder of the columns are built
                     * based on the current row/column
                     */
                    r = parseInt(i/6,10)*3 + parseInt(j/6,10);
                    g = j%6;
                    b = i%6;
                }
                var bgColor = '#'+this.options.hexColors[r]+
                                  this.options.hexColors[g]+
                                  this.options.hexColors[b];

                var td = new Element('td');
                if (!bSkip) {
                    td.setStyle('backgroundColor', bgColor);

                    var a = new Element('a', {
                        'class': 'colorSwatch ' + (((r > 2 && g > 2) || (r > 2 && b > 2) || (g > 2 && b > 2)) ? 'borderBlack': 'borderWhite'),
                        'href':'javascript:void(0)',
                        'title':bgColor,
                        'alt':bgColor,
                        events: {
                            'mouseover': swatchOver,
                            'click': swatchClick
                        }
                    });
                    a.store('swatchColor', bgColor);
                    td.adopt(a);
                } else {
                    var span = new Element('span', {'class':'emptyCell'});
                    td.adopt(span);
                }
                tr.adopt(td);
            }
            tbody.adopt(tr);
        }
        this.domObj.adopt(table);
        this.updateSelected();
        if (this.options.parent) {
            this.addTo(this.options.parent);
        }
    },

    /**
     * Method: swatchOver
     * handle the mouse moving over a colour swatch by updating the preview
     *
     * Parameters:
     * e - {Event} the mousemove event object
     */
    swatchOver: function(e) {
        var a = e.target;

        this.previewSwatch.setStyle('backgroundColor', a.retrieve('swatchColor'));
    },

    /**
     * Method: swatchClick
     * handle mouse click on a swatch by updating the color and hiding the
     * panel.
     *
     * Parameters:
     * e - {Event} the mouseclick event object
     */
    swatchClick: function(e) {
        var a = e.target;

        this.options.color = a.retrieve('swatchColor');
        this.updateSelected();
        this.fireEvent('click', this);
    },

    /**
     * Method: changed
     * handle the user entering a new colour value manually by updating the
     * selected colour if the entered value is valid HEX.
     */
    changed: function() {
        var color = this.colorInput.value;
        if (color.substring(0,1) == '#') {
            color = color.substring(1);
        }
        if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
            this.options.color = '#' +color.toUpperCase();
            this.updateSelected();
        }
    },

    /**
     * Method: alphaChanged
     * handle the user entering a new alpha value manually by updating the
     * selected alpha if the entered value is valid alpha (0-100).
     */
    alphaChanged: function() {
        var alpha = this.alphaInput.value;
        if (alpha.match(/^[0-9]{1,3}$/)) {
            this.options.alpha = parseFloat(alpha/100);
            this.updateSelected();
        }
    },

    /**
     * APIMethod: setColor
     * set the colour represented by this colour panel
     *
     * Parameters:
     * color - {String} the new hex color value
     */
    setColor: function( color ) {
        this.colorInput.value = color;
        this.changed();
    },

    /**
     * APIMethod: setAlpha
     * set the alpha represented by this colour panel
     *
     * Parameters:
     * alpha - {Integer} the new alpha value (between 0 and 100)
     */
    setAlpha: function( alpha ) {
        this.alphaInput.value = alpha;
        this.alphaChanged();
    },

    /**
     * Method: updateSelected
     * update the colour panel user interface based on the current
     * colour and alpha values
     */
    updateSelected: function() {
        var styles = {'backgroundColor':this.options.color};

        this.colorInput.value = this.options.color.substring(1);

        this.alphaInput.value = parseInt(this.options.alpha*100,10);
        if (this.options.alpha < 1) {
            styles.opacity = this.options.alpha;
            styles.filter = 'Alpha(opacity='+(this.options.alpha*100)+')';

        } else {
            styles.opacity = 1;
            //not sure what the proper way to remove the filter would be since
            // I don't have IE to test against.
            styles.filter = '';
        }
        this.selectedSwatch.setStyles(styles);
        this.previewSwatch.setStyles(styles);

        this.fireEvent('change', this);
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the
     * widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     *    translations changed.
     */
    changeText: function (lang) {
    	this.parent();

    	if ($defined(this.alphaLabel)) {
    		this.alphaLabel.set('html', this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}));
    	}
    }
});

/*
---

name: Jx.Button.Color

description:

license: MIT-style license.

requires:
 - Jx.Button.Flyout
 - Jx.ColorPalette

provides: [Jx.Button.Color]

...
 */
// $Id: color.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Button.Color
 *
 * Extends: <Jx.Button.Flyout>
 *
 * A <Jx.ColorPalette> wrapped up in a Jx.Button.  The button includes a
 * preview of the currently selected color.  Clicking the button opens
 * the color panel.
 *
 * A color button is essentially a <Jx.Button.Flyout> where the content
 * of the flyout is a <Jx.ColorPalette>.  For performance, all color buttons
 * share an instance of <Jx.ColorPalette> which means only one button can be
 * open at a time.  This isn't a huge restriction as flyouts already close
 * each other when opened.
 *
 * Example:
 * (code)
 * var colorButton = new Jx.Button.Color({
 *     onChange: function(button) {
 *         console.log('color:' + button.options.color + ' alpha: ' +
 *                     button.options.alpha);
 *     }
 * });
 * (end)
 *
 * Events:
 * change - fired when the color is changed.
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Button.Color = new Class({
    Family: 'Jx.Button.Color',
    Extends: Jx.Button.Flyout,

    /**
     * Property: swatch
     * the color swatch element used to portray the currently selected
     * color
     */
    swatch: null,

    options: {
        /**
         * Option: color
         * a color to initialize the panel with, defaults to #000000
         * (black) if not specified.
         */
        color: '#000000',
        /**
         * Option: alpha
         * an alpha value to initialize the panel with, defaults to 1
         *  (opaque) if not specified.
         *
         */
        alpha: 100,
        /*
         * Option: template
         * the HTML template for the color button
         */
        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><span class="jxButtonSwatch"><span class="jxButtonSwatchColor"></span></span><span class="jxButtonLabel"></span></span></a></span>'
    },

    /**
     * Property: classes
     * {<Hash>} a hash of object properties to CSS class names used to
     * automatically extract references to important DOM elements when
     * processing a widget template.  This allows developers to provide custom
     * HTML structures without affecting the functionality of widgets.
     */
    classes: new Hash({
        domObj: 'jxButtonContainer',
        domA: 'jxButton',
        swatch: 'jxButtonSwatchColor',
        domLabel: 'jxButtonLabel'
    }),

    /**
     * Method: render
     * creates a new color button.
     */
    render: function() {
        if (!Jx.Button.Color.ColorPalette) {
            Jx.Button.Color.ColorPalette = new Jx.ColorPalette(this.options);
        }

        /* we need to have an image to replace, but if a label is
           requested, there wouldn't normally be an image. */
        this.options.image = Jx.aPixel.src;

        /* now we can safely initialize */
        this.parent();
        this.updateSwatch();

        this.bound.changed = this.changed.bind(this);
        this.bound.hide = this.hide.bind(this);
    },
    cleanup: function() {
      this.bound.changed = false;
      this.bound.hide = false;
      this.parent();
    },
    /**
     * APIMethod: clicked
     * override <Jx.Button.Flyout> to use a singleton color palette.
     */
    clicked: function() {
        var cp = Jx.Button.Color.ColorPalette;
        if (cp.currentButton) {
            cp.currentButton.hide();
        }
        cp.currentButton = this;
        cp.addEvent('change', this.bound.changed);
        cp.addEvent('click', this.bound.hide);
        this.content.appendChild(cp.domObj);
        cp.domObj.setStyle('display', 'block');
        Jx.Button.Flyout.prototype.clicked.apply(this, arguments);
        /* setting these before causes an update problem when clicking on
         * a second color button when another one is open - the color
         * wasn't updating properly
         */

        cp.options.color = this.options.color;
        cp.options.alpha = this.options.alpha/100;
        cp.updateSelected();
},

    /**
     * APIMethod: hide
     * hide the color panel
     */
    hide: function() {
        var cp = Jx.Button.Color.ColorPalette;
        this.setActive(false);
        cp.removeEvent('change', this.bound.changed);
        cp.removeEvent('click', this.bound.hide);
        Jx.Button.Flyout.prototype.hide.apply(this, arguments);
        cp.currentButton = null;
    },

    /**
     * APIMethod: setColor
     * set the color represented by this color panel
     *
     * Parameters:
     * color - {String} the new hex color value
     */
    setColor: function(color) {
        this.options.color = color;
        this.updateSwatch();
    },

    /**
     * APIMethod: setAlpha
     * set the alpha represented by this color panel
     *
     * Parameters:
     * alpha - {Integer} the new alpha value (between 0 and 100)
     */
    setAlpha: function(alpha) {
        this.options.alpha = alpha;
        this.updateSwatch();
    },

    /**
     * Method: changed
     * handle the color changing in the palette by updating the preview swatch
     * in the button and firing the change event.
     *
     * Parameters:
     * panel - <Jx.ColorPalette> the palette that changed.
     */
    changed: function(panel) {
        var changed = false;
        if (this.options.color != panel.options.color) {
            this.options.color = panel.options.color;
            changed = true;
        }
        if (this.options.alpha != panel.options.alpha * 100) {
            this.options.alpha = panel.options.alpha * 100;
            changed = true;
        }
        if (changed) {
            this.updateSwatch();
            this.fireEvent('change',this);
        }
    },

    /**
     * Method: updateSwatch
     * Update the swatch color for the current color
     */
    updateSwatch: function() {
        var styles = {'backgroundColor':this.options.color};
        if (this.options.alpha < 100) {
            styles.filter = 'Alpha(opacity='+(this.options.alpha)+')';
            styles.opacity = this.options.alpha / 100;

        } else {
            styles.opacity = 1;
            styles.filter = '';
        }
        this.swatch.setStyles(styles);
    }
});
/*
---

name: Jx.Menu

description: A main menu as opposed to a sub menu that lives inside the menu.

license: MIT-style license.

requires:
 - Jx.Button
 - Jx.List

provides: [Jx.Menu]

css:
 - menu

images:
 - flyout_chrome.png
 - emblems.png
...
 */
// $Id: menu.js 1012 2011-03-03 20:37:26Z pagameba $
/**
 * Class: Jx.Menu
 *
 * Extends: <Jx.Widget>
 *
 * A main menu as opposed to a sub menu that lives inside the menu.
 *
 * TODO: Jx.Menu
 * revisit this to see if Jx.Menu and Jx.SubMenu can be merged into
 * a single implementation.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Menu = new Class({
    Family: 'Jx.Menu',
    Extends: Jx.Widget,
    // Binds: ['onMouseEnter','onMouseLeave','hide','keypressHandler'],
    /**
     * Property: button
     * {<Jx.Button>} The button that represents this menu in a toolbar and
     * opens the menu.
     */
    button : null,
    /**
     * Property: subDomObj
     * {HTMLElement} the HTML element that contains the menu items
     * within the menu.
     */
    subDomObj : null,
    /**
     * Property: list
     * {<Jx.List>} the list of items in the menu
     */
    list: null,

    parameters: ['buttonOptions', 'options'],

    options: {
        /**
         * Option: exposeOnHover
         * {Boolean} default false, if set to true the menu will show
         * when the mouse hovers over it rather than when it is clicked.
         */
        exposeOnHover: false,
        /**
         * Option: hideDelay
         * {Integer} default 0, if greater than 0, this is the number of
         * milliseconds to delay before hiding a menu when the mouse leaves
         * the menu button or list.
         */
        hideDelay: 0,
        template: "<div class='jxMenuContainer'><ul class='jxMenu'></ul></div>",
        buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>',
        position: {
            horizontal: ['left left'],
            vertical: ['bottom top', 'top bottom']
        }
    },

    classes: new Hash({
        contentContainer: 'jxMenuContainer',
        subDomObj: 'jxMenu'
    }),

    init: function() {
        this.bound.stop = function(e){e.stop();};
        this.bound.remove = function(item) {item.setOwner(null);};
        this.bound.show = this.show.bind(this);
        this.bound.mouseenter = this.onMouseEnter.bind(this);
        this.bound.mouseleave = this.onMouseLeave.bind(this);
        this.bound.keypress = this.keypressHandler.bind(this);
        this.bound.hide = this.hide.bind(this);
        this.parent();
    },

    /**
     * APIMethod: render
     * Create a new instance of Jx.Menu.
     */
    render : function() {
        this.parent();
        if (!Jx.Menu.Menus) {
            Jx.Menu.Menus = [];
        }

        this.contentClone = this.contentContainer.clone();
        this.list = new Jx.List(this.subDomObj, {
            onRemove: this.bound.remove
        });

        /* if options are passed, make a button inside an LI so the
           menu can be embedded inside a toolbar */
        if (this.options.buttonOptions) {
            this.button = new Jx.Button($merge(this.options.buttonOptions,{
                template: this.options.buttonTemplate,
                onClick:this.bound.show
            }));

            this.button.domA.addEvent('mouseenter', this.bound.mouseenter);
            this.button.domA.addEvent('mouseleave', this.bound.mouseleave);

            this.domObj = this.button.domObj;
            this.domObj.store('jxMenu', this);
        }

        this.subDomObj.addEvent('mouseenter', this.bound.mouseenter);
        this.subDomObj.addEvent('mouseleave', this.bound.mouseleave);
        this.subDomObj.store('jxSubMenu', this);

        if (this.options.parent) {
            this.addTo(this.options.parent);
        }
    },
    cleanup: function() {
      if (this.hideTimer) {
        window.clearTimeout(this.hideTimer);
      }
      this.list.removeEvent('remove', this.bound.remove);
      this.list.destroy();
      this.list = null;
      if (this.button) {
        this.domObj.eliminate('jxMenu');
        this.domObj = null;
        this.button.removeEvent('click', this.bound.show);
        this.button.domA.removeEvents({
          mouseenter: this.bound.mouseenter,
          mouseleave: this.bound.mouseleave
        });

        this.button.destroy();
        this.button = null;
      }
      this.subDomObj.removeEvents({
        mouseenter: this.bound.mouseenter,
        mouseleave: this.bound.mouseleave
      });
      this.subDomObj.removeEvents();
      this.contentContainer.removeEvent('contextmenu', this.bound.stop);
      this.subDomObj.destroy();
      this.contentContainer.destroy();
      this.contentClone.destroy();
      this.bound.remove = null;
      this.bound.show = null;
      this.bound.stop = null;
      this.bound.mouseenter = null;
      this.bound.mouseleave = null;
      this.bound.keypress = null;
      this.bound.hide = null;
      this.parent();
    },
    /**
     * APIMethod: add
     * Add menu items to the sub menu.
     *
     * Parameters:
     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
     *     can be added by passing an array of menu items.
     * position - the index to add the item at, defaults to the end of the
     *     menu
     */
    add: function(item, position, owner) {
        if (Jx.type(item) == 'array') {
            item.each(function(i){
                i.setOwner(owner||this);
            }, this);
        } else {
            item.setOwner(owner||this);
        }
        this.list.add(item, position);
        return this;
    },
    /**
     * APIMethod: remove
     * Remove a menu item from the menu
     *
     * Parameters:
     * item - {<Jx.MenuItem>} the menu item to remove
     */
    remove: function(item) {
        this.list.remove(item);
        return this;
    },
    /**
     * APIMethod: replace
     * Replace a menu item with another menu item
     *
     * Parameters:
     * what - {<Jx.MenuItem>} the menu item to replace
     * withWhat - {<Jx.MenuItem>} the menu item to replace it with
     */
    replace: function(item, withItem) {
        this.list.replace(item, withItem);
        return this;
    },
    /**
     * APIMethod: empty
     * Empty the menu of items
     */
    empty: function() {
      this.list.each(function(item){
        if (item.empty) {
          item.empty();
        }
        item.setOwner(null);
      }, this);
      this.list.empty();
    },
    /**
     * Method: deactivate
     * Deactivate the menu by hiding it.
     */
    deactivate: function() {this.hide();},
    /**
     * Method: onMouseOver
     * Handle the user moving the mouse over the button for this menu
     * by showing this menu and hiding the other menu.
     *
     * Parameters:
     * e - {Event} the mouse event
     */
    onMouseEnter: function(e) {
      if (this.hideTimer) {
        window.clearTimeout(this.hideTimer);
        this.hideTimer = null;
      }
      if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] != this) {
          this.show.delay(1,this);
      } else if (this.options.exposeOnHover) {
        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
          Jx.Menu.Menus[0] = null;
        }
        this.show.delay(1,this);
      }
    },
    /**
     * Method: onMouseLeave
     * Handle the user moving the mouse off this button or menu by
     * starting the hide process if so configured.
     *
     * Parameters:
     * e - {Event} the mouse event
     */
    onMouseLeave: function(e) {
      if (this.options.hideDelay > 0) {
        this.hideTimer = (function(){
          this.deactivate();
        }).delay(this.options.hideDelay, this);
      }
    },

    /**
     * Method: eventInMenu
     * determine if an event happened inside this menu or a sub menu
     * of this menu.
     *
     * Parameters:
     * e - {Event} the mouse event
     *
     * Returns:
     * {Boolean} true if the event happened in the menu or
     * a sub menu of this menu, false otherwise
     */
    eventInMenu: function(e) {
        var target = document.id(e.target);
        if (!target) {
            return false;
        }
        if (target.descendantOf(this.domObj) ||
            target.descendantOf(this.subDomObj)) {
            return true;
        } else {
            var ul = target.getParent('ul');
            if (ul) {
                var sm = ul.retrieve('jxSubMenu');
                if (sm) {
                    if (sm.eventInMenu(e)) {
                      return true;
                    }
                    var owner = sm.owner;
                    while (owner) {
                        if (owner == this) {
                            return true;
                        }
                        owner = owner.owner;
                    }
                }
            }
            return false;
        }
    },

    /**
     * APIMethod: hide
     * Hide the menu.
     *
     * Parameters:
     * e - {Event} the mouse event
     */
    hide: function(e) {
        if (e) {
            if (this.visibleItem && this.visibleItem.eventInMenu) {
                if (this.visibleItem.eventInMenu(e)) {
                    return;
                }
            } else if (this.eventInMenu(e)) {
                return;
            }
        }
        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
            Jx.Menu.Menus[0] = null;
        }
        if (this.button && this.button.domA) {
            this.button.domA.removeClass(this.button.options.activeClass);
        }
        if (this.hideTimer) {
          window.clearTimeout(this.hideTimer);
        }
        this.list.each(function(item){item.retrieve('jxMenuItem').hide(e);});
        document.removeEvent('mousedown', this.bound.hide);
        document.removeEvent('keydown', this.bound.keypress);
        this.unstack(this.contentContainer);
        this.contentContainer.dispose();
        this.visibleItem = null;
        this.fireEvent('hide', this);
    },
    /**
     * APIMethod: show
     * Show the menu
     */
    show : function() {
        if (this.button) {
            if (Jx.Menu.Menus[0]) {
                if (Jx.Menu.Menus[0] != this) {
                    Jx.Menu.Menus[0].button.blur();
                    Jx.Menu.Menus[0].hide();
                } else {
                    this.hide();
                    return;
                }
            }
            Jx.Menu.Menus[0] = this;
            this.button.focus();
            if (this.list.count() == 0) {
                return;
            }
        }
        if (this.hideTimer) {
          window.clearTimeout(this.hideTimer);
        }

        this.subDomObj.dispose();
        this.contentContainer.destroy();
        this.contentContainer = this.contentClone.clone();
        this.contentContainer.empty().adopt(this.subDomObj);
        this.contentContainer.addEvent('contextmenu', this.bound.stop);
        this.contentContainer.setStyle('display','none');
        document.id(document.body).adopt(this.contentContainer);
        this.contentContainer.setStyles({
            visibility: 'hidden',
            display: 'block'
        });
        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
        this.showChrome(this.contentContainer);

        this.position(this.contentContainer, this.domObj, $merge({
            offsets: this.chromeOffsets
        }, this.options.position));
        this.stack(this.contentContainer);
        this.contentContainer.setStyle('visibility','visible');

        if (this.button && this.button.domA) {
            this.button.domA.addClass(this.button.options.activeClass);
        }

        /* fix bug in IE that closes the menu as it opens
         * because of bubbling (I think)
         */
        document.addEvent('mousedown', this.bound.hide);
        document.addEvent('keydown', this.bound.keypress);
        this.fireEvent('show', this);
    },
    /**
     * APIMethod: setVisibleItem
     * Set the sub menu that is currently open
     *
     * Parameters:
     * obj- {<Jx.SubMenu>} the sub menu that just became visible
     */
    setVisibleItem: function(obj) {
        if (this.hideTimer) {
          window.clearTimeout(this.hideTimer);
        }
        if (this.visibleItem != obj) {
            if (this.visibleItem && this.visibleItem.hide) {
                this.visibleItem.hide();
            }
            this.visibleItem = obj;
            this.visibleItem.show();
        }
    },

    /* hide flyout if the user presses the ESC key */
    keypressHandler: function(e) {
        e = new Event(e);
        if (e.key == 'esc') {
            this.hide();
        }
    },
    /**
     * APIMethod: isEnabled
     * This returns true if the menu is enabled, false otherwise
     *
     * Returns:
     * {Boolean} whether the menu is enabled or not
     */
    isEnabled: function() {
        return this.button ? this.button.isEnabled() : this.options.enabled ;
    },

    /**
     * APIMethod: setEnabled
     * enable or disable the menu.
     *
     * Parameters:
     * enabled - {Boolean} the new enabled state of the menu
     */
    setEnabled: function(enabled) {
        return this.button ? this.button.setEnabled(enabled) : this.options.enable;
    },
    /**
     * APIMethod: isActive
     * returns true if the menu is open.
     *
     * Returns:
     * {Boolean} the active state of the menu
     */
    isActive: function() {
        return this.button ? this.button.isActive() : this.options.active;
    },
    /**
     * APIMethod: setActive
     * Set the active state of the menu
     *
     * Parameters:
     * active - {Boolean} the new active state of the menu
     */
    setActive: function(active) {
        if (this.button) {
          this.button.setActive(active);
        }
    },
    /**
     * APIMethod: setImage
     * set the image of this menu to a new image URL
     *
     * Parameters:
     * path - {String} the new url to use as the image for this menu
     */
    setImage: function(path) {
        if (this.button) {
          this.button.setImage(path);
        }
    },
    /**
     * APIMethod: setLabel
     *
     * sets the text of the menu.
     *
     * Parameters:
     *
     * label - {String} the new label for the menu
     */
    setLabel: function(label) {
        if (this.button) {
          this.button.setLabel(label);
        }
    },
    /**
     * APIMethod: getLabel
     *
     * returns the text of the menu.
     */
    getLabel: function() {
        return this.button ? this.button.getLabel() : '';
    },
    /**
     * APIMethod: setTooltip
     * sets the tooltip displayed by the menu
     *
     * Parameters:
     * tooltip - {String} the new tooltip
     */
    setTooltip: function(tooltip) {
        if (this.button) {
          this.button.setTooltip(tooltip);
        }
    },
    /**
     * APIMethod: focus
     * capture the keyboard focus on this menu
     */
    focus: function() {
        if (this.button) {
          this.button.focus();
        }
    },
    /**
     * APIMethod: blur
     * remove the keyboard focus from this menu
     */
    blur: function() {
        if (this.button) {
          this.button.blur();
        }
    }
});

/*
---

name: Jx.Menu.Item

description: A menu item is a single entry in a menu.

license: MIT-style license.

requires:
 - Jx.Menu

provides: [Jx.Menu.Item]

images:
 - menuitem.png
...
 */
// $Id: menu.item.js 967 2010-07-20 13:31:44Z pagameba $
/**
 * Class: Jx.Menu.Item
 *
 * Extends: <Jx.Button>
 *
 * A menu item is a single entry in a menu.  It is typically composed of
 * a label and an optional icon.  Selecting the menu item emits an event.
 *
 * Jx.Menu.Item is represented by a <Jx.Button> with type MenuItem and the
 * associated CSS changes noted in <Jx.Button>.  The container of a MenuItem
 * is an 'li' element.
 *
 * Example:
 * (code)
 * (end)
 *
 * Events:
 * click - fired when the menu item is clicked.
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Menu.Item = new Class({
    Family: 'Jx.Menu.Item',
    Extends: Jx.Button,
    // Binds: ['onMouseOver'],
    /**
     * Property: owner
     * {<Jx.SubMenu> or <Jx.Menu>} the menu that contains the menu item.
     */
    owner: null,
    options: {
        //image: null,
        label: '&nbsp;',
        toggleClass: 'jxMenuItemToggle',
        pressedClass: 'jxMenuItemPressed',
        activeClass: 'jxMenuItemActive',
        /* Option: template
         * the HTML structure of the button.  As a minimum, there must be a
         * containing element with a class of jxMenuItemContainer and an
         * internal element with a class of jxMenuItem.  jxMenuItemIcon and
         * jxMenuItemLabel are used if present to put the image and label into
         * the button.
         */
        template: '<li class="jxMenuItemContainer"><a class="jxMenuItem"><span class="jxMenuItemContent"><img class="jxMenuItemIcon" src="'+Jx.aPixel.src+'"><span class="jxMenuItemLabel"></span></span></a></li>'
    },
    classes: new Hash({
        domObj:'jxMenuItemContainer',
        domA: 'jxMenuItem',
        domImg: 'jxMenuItemIcon',
        domLabel: 'jxMenuItemLabel'
    }),
    init: function() {
      this.bound.mouseover = this.onMouseOver.bind(this);
      this.parent();
    },
    /**
     * APIMethod: render
     * Create a new instance of Jx.Menu.Item
     */
    render: function() {
        if (!this.options.image) {
            this.options.image = Jx.aPixel.src;
        }
        this.parent();
        if (this.options.image && this.options.image != Jx.aPixel.src) {
            this.domObj.removeClass(this.options.toggleClass);
        }
        if (this.options.target) {
          this.domA.set('target', this.options.target);
        }
        this.domObj.addEvent('mouseover', this.bound.mouseover);
        this.domObj.store('jxMenuItem', this);
    },
    cleanup: function() {
      this.domObj.eliminate('jxMenuItem');
      this.domObj.removeEvent('mouseover', this.bound.mouseover);
      this.bound.mouseover = null;
      this.owner = null;
      this.parent();
    },
    /**
     * Method: setOwner
     * Set the owner of this menu item
     *
     * Parameters:
     * obj - {Object} the new owner
     */
    setOwner: function(obj) {
        this.owner = obj;
    },
    /**
     * Method: hide
     * Hide the menu item.
     */
    hide: function() {this.blur.delay(1,this);},
    /**
     * Method: show
     * Show the menu item
     */
    show: $empty,
    /**
     * Method: clicked
     * Handle the user clicking on the menu item, overriding the <Jx.Button::clicked>
     * method to facilitate menu tracking
     *
     * Parameters:
     * obj - {Object} an object containing an event property that was the user
     * event.
     */
    clicked: function(obj) {
        var href = this.options.href && this.options.href.indexOf('javascript:') != 0;
        if (this.options.enabled) {
          if (!href) {
            if (this.options.toggle) {
                this.setActive.delay(1,this,!this.options.active);
            }
            this.fireEvent.delay(1, this, ['click', {obj: this}]);
            this.blur();
          }
          if (this.owner && this.owner.deactivate) {
              this.owner.deactivate.delay(1, this.owner, obj.event);
          }
        }
        return href ? true : false;
    },
    /**
     * Method: onmouseover
     * handle the mouse moving over the menu item
     */
    onMouseOver: function(e) {
        e.stop();
        if (this.owner && this.owner.setVisibleItem) {
            this.owner.setVisibleItem(this);
        }
        return false;
    },

    /**
     * APIMethod: changeText
     *
     * updates the label of the menu item on langChange Event for
     * Internationalization
     */
    changeText: function(lang) {
        this.parent();
        if (this.owner && this.owner.deactivate) {
            this.owner.deactivate();
        }
    }
});

/*
---

name: Jx.ButtonSet

description: A ButtonSet manages a set of Jx.Button instances by ensuring that only one of the buttons is active.

license: MIT-style license.

requires:
 - Jx.Object

provides: [Jx.ButtonSet]


...
 */
// $Id: set.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.ButtonSet
 *
 * Extends: <Jx.Object>
 *
 * A ButtonSet manages a set of <Jx.Button> instances by ensuring that only
 * one of the buttons is active.  All the buttons need to have been created
 * with the toggle option set to true for this to work.
 *
 * Example:
 * (code)
 * var toolbar = new Jx.Toolbar('bar');
 * var buttonSet = new Jx.ButtonSet();
 *
 * var b1 = new Jx.Button({label: 'b1', toggle:true, contentID: 'content1'});
 * var b2 = new Jx.Button({label: 'b2', toggle:true, contentID: 'content2'});
 * var b3 = new Jx.Button({label: 'b3', toggle:true, contentID: 'content3'});
 * var b4 = new Jx.Button({label: 'b4', toggle:true, contentID: 'content4'});
 *
 * buttonSet.add(b1,b2,b3,b4);
 * (end)
 *
 * Events:
 * change - the current button has changed
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.ButtonSet = new Class({
    Family: 'Jx.ButtonSet',
    Extends: Jx.Object,
    Binds: ['buttonChanged'],
    /**
     * Property: buttons
     * {Array} array of buttons that are managed by this button set
     */
    buttons: [],

    cleanup: function() {
      this.buttons.each(function(b){
        b.removeEvent('down', this.buttonChanged);
        b.setActive = null;
      },this);
      this.activeButton = null;
      this.buttons = null;
      this.parent();
    },

    /**
     * APIMethod: add
     * Add one or more <Jx.Button>s to the ButtonSet.
     *
     * Parameters:
     * button - {<Jx.Button>} an instance of <Jx.Button> to add to the button
     * set.  More than one button can be added by passing extra parameters to
     * this method.
     */
    add : function() {
        $A(arguments).each(function(button) {
            if (button.domObj.hasClass(button.options.toggleClass)) {
                button.domObj.removeClass(button.options.toggleClass);
                button.domObj.addClass(button.options.toggleClass+'Set');
            }
            button.addEvent('down',this.buttonChanged);
            button.setActive = function(active) {
                if (button.options.active && this.activeButton == button) {
                    return;
                } else {
                    Jx.Button.prototype.setActive.apply(button, [active]);
                }
            }.bind(this);
            if (!this.activeButton || button.options.active) {
                button.options.active = false;
                button.setActive(true);
            }
            this.buttons.push(button);
        }, this);
        return this;
    },
    /**
     * APIMethod: remove
     * Remove a button from this Button.
     *
     * Parameters:
     * button - {<Jx.Button>} the button to remove.
     */
    remove : function(button) {
        this.buttons.erase(button);
        if (this.activeButton == button) {
            if (this.buttons.length) {
                this.buttons[0].setActive(true);
            }
            button.removeEvent('down',this.buttonChanged);
            button.setActive = Jx.Button.prototype.setActive;
        }
    },
    /**
     * APIMethod: empty
     * empty the button set and clear the active button
     */
    empty: function() {
      this.buttons = [];
      this.activeButton = null;
    },
    /**
     * APIMethod: setActiveButton
     * Set the active button to the one passed to this method
     *
     * Parameters:
     * button - {<Jx.Button>} the button to make active.
     */
    setActiveButton: function(button) {
        var b = this.activeButton;
        this.activeButton = button;
        if (b && b != button) {
            b.setActive(false);
        }
    },
    /**
     * Method: buttonChanged
     * Handle selection changing on the buttons themselves and activate the
     * appropriate button in response.
     *
     * Parameters:
     * button - {<Jx.Button>} the button to make active.
     */
    buttonChanged: function(button) {
        this.setActiveButton(button);
        this.fireEvent('change', this);
    }
});/*
---

name: Jx.Button.Multi

description: Multi buttons are used to contain multiple buttons in a drop down list where only one button is actually visible and clickable in the interface.

license: MIT-style license.

requires:
 - Jx.Button
 - Jx.Menu
 - Jx.ButtonSet

provides: [Jx.Button.Multi]

images:
 - button_multi.png
 - button_multi_disclose.png

...
 */
// $Id: multi.js 977 2010-09-02 18:57:42Z pagameba $
/**
 * Class: Jx.Button.Multi
 *
 * Extends: <Jx.Button>
 *
 * Implements:
 *
 * Multi buttons are used to contain multiple buttons in a drop down list
 * where only one button is actually visible and clickable in the interface.
 *
 * When the user clicks the active button, it performs its normal action.
 * The user may also click a drop-down arrow to the right of the button and
 * access the full list of buttons.  Clicking a button in the list causes
 * that button to replace the active button in the toolbar and performs
 * the button's regular action.
 *
 * Other buttons can be added to the Multi button using the add method.
 *
 * This is not really a button, but rather a container for buttons.  The
 * button structure is a div containing two buttons, a normal button and
 * a flyout button.  The flyout contains a toolbar into which all the
 * added buttons are placed.  The main button content is cloned from the
 * last button clicked (or first button added).
 *
 * The Multi button does not trigger any events itself, only the contained
 * buttons trigger events.
 *
 * Example:
 * (code)
 * var b1 = new Jx.Button({
 *     label: 'b1',
 *     onClick: function(button) {
 *         console.log('b1 clicked');
 *     }
 * });
 * var b2 = new Jx.Button({
 *     label: 'b2',
 *     onClick: function(button) {
 *         console.log('b2 clicked');
 *     }
 * });
 * var b3 = new Jx.Button({
 *     label: 'b3',
 *     onClick: function(button) {
 *         console.log('b3 clicked');
 *     }
 * });
 * var multiButton = new Jx.Button.Multi();
 * multiButton.add(b1, b2, b3);
 * (end)
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Button.Multi = new Class({
    Family: 'Jx.Button.Multi',
    Extends: Jx.Button,

    /**
     * Property: {<Jx.Button>} activeButton
     * the currently selected button
     */
    activeButton: null,

    /**
     * Property: buttons
     * {Array} the buttons added to this multi button
     */
    buttons: null,

    options: {
        /* Option: template
         * the button template for a multi button
         */
        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonMulti jxDiscloser"><span class="jxButtonContent"><img src="'+Jx.aPixel.src+'" class="jxButtonIcon"><span class="jxButtonLabel"></span></span></a><a class="jxButtonDisclose" href="javascript:void(0)"><img src="'+Jx.aPixel.src+'"></a></span>',
        menuOptions: {}
    },

    /**
     * Property: classes
     * {<Hash>} a hash of object properties to CSS class names used to
     * automatically extract references to important DOM elements when
     * processing a widget template.  This allows developers to provide custom
     * HTML structures without affecting the functionality of widgets.
     */
    classes: new Hash({
        domObj: 'jxButtonContainer',
        domA: 'jxButton',
        domImg: 'jxButtonIcon',
        domLabel: 'jxButtonLabel',
        domDisclose: 'jxButtonDisclose'
    }),

    /**
     * Method: render
     * construct a new instance of Jx.Button.Multi.
     */
    render: function() {
        this.parent();
        this.buttons = [];

        this.menu = new Jx.Menu({}, this.options.menuOptions);
        this.menu.button = this;
        this.buttonSet = new Jx.ButtonSet();

        this.bound.click = this.clicked.bind(this);

        if (this.domDisclose) {
            var button = this;
            var hasFocus;

            this.bound.disclose = {
              click: function(e) {
                  if (this.list.count() === 0) {
                      return;
                  }
                  if (!button.options.enabled) {
                      return;
                  }
                  this.contentContainer.setStyle('visibility','hidden');
                  this.contentContainer.setStyle('display','block');
                  document.id(document.body).adopt(this.contentContainer);
                  /* we have to size the container for IE to render the chrome
                   * correctly but just in the menu/sub menu case - there is
                   * some horrible peekaboo bug in IE related to ULs that we
                   * just couldn't figure out
                   */
                  this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());

                  this.showChrome(this.contentContainer);

                  this.position(this.contentContainer, this.button.domObj, {
                      horizontal: ['right right'],
                      vertical: ['bottom top', 'top bottom'],
                      offsets: this.chromeOffsets
                  });

                  this.contentContainer.setStyle('visibility','');

                  document.addEvent('mousedown', this.bound.hide);
                  document.addEvent('keyup', this.bound.keypress);

                  this.fireEvent('show', this);
              }.bindWithEvent(this.menu),
              mouseenter:function(){
                  document.id(this.domObj.firstChild).addClass('jxButtonHover');
                  if (hasFocus) {
                      this.domDisclose.addClass(this.options.pressedClass);
                  }
              }.bind(this),
              mouseleave:function(){
                  document.id(this.domObj.firstChild).removeClass('jxButtonHover');
                  this.domDisclose.removeClass(this.options.pressedClass);
              }.bind(this),
              mousedown: function(e) {
                  this.domDisclose.addClass(this.options.pressedClass);
                  hasFocus = true;
                  this.focus();
              }.bindWithEvent(this),
              mouseup: function(e) {
                  this.domDisclose.removeClass(this.options.pressedClass);
              }.bindWithEvent(this),
              keydown: function(e) {
                  if (e.key == 'enter') {
                      this.domDisclose.addClass(this.options.pressedClass);
                  }
              }.bindWithEvent(this),
              keyup: function(e) {
                  if (e.key == 'enter') {
                      this.domDisclose.removeClass(this.options.pressedClass);
                  }
              }.bindWithEvent(this),
              blur: function() { hasFocus = false; }
            };

            this.domDisclose.addEvents({
              click: this.bound.disclose.click,
              mouseenter: this.bound.disclose.mouseenter,
              mouseleave: this.bound.disclose.mouseleave,
              mousedown: this.bound.disclose.mousedown,
              mouseup: this.bound.disclose.mouseup,
              keydown: this.bound.disclose.keydown,
              keyup: this.bound.disclose.keyup,
              blur: this.bound.disclose.blur
            });
            if (typeof Drag != 'undefined') {
                new Drag(this.domDisclose, {
                    onStart: function() {this.stop();}
                });
            }
        }
        this.bound.show = function() {
            this.domA.addClass(this.options.activeClass);
        }.bind(this);
        this.bound.hide = function() {
            if (this.options.active) {
                this.domA.addClass(this.options.activeClass);
            }
        }.bind(this);

        this.menu.addEvents({
            'show': this.bound.show,
            'hide': this.bound.hide
        });
        if (this.options.items) {
            this.add(this.options.items);
        }
    },
    cleanup: function() {
      var self = this,
          bound = this.bound;
      // clean up the discloser
      if (self.domDisclose) {
        self.domDisclose.removeEvents({
          click: bound.disclose.click,
          mouseenter: bound.disclose.mouseenter,
          mouseleave: bound.disclose.mouseleave,
          mousedown: bound.disclose.mousedown,
          mouseup: bound.disclose.mouseup,
          keydown: bound.disclose.keydown,
          keyup: bound.disclose.keyup,
          blur: bound.disclose.blur
        });
      }

      // clean up the button set
      self.buttonSet.destroy();
      self.buttonSet = null;

      // clean up the buttons array
      self.buttons.each(function(b){
        b.removeEvents();
        self.menu.remove(b.multiButton);
        b.multiButton.destroy();
        b.multiButton = null;
        b.destroy();
      });
      self.buttons.empty();
      self.buttons = null;

      // clean up the menu object
      self.menu.removeEvents({
        'show': bound.show,
        'hide': bound.hide
      });
      // unset the menu button because it references this object
      self.menu.button = null;
      self.menu.destroy();
      self.menu = null;

      // clean up binds and call parent to finish
      self.bound.show = null;
      self.bound.hide = null;
      self.bound.clicked = null;
      self.bound.disclose = null;
      self.activeButton = null;
      self.parent();
    },
    /**
     * APIMethod: add
     * adds one or more buttons to the Multi button.  The first button
     * added becomes the active button initialize.  This function
     * takes a variable number of arguments, each of which is expected
     * to be an instance of <Jx.Button>.
     *
     * Parameters:
     * button - {<Jx.Button>} a <Jx.Button> instance, may be repeated in the parameter list
     */
    add: function() {
        $A(arguments).flatten().each(function(theButton){
          var f,
              opts,
              button;
            if (!theButton instanceof Jx.Button) {
                return;
            }
            theButton.domA.addClass('jxDiscloser');
            theButton.setLabel(theButton.options.label);
            this.buttons.push(theButton);
            f = this.setButton.bind(this, theButton);
            opts = {
                image: theButton.options.image,
                imageClass: theButton.options.imageClass,
                label: theButton.options.label || '&nbsp;',
                enabled: theButton.options.enabled,
                tooltip: theButton.options.tooltip,
                toggle: true,
                onClick: f
            };
            if (!opts.image || opts.image.indexOf('a_pixel') != -1) {
                delete opts.image;
            }
            button = new Jx.Menu.Item(opts);
            this.buttonSet.add(button);
            this.menu.add(button);
            theButton.multiButton = button;
            theButton.domA.addClass('jxButtonMulti');
            if (!this.activeButton) {
                this.domA.dispose();
                this.setActiveButton(theButton);
            }
        }, this);
    },
    /**
     * APIMethod: remove
     * remove a button from a multi button
     *
     * Parameters:
     * button - {<Jx.Button>} the button to remove
     */
    remove: function(button) {
        if (!button || !button.multiButton) {
            return;
        }
        // the toolbar will only remove the li.toolItem, which is
        // the parent node of the multiButton's domObj.
        if (this.menu.remove(button.multiButton)) {
            button.multiButton = null;
            if (this.activeButton == button) {
                // if any buttons are left that are not this button
                // then set the first one to be the active button
                // otherwise set the active button to nothing
                if (!this.buttons.some(function(b) {
                    if (b != button) {
                        this.setActiveButton(b);
                        return true;
                    } else {
                        return false;
                    }
                }, this)) {
                    this.setActiveButton(null);
                }
            }
            this.buttons.erase(button);
        }
    },
    /**
     * APIMethod: empty
     * remove all buttons from the multi button
     */
    empty: function() {
      this.buttons.each(function(b){this.remove(b);}, this);
    },
    /**
     * APIMethod: setActiveButton
     * update the menu item to be the requested button.
     *
     * Parameters:
     * button - {<Jx.Button>} a <Jx.Button> instance that was added to this multi button.
     */
    setActiveButton: function(button) {
        if (this.activeButton) {
            this.activeButton.domA.dispose();
            this.activeButton.domA.removeEvent('click', this.bound.click);
        }
        if (button && button.domA) {
            this.domObj.grab(button.domA, 'top');
            this.domA = button.domA;
            this.domA.addEvent('click', this.bound.click);
            if (this.options.toggle) {
                this.options.active = false;
                this.setActive(true);
            }
        }
        this.activeButton = button;
    },
    /**
     * Method: setButton
     * update the active button in the menu item, trigger the button's action
     * and hide the flyout that contains the buttons.
     *
     * Parameters:
     * button - {<Jx.Button>} The button to set as the active button
     */
    setButton: function(button) {
        this.setActiveButton(button);
        button.clicked();
    }
});/*
---

name: Jx.Layout

description: Jx.Layout is used to provide more flexible layout options for applications

license: MIT-style license.

requires:
 - Jx.Object


provides: [Jx.Layout]

css:
 - layout

...
 */
// $Id: layout.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Layout
 *
 * Extends: <Jx.Object>
 *
 * Jx.Layout is used to provide more flexible layout options for applications
 *
 * Jx.Layout wraps an existing DOM element (typically a div) and provides
 * extra functionality for sizing that element within its parent and sizing
 * elements contained within it that have a 'resize' function attached to them.
 *
 * To create a Jx.Layout, pass the element or id plus an options object to
 * the constructor.
 *
 * Example:
 * (code)
 * var myContainer = new Jx.Layout('myDiv', options);
 * (end)
 *
 * Events:
 * sizeChange - fired when the size of the container changes
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */

Jx.Layout = new Class({
    Family: 'Jx.Layout',
    Extends: Jx.Object,

    options: {
        /* Option: resizeWithWindow
         * boolean, automatically resize this layout when the window size
         * changes, even if the element is not a direct descendant of the
         * BODY.  False by default.
         */
        resizeWithWindow: false,
        /* Option: propagate
         * boolean, controls propogation of resize to child nodes.
         * True by default. If set to false, changes in size will not be
         * propogated to child nodes.
         */
        propagate: true,
        /* Option: position
         * how to position the element, either 'absolute' or 'relative'.
         * The default (if not passed) is 'absolute'.  When using
         * 'absolute' positioning, both the width and height are
         * controlled by Jx.Layout.  If 'relative' positioning is used
         * then only the width is controlled, allowing the height to
         * be controlled by its content.
         */
        position: 'absolute',
        /* Option: left
         * the distance (in pixels) to maintain the left edge of the element
         * from its parent element.  The default value is 0.  If this is set
         * to 'null', then the left edge can be any distance from its parent
         * based on other parameters.
         */
        left: 0,
        /* Option: right
         * the distance (in pixels) to maintain the right edge of the element
         * from its parent element.  The default value is 0.  If this is set
         * to 'null', then the right edge can be any distance from its parent
         * based on other parameters.
         */
        right: 0,
        /* Option: top
         * the distance (in pixels) to maintain the top edge of the element
         * from its parent element.  The default value is 0.  If this is set
         * to 'null', then the top edge can be any distance from its parent
         * based on other parameters.
         */
        top: 0,
        /* Option: bottom
         * the distance (in pixels) to maintain the bottom edge of the element
         * from its parent element.  The default value is 0.  If this is set
         * to 'null', then the bottom edge can be any distance from its parent
         * based on other parameters.
         */
        bottom: 0,
        /* Option: width
         * the width (in pixels) of the element.  The default value is null.
         * If this is set to 'null', then the width can be any value based on
         * other parameters.
         */
        width: null,
        /* Option: height
         * the height (in pixels) of the element.  The default value is null.
         * If this is set to 'null', then the height can be any value based on
         * other parameters.
         */
        height: null,
        /* Option: minWidth
         * the minimum width that the element can be sized to.  The default
         * value is 0.
         */
        minWidth: 0,
        /* Option: minHeight
         * the minimum height that the element can be sized to.  The
         * default value is 0.
         */
        minHeight: 0,
        /* Option: maxWidth
         * the maximum width that the element can be sized to.  The default
         * value is -1, which means no maximum.
         */
        maxWidth: -1,
        /* Option: maxHeight
         * the maximum height that the element can be sized to.  The
         * default value is -1, which means no maximum.
         */
        maxHeight: -1
    },

    /**
     * Parameters:
     * domObj - {HTMLElement} element or id to apply the layout to
     * options - <Jx.Layout.Options>
     */
    parameters: ['domObj','options'],

    /**
     * APIMethod: init
     * Create a new instance of Jx.Layout.
     */
    init: function() {
        this.domObj = document.id(this.options.domObj);
        this.domObj.resize = this.resize.bind(this);
        this.domObj.setStyle('position', this.options.position);
        this.domObj.store('jxLayout', this);

        if (this.options.resizeWithWindow || document.body == this.domObj.parentNode) {
            window.addEvent('resize', this.windowResize.bindWithEvent(this));
            window.addEvent('load', this.windowResize.bind(this));
        }
        //this.resize();
    },

    /**
     * Method: windowResize
     * when the window is resized, any Jx.Layout controlled elements that are
     * direct children of the BODY element are resized
     */
     windowResize: function() {
         this.resize();
         if (this.resizeTimer) {
             $clear(this.resizeTimer);
             this.resizeTimer = null;
         }
         this.resizeTimer = this.resize.delay(50, this);
    },

    /**
     * Method: resize
     * resize the element controlled by this Jx.Layout object.
     *
     * Parameters:
     * options - new options to apply, see <Jx.Layout.Options>
     */
    resize: function(options) {
         /* this looks like a really big function but actually not
          * much code gets executed in the two big if statements
          */
        this.resizeTimer = null;
        var needsResize = false;
        if (options) {
            for (var i in options) {
                //prevent forceResize: false from causing a resize
                if (i == 'forceResize') {
                    continue;
                }
                if (this.options[i] != options[i]) {
                    needsResize = true;
                    this.options[i] = options[i];
                }
            }
            if (options.forceResize) {
                needsResize = true;
            }
        }
        if (!document.id(this.domObj.parentNode)) {
            return;
        }

        var parentSize;
        if (this.domObj.parentNode.tagName == 'BODY') {
            parentSize = Jx.getPageDimensions();
        } else {
            parentSize = document.id(this.domObj.parentNode).getContentBoxSize();
        }

        if (this.lastParentSize && !needsResize) {
            needsResize = (this.lastParentSize.width != parentSize.width ||
                          this.lastParentSize.height != parentSize.height);
        } else {
            needsResize = true;
        }
        this.lastParentSize = parentSize;

        if (!needsResize) {
            return;
        }

        var l, t, w, h;

        /* calculate left and width */
        if (this.options.left != null) {
            /* fixed left */
            l = this.options.left;
            if (this.options.right == null) {
                /* variable right */
                if (this.options.width == null) {
                    /* variable right and width
                     * set right to min, stretch width */
                    w = parentSize.width - l;
                    if (w < this.options.minWidth ) {
                        w = this.options.minWidth;
                    }
                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
                        w = this.options.maxWidth;
                    }
                } else {
                    /* variable right, fixed width
                     * use width
                     */
                    w = this.options.width;
                }
            } else {
                /* fixed right */
                if (this.options.width == null) {
                    /* fixed right, variable width
                     * stretch width
                     */
                    w = parentSize.width - l - this.options.right;
                    if (w < this.options.minWidth) {
                        w = this.options.minWidth;
                    }
                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
                        w = this.options.maxWidth;
                    }
                } else {
                    /* fixed right, fixed width
                     * respect left and width, allow right to stretch
                     */
                    w = this.options.width;
                }
            }

        } else {
            if (this.options.right == null) {
                if (this.options.width == null) {
                    /* variable left, width and right
                     * set left, right to min, stretch width
                     */
                     l = 0;
                     w = parentSize.width;
                     if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
                         l = l + parseInt(w - this.options.maxWidth,10)/2;
                         w = this.options.maxWidth;
                     }
                } else {
                    /* variable left, fixed width, variable right
                     * distribute space between left and right
                     */
                    w = this.options.width;
                    l = parseInt((parentSize.width - w)/2,10);
                    if (l < 0) {
                        l = 0;
                    }
                }
            } else {
                if (this.options.width != null) {
                    /* variable left, fixed width, fixed right
                     * left is calculated directly
                     */
                    w = this.options.width;
                    l = parentSize.width - w - this.options.right;
                    if (l < 0) {
                        l = 0;
                    }
                } else {
                    /* variable left and width, fixed right
                     * set left to min value and stretch width
                     */
                    l = 0;
                    w = parentSize.width - this.options.right;
                    if (w < this.options.minWidth) {
                        w = this.options.minWidth;
                    }
                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
                        l = w - this.options.maxWidth - this.options.right;
                        w = this.options.maxWidth;
                    }
                }
            }
        }

        /* calculate the top and height */
        if (this.options.top != null) {
            /* fixed top */
            t = this.options.top;
            if (this.options.bottom == null) {
                /* variable bottom */
                if (this.options.height == null) {
                    /* variable bottom and height
                     * set bottom to min, stretch height */
                    h = parentSize.height - t;
                    if (h < this.options.minHeight) {
                        h = this.options.minHeight;
                    }
                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
                        h = this.options.maxHeight;
                    }
                } else {
                    /* variable bottom, fixed height
                     * stretch height
                     */
                    h = this.options.height;
                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
                        t = h - this.options.maxHeight;
                        h = this.options.maxHeight;
                    }
                }
            } else {
                /* fixed bottom */
                if (this.options.height == null) {
                    /* fixed bottom, variable height
                     * stretch height
                     */
                    h = parentSize.height - t - this.options.bottom;
                    if (h < this.options.minHeight) {
                        h = this.options.minHeight;
                    }
                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
                        h = this.options.maxHeight;
                    }
                } else {
                    /* fixed bottom, fixed height
                     * respect top and height, allow bottom to stretch
                     */
                    h = this.options.height;
                }
            }
        } else {
            if (this.options.bottom == null) {
                if (this.options.height == null) {
                    /* variable top, height and bottom
                     * set top, bottom to min, stretch height
                     */
                     t = 0;
                     h = parentSize.height;
                     if (h < this.options.minHeight) {
                         h = this.options.minHeight;
                     }
                     if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
                         t = parseInt((parentSize.height - this.options.maxHeight)/2,10);
                         h = this.options.maxHeight;
                     }
                } else {
                    /* variable top, fixed height, variable bottom
                     * distribute space between top and bottom
                     */
                    h = this.options.height;
                    t = parseInt((parentSize.height - h)/2,10);
                    if (t < 0) {
                        t = 0;
                    }
                }
            } else {
                if (this.options.height != null) {
                    /* variable top, fixed height, fixed bottom
                     * top is calculated directly
                     */
                    h = this.options.height;
                    t = parentSize.height - h - this.options.bottom;
                    if (t < 0) {
                        t = 0;
                    }
                } else {
                    /* variable top and height, fixed bottom
                     * set top to min value and stretch height
                     */
                    t = 0;
                    h = parentSize.height - this.options.bottom;
                    if (h < this.options.minHeight) {
                        h = this.options.minHeight;
                    }
                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
                        t = parentSize.height - this.options.maxHeight - this.options.bottom;
                        h = this.options.maxHeight;
                    }
                }
            }
        }

        //TODO: check left, top, width, height against current styles
        // and only apply changes if they are not the same.

        /* apply the new sizes */
        var sizeOpts = {width: w};
        if (this.options.position == 'absolute') {
            var m = document.id(this.domObj.parentNode).measure(function(){
                return this.getSizes(['padding'],['left','top']).padding;
            });
            this.domObj.setStyles({
                position: this.options.position,
                left: l+m.left,
                top: t+m.top
            });
            sizeOpts.height = h;
        } else {
            if (this.options.height) {
                sizeOpts.height = this.options.height;
            }
        }
        this.domObj.setBorderBoxSize(sizeOpts);

        if (this.options.propagate) {
            // propogate changes to children
            var o = {forceResize: options ? options.forceResize : false};
            $A(this.domObj.childNodes).each(function(child){
                if (child.resize && child.getStyle('display') != 'none') {
                    child.resize.delay(0,child,o);
                }
            });
        }

        this.fireEvent('sizeChange',this);
    }
});/*
---

name: Jx.Toolbar

description: A toolbar is a container object that contains other objects such as buttons.

license: MIT-style license.

requires:
 - Jx.Widget
 - Jx.List

provides: [Jx.Toolbar]

css:
 - toolbar

images:
 - toolbar.png
...
 */
// $Id: toolbar.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Toolbar
 *
 * Extends: <Jx.Widget>
 *
 * A toolbar is a container object that contains other objects such as
 * buttons.  The toolbar organizes the objects it contains automatically,
 * wrapping them as necessary.  Multiple toolbars may be placed within
 * the same containing object.
 *
 * Jx.Toolbar includes CSS classes for styling the appearance of a
 * toolbar to be similar to traditional desktop application toolbars.
 *
 * There is one special object, Jx.ToolbarSeparator, that provides
 * a visual separation between objects in a toolbar.
 *
 * While a toolbar is generally a *dumb* container, it serves a special
 * purpose for menus by providing some infrastructure so that menus can behave
 * properly.
 *
 * In general, almost anything can be placed in a Toolbar, and mixed with
 * anything else.
 *
 * Example:
 * The following example shows how to create a Jx.Toolbar instance and place
 * two objects in it.
 *
 * (code)
 * //myToolbarContainer is the id of a <div> in the HTML page.
 * function myFunction() {}
 * var myToolbar = new Jx.Toolbar('myToolbarContainer');
 *
 * var myButton = new Jx.Button(buttonOptions);
 *
 * var myElement = document.createElement('select');
 *
 * myToolbar.add(myButton, new Jx.ToolbarSeparator(), myElement);
 * (end)
 *
 * Events:
 * add - fired when one or more buttons are added to a toolbar
 * remove - fired when on eor more buttons are removed from a toolbar
 *
 * Implements:
 * Options
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Toolbar = new Class({
    Family: 'Jx.Toolbar',
    Extends: Jx.Widget,
    /**
     * Property: list
     * {<Jx.List>} the list that holds the items in this toolbar
     */
    list : null,
    /**
     * Property: domObj
     * {HTMLElement} the HTML element that the toolbar lives in
     */
    domObj : null,
    /**
     * Property: isActive
     * When a toolbar contains <Jx.Menu> instances, they want to know
     * if any menu in the toolbar is active and this is how they
     * find out.
     */
    isActive : false,
    options: {
        /* Option: position
         * the position of this toolbar in the container.  The position
         * affects some items in the toolbar, such as menus and flyouts, which
         * need to open in a manner sensitive to the position.  May be one of
         * 'top', 'right', 'bottom' or 'left'.  Default is 'top'.
         */
        position: 'top',
        /* Option: parent
         * a DOM element to add this toolbar to
         */
        parent: null,
        /* Option: autoSize
         * if true, the toolbar will attempt to set its size based on the
         * things it contains.  Default is false.
         */
        autoSize: false,
        /**
         * Option: align
         * Determines whether the toolbar is aligned left, center, or right.
         * Mutually exclusive with the scroll option. If scroll is set to true
         * this option does nothing. Default: 'left', valid values: 'left',
         * 'center', or 'right'
         */
        align: 'left',
        /* Option: scroll
         * if true, the toolbar may scroll if the contents are wider than
         * the size of the toolbar
         */
        scroll: true,
        template: '<ul class="jxToolbar"></ul>'
    },
    classes: new Hash({
        domObj: 'jxToolbar'
    }),
    /**
     * APIMethod: render
     * Create a new instance of Jx.Toolbar.
     */
    render: function() {
        this.parent();
        this.domObj.store('jxToolbar', this);
        if ($defined(this.options.id)) {
            this.domObj.id = this.options.id;
        }

        this.list = new Jx.List(this.domObj, {
            onAdd: function(item) {
                this.fireEvent('add', this);
            }.bind(this),
            onRemove: function(item) {
                this.fireEvent('remove', this);
            }.bind(this)
        });

        if (this.options.parent) {
            this.addTo(this.options.parent);
        }
        this.deactivateWatcher = this.deactivate.bindWithEvent(this);
        if (this.options.items) {
            this.add(this.options.items);
        }
    },

    /**
     * Method: addTo
     * add this toolbar to a DOM element automatically creating a toolbar
     * container if necessary
     *
     * Parameters:
     * parent - the DOM element or toolbar container to add this toolbar to.
     */
    addTo: function(parent) {
        var tbc = document.id(parent).retrieve('jxBarContainer');
        if (!tbc) {
            tbc = new Jx.Toolbar.Container({
                parent: parent,
                position: this.options.position,
                autoSize: this.options.autoSize,
                align: this.options.align,
                scroll: this.options.scroll
            });
        }
        tbc.add(this);
        return this;
    },

    /**
     * Method: add
     * Add an item to the toolbar.  If the item being added is a Jx component
     * with a domObj property, the domObj is added.  If the item being added
     * is an LI element, then it is given a CSS class of *jxToolItem*.
     * Otherwise, the thing is wrapped in a <Jx.ToolbarItem>.
     *
     * Parameters:
     * thing - {Object} the thing to add.  More than one thing can be added
     * by passing multiple arguments.
     */
    add: function( ) {
        $A(arguments).flatten().each(function(thing) {
            var item = thing;
            if (item.domObj) {
                item = item.domObj;
            }

            if (item.tagName == 'LI') {
                if (!item.hasClass('jxToolItem')) {
                    item.addClass('jxToolItem');
                }
            } else {
                item = new Jx.Toolbar.Item(thing);
            }
            this.list.add(item);
        }, this);

        //Update the size of the toolbar container.
        this.update();

        return this;
    },
    /**
     * Method: remove
     * remove an item from a toolbar.  If the item is not in this toolbar
     * nothing happens
     *
     * Parameters:
     * item - {Object} the object to remove
     *
     * Returns:
     * {Object} the item that was removed, or null if the item was not
     * removed.
     */
    remove: function(item) {
        if (item.domObj) {
            item = item.domObj;
        }
        var li = item.findElement('LI');
        this.list.remove(li);
        this.update();
        return this;
    },
    /**
     * APIMethod: empty
     * remove all items from the toolbar
     */
    empty: function() {
      this.list.each(function(item){this.remove(item);},this);
    },
    /**
     * Method: deactivate
     * Deactivate the Toolbar (when it is acting as a menu bar).
     */
    deactivate: function() {
        this.list.each(function(item){
            if (item.retrieve('jxMenu')) {
                item.retrieve('jxMenu').hide();
            }
        });
        this.setActive(false);
    },
    /**
     * Method: isActive
     * Indicate if the toolbar is currently active (as a menu bar)
     *
     * Returns:
     * {Boolean}
     */
    isActive: function() {
        return this.isActive;
    },
    /**
     * Method: setActive
     * Set the active state of the toolbar (for menus)
     *
     * Parameters:
     * b - {Boolean} the new state
     */
    setActive: function(b) {
        this.isActive = b;
        if (this.isActive) {
            document.addEvent('click', this.deactivateWatcher);
        } else {
            document.removeEvent('click', this.deactivateWatcher);
        }
    },
    /**
     * Method: setVisibleItem
     * For menus, they want to know which menu is currently open.
     *
     * Parameters:
     * obj - {<Jx.Menu>} the menu that just opened.
     */
    setVisibleItem: function(obj) {
        if (this.visibleItem && this.visibleItem.hide && this.visibleItem != obj) {
            this.visibleItem.hide();
        }
        this.visibleItem = obj;
        if (this.isActive()) {
            this.visibleItem.show();
        }
    },

    showItem: function(item) {
        this.fireEvent('show', item);
    },
    /**
     * Method: update
     * Updates the size of the UL so that the size is always consistently the
     * exact size of the size of the sum of the buttons. This will keep all of
     * the buttons on one line.
     */
    update: function () {
        // if (['top','bottom'].contains(this.options.position)) {
        //     (function(){
        //         var s = 0;
        //         var children = this.domObj.getChildren();
        //         children.each(function(button){
        //             var size = button.getMarginBoxSize();
        //             s += size.width +0.5;
        //         },this);
        //         if (s !== 0) {
        //             this.domObj.setStyle('width', Math.round(s));
        //         } else {
        //             this.domObj.setStyle('width','auto');
        //         }
        //     }).delay(1,this);
        // }
        this.fireEvent('update');
    },
    changeText : function(lang) {
      this.update();
    }
});
/*
---

name: Jx.Toolbar.Container

description: A toolbar container contains toolbars.  This has an optional dependency on Fx.Tween that, if included, will allow toolbars that contain more elements than can be displayed to be smoothly scrolled left and right.  Without this optional dependency, the toolbar will jump in fixed increments rather than smoothly scrolling.

license: MIT-style license.

requires:
 - Jx.Toolbar
 - Jx.Button

optional:
 - Core/Fx.Tween

provides: [Jx.Toolbar.Container]

images:
 - emblems.png

...
 */
// $Id: container.js 1006 2011-01-01 22:43:42Z jonlb@comcast.net $
/**
 * Class: Jx.Toolbar.Container
 *
 * Extends: <Jx.Widget>
 *
 * A toolbar container contains toolbars.  A single toolbar container fills
 * the available space horizontally.  Toolbars placed in a toolbar container
 * do not wrap when they exceed the available space.
 *
 * Events:
 * add - fired when one or more toolbars are added to a container
 * remove - fired when one or more toolbars are removed from a container
 *
 * Implements:
 * Options
 * Events
 * {<Jx.Addable>}
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */

Jx.Toolbar.Container = new Class({

    Family: 'Jx.Toolbar.Container',
    Extends: Jx.Widget,
    Binds: ['update'],
    pluginNamespace: 'ToolbarContainer',
    /**
     * Property: domObj
     * {HTMLElement} the HTML element that the container lives in
     */
    domObj: null,
    options: {
        /* Option: parent
         * a DOM element to add this to
         */
        parent: null,
        /* Option: position
         * the position of the toolbar container in its parent, one of 'top',
         * 'right', 'bottom', or 'left'.  Default is 'top'
         */
        position: 'top',
        /* Option: autoSize
         * automatically size the toolbar container to fill its container.
         * Default is false
         */
        autoSize: false,
        /* Option: scroll
         * Control whether the user can scroll of the content of the
         * container if the content exceeds the size of the container.
         * Default is true.
         */
        scroll: true,
        /**
         * Option: align
         * Determines whether the toolbar is aligned left, center, or right.
         * Mutually exclusive with the scroll option. This option overrides
         * scroll if set to something other than the default. Default: 'left',
         * valid values are 'left','center', or 'right'
         */
        align: 'left',
        template: "<div class='jxBarContainer'><div class='jxBarControls'></div></div>",
        scrollerTemplate: "<div class='jxBarScroller'><div class='jxBarWrapper'></div></div>"
    },
    classes: new Hash({
        domObj: 'jxBarContainer',
        scroller: 'jxBarScroller',
        //used to hide the overflow of the wrapper
        wrapper: 'jxBarWrapper',
        controls: 'jxBarControls'
        //used to allow multiple toolbars to float next to each other
    }),

    updating: false,

    /**
     * APIMethod: render
     * Create a new instance of Jx.Toolbar.Container
     */
    render: function() {
        this.parent();
        /* if a container was passed in, use it instead of the one from the
         * template
         */
        if (document.id(this.options.parent)) {
            this.domObj = document.id(this.options.parent);
            this.elements = new Hash({
                'jxBarContainer': this.domObj
            });
            this.domObj.addClass('jxBarContainer');
            this.domObj.grab(this.controls);
            this.domObj.addEvent('sizeChange', this.update);
        }

        if (!['center', 'right'].contains(this.options.align) && this.options.scroll) {
            this.processElements(this.options.scrollerTemplate, this.classes);
            this.domObj.grab(this.scroller, 'top');
        }

        //add the alignment option... not sure why this keeps getting removed??
        this.domObj.addClass('jxToolbarAlign' +
                this.options.align.capitalize());

        /* this allows toolbars to add themselves to this bar container
         * once it already exists without requiring an explicit reference
         * to the toolbar container
         */
        this.domObj.store('jxBarContainer', this);

        if (['top', 'right', 'bottom', 'left'].contains(this.options.position)) {
            this.domObj.addClass('jxBar' +
            this.options.position.capitalize());
        } else {
            this.domObj.addClass('jxBarTop');
            this.options.position = 'top';
        }

        if (this.options.scroll && ['top', 'bottom'].contains(this.options.position)) {
            // make sure we update our size when we get added to the DOM
            this.addEvent('addTo', function(){
              this.domObj.getParent().addEvent('sizeChange', this.update);
              this.update();
            });

            this.scrollLeft = new Jx.Button({
                image: Jx.aPixel.src
            }).addTo(this.controls, 'bottom');
            document.id(this.scrollLeft).addClass('jxBarScrollLeft');
            this.scrollLeft.addEvents({
                click: this.scroll.bind(this, 'left')
            });

            this.scrollRight = new Jx.Button({
                image: Jx.aPixel.src
            }).addTo(this.controls, 'bottom');
            document.id(this.scrollRight).addClass('jxBarScrollRight');
            this.scrollRight.addEvents({
                click: this.scroll.bind(this, 'right')
            });

        } else if (this.options.scroll && ['left', 'right'].contains(this.options.position)) {
            //do we do scrolling up and down?
            //for now disable scroll in this case
            this.options.scroll = false;
        } else {
            this.options.scroll = false;
        }

        this.addEvent('add', this.update);
        if (this.options.toolbars) {
            this.add(this.options.toolbars);
        }
    },

    /**
     * APIMethod: update
     * Updates the scroller enablement dependent on the total size of the
     * toolbar(s).
     */
    update: function() {
        if (this.options.scroll) {
            if (['top', 'bottom'].contains(this.options.position)) {
                var tbcSize = this.domObj.getContentBoxSize().width;

                var s = 0;
                //next check to see if we need the scrollers or not.
                var children = this.wrapper.getChildren();
                if (children.length > 0) {
                    children.each(function(tb) {
                        s += tb.getMarginBoxSize().width;
                    },
                    this);

                    var scrollerSize = tbcSize;

                    if (s === 0) {
                        this.scrollLeft.setEnabled(false);
                        this.scrollRight.setEnabled(false);
                    } else {


                        var leftMargin = this.wrapper.getStyle('margin-left').toInt();
                        scrollerSize -= this.controls.getMarginBoxSize().width;


                        if (leftMargin < 0) {
                            //has been scrolled left so activate the right scroller
                            this.scrollLeft.setEnabled(true);
                        } else {
                            //we don't need it
                            this.scrollLeft.setEnabled(false);
                        }

                        if (s + leftMargin > scrollerSize) {
                            //we need the right one
                            this.scrollRight.setEnabled(true);
                        } else {
                            //we don't need it
                            this.scrollRight.setEnabled(false);
                        }
                    }

                } else {
                    this.scrollRight.setEnabled(false);
                    this.scrollLeft.setEnabled(false);
                }
                this.scroller.setStyle('width', scrollerSize);

                this.findFirstVisible();
                this.updating = false;
            }
        }
    },
    /**
     * Method: findFirstVisible
     * Finds the first visible button on the toolbar and saves a reference in
     * the scroller object
     */
    findFirstVisible: function() {
        if ($defined(this.scroller.retrieve('buttonPointer'))) {
            return;
        };

        var children = this.wrapper.getChildren();

        if (children.length > 0) {
            children.each(function(toolbar) {
                var buttons = toolbar.getChildren();
                if (buttons.length > 1) {
                    buttons.each(function(button) {
                        var pos = button.getCoordinates(this.scroller);
                        if (pos.left >= 0 && !$defined(this.scroller.retrieve('buttonPointer'))) {
                            //this is the first visible button
                            this.scroller.store('buttonPointer', button);
                        }
                    },
                    this);
                }
            },
            this);
        }
    },

    /**
     * APIMethod: add
     * Add a toolbar to the container.
     *
     * Parameters:
     * toolbar - {Object} the toolbar to add.  More than one toolbar
     *    can be added by passing multiple arguments.
     */
    add: function() {
        $A(arguments).flatten().each(function(thing) {
            if (this.options.scroll) {
                /* we potentially need to show or hide scroller buttons
                 * when the toolbar contents change
                 */
                thing.addEvent('update', this.update.bind(this));
                thing.addEvent('show', this.scrollIntoView.bind(this));
            }
            if (this.wrapper) {
                this.wrapper.adopt(thing.domObj);
            } else {
                this.domObj.adopt(thing.domObj);
            }
            this.domObj.addClass('jxBar' + this.options.position.capitalize());
        },
        this);
        if (arguments.length > 0) {
            this.fireEvent('add', this);
        }
        return this;
    },

    /**
     * Method: scroll
     * Does the work of scrolling the toolbar to a specific position.
     *
     * Parameters:
     * direction - whether to scroll left or right
     */
    scroll: function(direction) {
        if (this.updating) {
            return
        };
        this.updating = true;

        var currentButton = this.scroller.retrieve('buttonPointer');
        if (direction === 'left') {
            //need to tween the amount of the previous button
            var previousButton = this.scroller.retrieve('previousPointer');
            if (!previousButton) {
                previousButton = this.getPreviousButton(currentButton);
            }
            if (previousButton) {
                var w = previousButton.getMarginBoxSize().width;
                var ml = this.wrapper.getStyle('margin-left').toInt();
                ml += w;
                if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
                    //scroll it
                    this.wrapper.get('tween', {
                        property: 'margin-left',
                        onComplete: this.afterTweenLeft.bind(this, previousButton)
                    }).start(ml);
                } else {
                    //set it
                    this.wrapper.setStyle('margin-left', ml);
                    this.afterTweenLeft(previousButton);
                }
            } else {
                this.update();
            }
        } else {
            //must be right
            var w = currentButton.getMarginBoxSize().width;

            var ml = this.wrapper.getStyle('margin-left').toInt();
            ml -= w;

            //now, if Fx is defined tween the margin to the left to
            //hide the current button
            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
                //scroll it
                this.wrapper.get('tween', {
                    property: 'margin-left',
                    onComplete: this.afterTweenRight.bind(this, currentButton)
                }).start(ml);
            } else {
                //set it
                this.wrapper.setStyle('margin-left', ml);
                this.afterTweenRight(currentButton);
            }

        }
    },

    /**
     * Method: afterTweenRight
     * Updates pointers to buttons after the toolbar scrolls right
     *
     * Parameters:
     * currentButton - the button that was currently first before the scroll
     */
    afterTweenRight: function(currentButton) {
        var np = this.getNextButton(currentButton);
        if (!np) {
            np = currentButton;
        }
        this.scroller.store('buttonPointer', np);
        if (np !== currentButton) {
            this.scroller.store('previousPointer', currentButton);
        }
        this.update();
    },
    /**
     * Method: afterTweenLeft
     * Updates pointers to buttons after the toolbar scrolls left
     *
     * Parameters:
     * previousButton - the button that was to the left of the first visible
     *      button.
     */
    afterTweenLeft: function(previousButton) {
        this.scroller.store('buttonPointer', previousButton);
        var pp = this.getPreviousButton(previousButton);
        if ($defined(pp)) {
            this.scroller.store('previousPointer', pp);
        } else {
            this.scroller.eliminate('previousPointer');
        }
        this.update();
    },
    /**
     * APIMethod: remove
     * remove an item from a toolbar.  If the item is not in this toolbar
     * nothing happens
     *
     * Parameters:
     * item - {Object} the object to remove
     *
     * Returns:
     * {Object} the item that was removed, or null if the item was not
     * removed.
     */
    remove: function(item) {
        if (item instanceof Jx.Widget) {
            item.dispose();
        } else {
            document.id(item).dispose();
        }
        this.update();
    },
    /**
     * APIMethod: scrollIntoView
     * scrolls an item in one of the toolbars into the currently visible
     * area of the container if it is not already fully visible
     *
     * Parameters:
     * item - the item to scroll.
     */
    scrollIntoView: function(item) {
        var currentButton = this.scroller.retrieve('buttonPointer');

        if (!$defined(currentButton)) return;

        if ($defined(item.domObj)) {
            item = item.domObj;
            while (!item.hasClass('jxToolItem')) {
                item = item.getParent();
            }
        }
        var pos = item.getCoordinates(this.scroller);
        var scrollerSize = this.scroller.getStyle('width').toInt();

        if (pos.right > 0 && pos.right <= scrollerSize && pos.left > 0 && pos.left <= scrollerSize) {
           //we are completely on screen
            return;
        };

        if (pos.right > scrollerSize) {
            //it's right of the scroller
            var diff = pos.right - scrollerSize;

            //loop through toolbar items until we have enough width to
            //make the item visible
            var ml = this.wrapper.getStyle('margin-left').toInt();
            var w = currentButton.getMarginBoxSize().width;
            var np;
            while (w < diff && $defined(currentButton)) {
                np = this.getNextButton(currentButton);
                if (np) {
                    w += np.getMarginBoxSize().width;
                } else {
                    break;
                }
                currentButton = np;
            }

            ml -= w;

            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
                //scroll it
                this.wrapper.get('tween', {
                    property: 'margin-left',
                    onComplete: this.afterTweenRight.bind(this, currentButton)
                }).start(ml);
            } else {
                //set it
                this.wrapper.setStyle('margin-left', ml);
                this.afterTweenRight(currentButton);
            }
        } else {
            //it's left of the scroller
            var ml = this.wrapper.getStyle('margin-left').toInt();
            ml -= pos.left;

            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
                //scroll it
                this.wrapper.get('tween', {
                    property: 'margin-left',
                    onComplete: this.afterTweenLeft.bind(this, item)
                }).start(ml);
            } else {
                //set it
                this.wrapper.setStyle('margin-left', ml);
                this.afterTweenLeft(item);
            }
        }

    },
    /**
     * Method: getPreviousButton
     * Finds the button to the left of the first visible button
     *
     * Parameters:
     * currentButton - the first visible button
     */
    getPreviousButton: function(currentButton) {
        pp = currentButton.getPrevious();
        if (!$defined(pp)) {
            //check for a new toolbar
            pp = currentButton.getParent().getPrevious();
            if (pp) {
                pp = pp.getLast();
            }
        }
        return pp;
    },
    /**
     * Method: getNextButton
     * Finds the button to the right of the first visible button
     *
     * Parameters:
     * currentButton - the first visible button
     */
    getNextButton: function(currentButton) {
        np = currentButton.getNext();
        if (!np) {
            np = currentButton.getParent().getNext();
            if (np) {
                np = np.getFirst();
            }
        }
        return np;
    }

});
/*
---

name: Jx.Toolbar.Item

description: A helper class to provide a container for something to go into a Jx.Toolbar.

license: MIT-style license.

requires:
 - Jx.Toolbar

provides: [Jx.Toolbar.Item]

...
 */
// $Id: toolbar.item.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Toolbar.Item
 *
 * Extends: Object
 *
 * Implements: Options
 *
 * A helper class to provide a container for something to go into
 * a <Jx.Toolbar>.
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Toolbar.Item = new Class( {
    Family: 'Jx.Toolbar.Item',
    Extends: Jx.Widget,
    options: {
        /* Option: active
         * is this item active or not?  Default is true.
         */
        active: true,
        template: '<li class="jxToolItem"></li>'
    },
    classes: new Hash({
        domObj: 'jxToolItem'
    }),

    parameters: ['jxThing', 'options'],

    /**
     * APIMethod: render
     * Create a new instance of Jx.Toolbar.Item.
     */
    render: function() {
        this.parent();
        var el = document.id(this.options.jxThing);
        if (el) {
            this.domObj.adopt(el);
        }
    }
});/*
---

name: Jx.Panel

description: A panel is a fundamental container object that has a content area and optional toolbars around the content area.

license: MIT-style license.

requires:
 - Jx.Widget
 - Jx.Menu.Item
 - Jx.Layout
 - Jx.Toolbar.Container
 - Jx.Toolbar.Item

provides: [Jx.Panel]

css:
 - panel

images:
 - panel_controls.png
 - panelbar.png

...
 */
// $Id: panel.js 980 2010-09-09 14:02:45Z pagameba $
/**
 * Class: Jx.Panel
 *
 * Extends: <Jx.Widget>
 *
 * A panel is a fundamental container object that has a content
 * area and optional toolbars around the content area.  It also
 * has a title bar area that contains an optional label and
 * some user controls as determined by the options passed to the
 * constructor.
 *
 * Example:
 * (code)
 * (end)
 *
 * Events:
 * close - fired when the panel is closed
 * collapse - fired when the panel is collapsed
 * expand - fired when the panel is opened
 *
 * MooTools.lang Keys:
 * - panel.collapseTooltip
 * - panel.collapseLabel
 * - panel.expandlabel
 * - panel.maximizeTooltip
 * - panel.maximizeLabel
 * - panel.restoreTooltip
 * - panel.restoreLabel
 * - panel.closeTooltip
 * - panel.closeLabel
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Panel = new Class({
    Family: 'Jx.Panel',
    Extends: Jx.Widget,

    toolbarContainers: {
        top: null,
        right: null,
        bottom: null,
        left: null
    },

     options: {
        position: null,
        collapsedClass: 'jxPanelMin',
        collapseClass: 'jxPanelCollapse',
        menuClass: 'jxPanelMenu',
        maximizeClass: 'jxPanelMaximize',
        closeClass: 'jxPanelClose',

        /* Option: label
         * String, the title of the Jx Panel
         */
        label: '&nbsp;',
        /* Option: height
         * integer, fixed height to give the panel - no fixed height by
         * default.
         */
        height: null,
        /* Option: collapse
         * boolean, determine if the panel can be collapsed and expanded
         * by the user.  This puts a control into the title bar for the user
         * to control the state of the panel.
         */
        collapse: true,
        /* Option: close
         * boolean, determine if the panel can be closed (hidden) by the user.
         * The application needs to provide a way to re-open the panel after
         * it is closed.  The closeable property extends to dialogs created by
         * floating panels.  This option puts a control in the title bar of
         * the panel.
         */
        close: false,
        /* Option: closed
         * boolean, initial state of the panel (true to start the panel
         *  closed), default is false
         */
        closed: false,
        /* Option: hideTitle
         * Boolean, hide the title bar if true.  False by default.
         */
        hideTitle: false,
        /* Option: toolbars
         * array of Jx.Toolbar objects to put in the panel.  The position
         * of each toolbar is used to position the toolbar within the panel.
         */
        toolbars: [],
        type: 'panel',
        template: '<div class="jxPanel"><div class="jxPanelTitle"><img class="jxPanelIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxPanelLabel"></span><div class="jxPanelControls"></div></div><div class="jxPanelContentContainer"><div class="jxPanelContent"></div></div></div>',
        controlButtonTemplate: '<a class="jxButtonContainer jxButton"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>'
    },
    classes: new Hash({
        domObj: 'jxPanel',
        title: 'jxPanelTitle',
        domImg: 'jxPanelIcon',
        domLabel: 'jxPanelLabel',
        domControls: 'jxPanelControls',
        contentContainer: 'jxPanelContentContainer',
        content: 'jxPanelContent'
    }),

    /**
     * APIMethod: render
     * Initialize a new Jx.Panel instance
     */
    render : function(){
        this.parent();

        this.toolbars = this.options ? this.options.toolbars || [] : [];

        this.options.position = ($defined(this.options.height) && !$defined(this.options.position)) ? 'relative' : 'absolute';

        if (this.options.image && this.domImg) {
            this.domImg.setStyle('backgroundImage', 'url('+this.options.image+')');
        }
        if (this.options.label && this.domLabel) {
            this.setLabel(this.options.label);
        }

        var tbDiv = new Element('div');
        this.domControls.adopt(tbDiv);
        this.toolbar = new Jx.Toolbar({parent:tbDiv, scroll: false});

        var that = this;
        if (this.options.menu) {
            this.menu = new Jx.Menu({
                image: Jx.aPixel.src
            }, {
              buttonTemplate: this.options.controlButtonTemplate
            });
            this.menu.domObj.addClass(this.options.menuClass);
            this.menu.domObj.addClass('jxButtonContentLeft');
            this.toolbar.add(this.menu);
        }

        //var b, item;
        if (this.options.collapse) {
            if (this.title) {
              this.title.addEvent('dblclick', function() {
                that.toggleCollapse();
              });
            }
            this.colB = new Jx.Button({
                template: this.options.controlButtonTemplate,
                image: Jx.aPixel.src,
                tooltip: {set:'Jx',key:'panel',value:'collapseTooltip'},
                onClick: function() {
                    that.toggleCollapse();
                }
            });
            this.colB.domObj.addClass(this.options.collapseClass);
            this.addEvents({
                collapse: function() {
                    this.colB.setTooltip({set:'Jx',key:'panel',value:'expandTooltip'});
                }.bind(this),
                expand: function() {
                    this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
                }.bind(this)
            });
            this.toolbar.add(this.colB);
            if (this.menu) {
                this.colM = new Jx.Menu.Item({
                    label: this.options.collapseLabel,
                    onClick: function() { that.toggleCollapse(); }
                });
                var item = this.colM
                this.addEvents({
                    collapse: function() {
                        this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
                    }.bind(this),
                    expand: function() {
                        this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
                    }.bind(this)
                });
                this.menu.add(item);
            }
        }

        if (this.options.maximize) {
            this.maxB = new Jx.Button({
                template: this.options.controlButtonTemplate,
                image: Jx.aPixel.src,
                tooltip: {set:'Jx',key:'panel',value:'maximizeTooltip'},
                onClick: function() {
                    that.maximize();
                }
            });
            this.maxB.domObj.addClass(this.options.maximizeClass);
            this.addEvents({
                maximize: function() {
                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'restoreTooltip'});
                }.bind(this),
                restore: function() {
                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
                }.bind(this)
            });
            this.toolbar.add(this.maxB);
            if (this.menu) {
                this.maxM = new Jx.Menu.Item({
                    label: this.options.maximizeLabel,
                    onClick: function() { that.maximize(); }
                });

                this.addEvents({
                    maximize: function() {
                        this.maxM.setLabel({set:'Jx',key:'panel',value:'maximizeLabel'});
                    }.bind(this),
                    restore: function() {
                        this.maxM.setLabel({set:'Jx',key:'panel',value:'restoreLabel'});
                    }.bind(this)
                });
                this.menu.add(this.maxM);
            }
        }

        if (this.options.close) {
            this.closeB = new Jx.Button({
                template: this.options.controlButtonTemplate,
                image: Jx.aPixel.src,
                tooltip: {set:'Jx',key:'panel',value:'closeTooltip'},
                onClick: function() {
                    that.close();
                }
            });
            this.closeB.domObj.addClass(this.options.closeClass);
            this.toolbar.add(this.closeB);
            if (this.menu) {
                this.closeM = new Jx.Menu.Item({
                    label: {set:'Jx',key:'panel',value:'closeLabel'},
                    onClick: function() {
                        that.close();
                    }
                });
                this.menu.add(item);
            }

        }

        if (this.options.id) {
            this.domObj.id = this.options.id;
        }
        var jxl = new Jx.Layout(this.domObj, $merge(this.options, {propagate:false}));
        var layoutHandler = this.layoutContent.bind(this);
        jxl.addEvent('sizeChange', layoutHandler);

        if (this.options.hideTitle) {
            this.title.dispose();
        }

        if (Jx.type(this.options.toolbars) == 'array') {
            this.options.toolbars.each(function(tb){
                var position = tb.options.position;
                var tbc = this.toolbarContainers[position];
                if (!tbc) {
                    tbc = new Element('div');
                    new Jx.Layout(tbc);
                    this.contentContainer.adopt(tbc);
                    this.toolbarContainers[position] = tbc;
                }
                tb.addTo(tbc);
            }, this);
        }

        new Jx.Layout(this.contentContainer);
        new Jx.Layout(this.content);

        if(this.shouldLoadContent()) {
          this.loadContent(this.content);
        }

        this.toggleCollapse(this.options.closed);

        this.addEvent('addTo', function() {
            this.domObj.resize();
        });
        if (this.options.parent) {
            this.addTo(this.options.parent);
        }
    },

    /**
     * Method: layoutContent
     * the sizeChange event of the <Jx.Layout> that manages the outer container
     * is intercepted and passed through this method to handle resizing of the
     * panel contents because we need to do some calculations if the panel
     * is collapsed and if there are toolbars to put around the content area.
     */
    layoutContent: function() {
        var titleHeight = 0;
        var top = 0;
        var bottom = 0;
        var left = 0;
        var right = 0;
        var tbc;
        var tb;
        var position;
        if (!this.options.hideTitle && this.title.parentNode == this.domObj) {
            titleHeight = this.title.getMarginBoxSize().height;
        }
        var domSize = this.domObj.getContentBoxSize();
        if (domSize.height > titleHeight) {
            this.contentContainer.setStyle('display','block');
            this.options.closed = false;
            this.contentContainer.resize({
                top: titleHeight,
                height: null,
                bottom: 0
            });
            ['left','right'].each(function(position){
                if (this.toolbarContainers[position]) {
                    this.toolbarContainers[position].style.width = 'auto';
                }
            }, this);
            ['top','bottom'].each(function(position){
                if (this.toolbarContainers[position]) {
                    this.toolbarContainers[position].style.height = '';
                }
            }, this);
            if (Jx.type(this.options.toolbars) == 'array') {
                this.options.toolbars.each(function(tb){
                    tb.update();
                    position = tb.options.position;
                    tbc = this.toolbarContainers[position];
                    // IE 6 doesn't seem to want to measure the width of
                    // things correctly
                    if (Browser.Engine.trident4) {
                        var oldParent = document.id(tbc.parentNode);
                        tbc.style.visibility = 'hidden';
                        document.id(document.body).adopt(tbc);
                    }
                    var size = tbc.getBorderBoxSize();
                    // put it back into its real parent now we are done
                    // measuring
                    if (Browser.Engine.trident4) {
                        oldParent.adopt(tbc);
                        tbc.style.visibility = '';
                    }
                    switch(position) {
                        case 'bottom':
                            bottom = size.height;
                            break;
                        case 'left':
                            left = size.width;
                            break;
                        case 'right':
                            right = size.width;
                            break;
                        case 'top':
                        default:
                            top = size.height;
                            break;
                    }
                },this);
            }
            tbc = this.toolbarContainers['top'];
            if (tbc) {
                tbc.resize({top: 0, left: left, right: right, bottom: null, height: top, width: null});
            }
            tbc = this.toolbarContainers['bottom'];
            if (tbc) {
                tbc.resize({top: null, left: left, right: right, bottom: 0, height: bottom, width: null});
            }
            tbc = this.toolbarContainers['left'];
            if (tbc) {
                tbc.resize({top: top, left: 0, right: null, bottom: bottom, height: null, width: left});
            }
            tbc = this.toolbarContainers['right'];
            if (tbc) {
                tbc.resize({top: top, left: null, right: 0, bottom: bottom, height: null, width: right});
            }
            this.content.resize({top: top, bottom: bottom, left: left, right: right});
        } else {
            this.contentContainer.setStyle('display','none');
            this.options.closed = true;
        }
        this.fireEvent('sizeChange', this);
    },

    /**
     * Method: setLabel
     * Set the label in the title bar of this panel
     *
     * Parameters:
     * s - {String} the new label
     */
    setLabel: function(s) {
        this.domLabel.set('html',this.getText(s));
    },
    /**
     * Method: getLabel
     * Get the label of the title bar of this panel
     *
     * Returns:
     * {String} the label
     */
    getLabel: function() {
        return this.domLabel.get('html');
    },
    /**
     * Method: finalize
     * Clean up the panel
     */
    finalize: function() {
        this.domObj = null;
        this.deregisterIds();
    },
    /**
     * Method: maximize
     * Maximize this panel
     */
    maximize: function() {
        if (this.manager) {
            this.manager.maximizePanel(this);
        }
    },
    /**
     * Method: setContent
     * set the content of this panel to some HTML
     *
     * Parameters:
     * html - {String} the new HTML to go in the panel
     */
    setContent : function (html) {
        this.content.innerHTML = html;
        this.bContentReady = true;
    },
    /**
     * Method: setContentURL
     * Set the content of this panel to come from some URL.
     *
     * Parameters:
     * url - {String} URL to some HTML content for this panel
     */
    setContentURL : function (url) {
        this.bContentReady = false;
        this.setBusy(true);
        if (arguments[1]) {
            this.onContentReady = arguments[1];
        }
        if (url.indexOf('?') == -1) {
            url = url + '?';
        }
        var a = new Request({
            url: url,
            method: 'get',
            evalScripts:true,
            onSuccess:this.panelContentLoaded.bind(this),
            requestHeaders: ['If-Modified-Since', 'Sat, 1 Jan 2000 00:00:00 GMT']
        }).send();
    },
    /**
     * Method: panelContentLoaded
     * When the content of the panel is loaded from a remote URL, this
     * method is called when the ajax request returns.
     *
     * Parameters:
     * html - {String} the html return from xhr.onSuccess
     */
    panelContentLoaded: function(html) {
        this.content.innerHTML = html;
        this.bContentReady = true;
        this.setBusy(false);
        if (this.onContentReady) {
            window.setTimeout(this.onContentReady.bind(this),1);
        }
    },

    /**
     * Method: toggleCollapse
     * sets or toggles the collapsed state of the panel.  If a
     * new state is passed, it is used, otherwise the current
     * state is toggled.
     *
     * Parameters:
     * state - optional, if passed then the state is used,
     * otherwise the state is toggled.
     */
    toggleCollapse: function(state) {
        if ($defined(state)) {
            this.options.closed = state;
        } else {
            this.options.closed = !this.options.closed;
        }
        if (this.options.closed) {
            if (!this.domObj.hasClass(this.options.collapsedClass)) {
                this.domObj.addClass(this.options.collapsedClass);
                this.contentContainer.setStyle('display','none');
                var m = this.domObj.measure(function(){
                    return this.getSizes(['margin'],['top','bottom']).margin;
                });
                var height = m.top + m.bottom;
                if (this.title.parentNode == this.domObj) {
                    height += this.title.getMarginBoxSize().height;
                }
                this.domObj.resize({height: height});
                this.fireEvent('collapse', this);
            }
        } else {
            if (this.domObj.hasClass(this.options.collapsedClass)) {
                this.domObj.removeClass(this.options.collapsedClass);
                this.contentContainer.setStyle('display','block');
                this.domObj.resize({height: this.options.height});
                this.fireEvent('expand', this);
            }
        }
    },

    /**
     * Method: close
     * Closes the panel (completely hiding it).
     */
    close: function() {
        this.domObj.dispose();
        this.fireEvent('close', this);
    },

    changeText: function (lang) {
    	this.parent();	//TODO: change this class so that we can access these properties without too much voodoo...
    	if($defined(this.closeB)) {
    		this.closeB.setTooltip({set:'Jx',key:'panel',value:'closeTooltip'});
    	}
    	if ($defined(this.closeM)) {
    		this.closeM.setLabel({set:'Jx',key:'panel',value:'closeLabel'});
    	}
    	if ($defined(this.maxB)) {
    		this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
    	}
    	if ($defined(this.colB)) {
    		this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
    	}
    	if ($defined(this.colM)) {
	    	if (this.options.closed == true) {
	    		this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
	    	} else {
	    		this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
	    	}
    	}
      if (this.options.label && this.domLabel) {
          this.setLabel(this.options.label);
      }
      // TODO: is this the right method to call?
      // if toolbars left/right are used and localized, they may change their size..
      this.layoutContent();
    },

    /**
     * Method to be able to allow loadingOnDemand in subclasses but not here
     */
    shouldLoadContent: function() {
      return true;
    }
});/*
---

name: Jx.Dialog

description: A Jx.Panel that implements a floating dialog.

license: MIT-style license.

requires:
 - Jx.Panel
 - more/Keyboard

optional:
 - More/Drag

provides: [Jx.Dialog]

css:
 - dialog

images:
 - dialog_chrome.png
 - dialog_resize.png

...
 */
// $Id: dialog.js 1006 2011-01-01 22:43:42Z jonlb@comcast.net $
/**
 * Class: Jx.Dialog
 *
 * Extends: <Jx.Panel>
 *
 * A Jx.Dialog implements a floating dialog.  Dialogs represent a useful way
 * to present users with certain information or application controls.
 * Jx.Dialog is designed to provide the same types of features as traditional
 * operating system dialog boxes, including:
 *
 * - dialogs may be modal (user must dismiss the dialog to continue) or
 * non-modal
 *
 * - dialogs are movable (user can drag the title bar to move the dialog
 * around)
 *
 * - dialogs may be a fixed size or allow user resizing.
 *
 * Jx.Dialog uses <Jx.ContentLoader> to load content into the content area
 * of the dialog.  Refer to the <Jx.ContentLoader> documentation for details
 * on content options.
 *
 * Example:
 * (code)
 * var dialog = new Jx.Dialog();
 * (end)
 *
 * Events:
 * open - triggered when the dialog is opened
 * close - triggered when the dialog is closed
 * change - triggered when the value of an input in the dialog is changed
 * resize - triggered when the dialog is resized
 *
 * Extends:
 * Jx.Dialog extends <Jx.Panel>, please go there for more details.
 *
 * MooTools.lang Keys:
 * - dialog.resizeToolTip
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Dialog = new Class({
    Family: 'Jx.Dialog',
    Extends: Jx.Panel,

    options: {
        /* Option: modal
         * (optional) {Boolean} controls whether the dialog will be modal
         * or not.  The default is to create modal dialogs.
         */
        modal: true,
        /**
         * Option: maskOptions
         */
        maskOptions: {
          'class':'jxModalMask',
          maskMargins: true,
          useIframeShim: true,
          iframeShimOptions: {
            className: 'jxIframeShim'
          }
        },
        eventMaskOptions: {
          'class':'jxEventMask',
          maskMargins: false,
          useIframeShim: false,
          destroyOnHide: true
        },
        /* just overrides default position of panel, don't document this */
        position: 'absolute',
        /* Option: width
         * (optional) {Integer} the initial width in pixels of the dialog.
         * The default value is 250 if not specified.
         */
        width: 250,
        /* Option: height
         * (optional) {Integer} the initial height in pixels of the
         * dialog. The default value is 250 if not specified.
         */
        height: 250,
        /* Option: horizontal
         * (optional) {String} the horizontal rule for positioning the
         * dialog.  The default is 'center center' meaning the dialog will be
         * centered on the page.  See {<Jx.AutoPosition>} for details.
         */
        horizontal: 'center center',
        /* Option: vertical
         * (optional) {String} the vertical rule for positioning the
         * dialog.  The default is 'center center' meaning the dialog will be
         * centered on the page.  See {<Jx.AutoPosition>} for details.
         */
        vertical: 'center center',
        /* Option: label
         * (optional) {String} the title of the dialog box.
         */
        label: '',
        /* Option: parent
         * (optional) {HTMLElement} a reference to an HTML element that
         * the dialog is to be contained by.  The default value is for the dialog
         * to be contained by the body element.
         */
        //parent: null,
        /* Option: resize
         * (optional) {Boolean} determines whether the dialog is
         * resizeable by the user or not.  Default is false.
         */
        resize: false,

        /* Option: move
         * (optional) {Boolean} determines whether the dialog is
         * moveable by the user or not.  Default is true.
         */
        move: true,
        /*
         * Option: limit
         * (optional) {Object} || false
         * passed to the Drag instance of this dialog to limit the movement
         * {Object} must have x&y coordinates with a range, like {x:[0,500],y:[0,500]}.
         * Set an id or a reference of a DOM Element (ie 'document', 'myContainerWithId',
         * $('myContainer'), $('domID').getParent()) to use these dimensions
         * as boundaries. Default is false.
         */
        limit : false,
        /* Option: close
         * (optional) {Boolean} determines whether the dialog is
         * closeable by the user or not.  Default is true.
         */
        close: true,
        /**
         * Option: useKeyboard
         * (optional) {Boolean} determines whether the Dialog listens to keyboard events globally
         * Default is false
         */
        useKeyboard : false,
        /**
         * Option: keys
         * (optional) {Object} refers with the syntax for MooTools Keyboard Class
         * to functions. Set key to false to disable it manually
         */
        keys: {
          'esc' : 'close'
        },
        /**
         * Option: keyboardMethods
         *
         * can be used to overwrite existing keyboard methods that are used inside
         * this.options.keys - also possible to add new ones.
         * Functions are bound to the dialog when using 'this'
         *
         * example:
         *  keys : {
         *    'alt+enter' : 'maximizeDialog'
         *  },
         *  keyboardMethods: {
         *    'maximizeDialog' : function(ev){
         *      ev.preventDefault();
         *      this.maximize();
         *    }
         *  }
         */
        keyboardMethods : {},
        collapsedClass: 'jxDialogMin',
        collapseClass: 'jxDialogCollapse',
        menuClass: 'jxDialogMenu',
        maximizeClass: 'jxDialogMaximize',
        closeClass: 'jxDialogClose',
        type: 'dialog',
        template: '<div class="jxDialog"><div class="jxDialogTitle"><img class="jxDialogIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxDialogLabel"></span><div class="jxDialogControls"></div></div><div class="jxDialogContentContainer"><div class="jxDialogContent"></div></div></div>'
    },
    classes: new Hash({
        domObj: 'jxDialog',
        title: 'jxDialogTitle',
        domImg: 'jxDialogIcon',
        domLabel: 'jxDialogLabel',
        domControls: 'jxDialogControls',
        contentContainer: 'jxDialogContentContainer',
        content: 'jxDialogContent'
    }),
    /**
     * MooTools Keyboard class for Events (mostly used in Dialog.Confirm, Prompt or Message)
     * But also optional here with esc to close
     */
    keyboard : null,
    /**
     * APIMethod: render
     * renders Jx.Dialog
     */
    render: function() {
        this.isOpening = false;
        this.firstShow = true;

        this.options = $merge(
            {parent:document.body}, // these are defaults that can be overridden
            this.options,
            {position: 'absolute'} // these override anything passed to the options
        );

        /* initialize the panel overriding the type and position */
        this.parent();
        this.openOnLoaded = this.open.bind(this);
        this.options.parent = document.id(this.options.parent);

        this.domObj.setStyle('display','none');
        this.options.parent.adopt(this.domObj);

        /* the dialog is moveable by its title bar */
        if (this.options.move && typeof Drag != 'undefined') {
            this.title.addClass('jxDialogMoveable');

            this.options.limit = this.setDragLimit(this.options.limit);
            // local reference to use Drag instance variables inside onDrag()
            var self = this;
            // COMMENT: any reason why the drag instance isn't referenced to the dialog?
            new Drag(this.domObj, {
                handle: this.title,
                limit: this.options.limit,
                onBeforeStart: (function(){
                    this.stack();
                }).bind(this),
                onStart: function() {
                    if (!self.options.modal && self.options.parent.mask) {
                      self.options.parent.mask(self.options.eventMaskOptions);
                    }
                    self.contentContainer.setStyle('visibility','hidden');
                    self.chrome.addClass('jxChromeDrag');
                    if(self.options.limit) {
                      var coords = self.options.limitOrig.getCoordinates();
                      for(var i in coords) {
                        window.console ? console.log(i, coords[i]) : false;
                      }
                      this.options.limit = self.setDragLimit(self.options.limitOrig);
                    }
                }, // COMMENT: removed bind(this) for setting the limit to the drag instance
                onDrag: function() {
                  if(this.options.limit) {
                    // find out if the right border of the dragged element is out of range
                    if(this.value.now.x+self.options.width >= this.options.limit.x[1]) {
                      this.value.now.x = this.options.limit.x[1] - self.options.width;
                      this.element.setStyle('left',this.value.now.x);
                    }
                    // find out if the bottom border of the dragged element is out of range
                    if(this.value.now.y+self.options.height >= this.options.limit.y[1]) {
                      this.value.now.y = this.options.limit.y[1] - self.options.height;
                      this.element.setStyle('top',this.value.now.y);
                    }
                  }
                },
                onComplete: (function() {
                    if (!this.options.modal && this.options.parent.unmask) {
                      this.options.parent.unmask();
                    }
                    this.chrome.removeClass('jxChromeDrag');
                    this.contentContainer.setStyle('visibility','');
                    var left = Math.max(this.chromeOffsets.left, parseInt(this.domObj.style.left,10));
                    var top = Math.max(this.chromeOffsets.top, parseInt(this.domObj.style.top,10));
                    this.options.horizontal = left + ' left';
                    this.options.vertical = top + ' top';
                    this.position(this.domObj, this.options.parent, this.options);
                    this.options.left = parseInt(this.domObj.style.left,10);
                    this.options.top = parseInt(this.domObj.style.top,10);
                    if (!this.options.closed) {
                        this.domObj.resize(this.options);
                    }
                }).bind(this)
            });
        }

        /* the dialog is resizeable */
        if (this.options.resize && typeof Drag != 'undefined') {
            this.resizeHandle = new Element('div', {
                'class':'jxDialogResize',
                title: this.getText({set:'Jx',key:'panel',value:'resizeTooltip'}),
                styles: {
                    'display':this.options.closed?'none':'block'
                }
            });
            this.domObj.appendChild(this.resizeHandle);

            this.resizeHandleSize = this.resizeHandle.getSize();
            this.resizeHandle.setStyles({
                bottom: this.resizeHandleSize.height,
                right: this.resizeHandleSize.width
            });
            this.domObj.makeResizable({
                handle:this.resizeHandle,
                onStart: (function() {
                    if (!this.options.modal && this.options.parent.mask) {
                      this.options.parent.mask(this.options.eventMaskOptions);
                    }
                    this.contentContainer.setStyle('visibility','hidden');
                    this.chrome.addClass('jxChromeDrag');
                }).bind(this),
                onDrag: (function() {
                    this.resizeChrome(this.domObj);
                }).bind(this),
                onComplete: (function() {
                    if (!this.options.modal && this.options.parent.unmask) {
                      this.options.parent.unmask();
                    }
                    this.chrome.removeClass('jxChromeDrag');
                    var size = this.domObj.getMarginBoxSize();
                    this.options.width = size.width;
                    this.options.height = size.height;
                    this.layoutContent();
                    this.domObj.resize(this.options);
                    this.contentContainer.setStyle('visibility','');
                    this.fireEvent('resize');
                    this.resizeChrome(this.domObj);

                }).bind(this)
            });
        }
        /* this adjusts the zIndex of the dialogs when activated */
        this.domObj.addEvent('mousedown', (function(){
            this.stack();
        }).bind(this));

        // initialize keyboard class
        this.initializeKeyboard();
    },

    /**
     * Method: resize
     * resize the dialog.  This can be called when the dialog is closed
     * or open.
     *
     * Parameters:
     * width - the new width
     * height - the new height
     * autoPosition - boolean, false by default, if resizing an open dialog
     * setting this to true will reposition it according to its position
     * rules.
     */
    resize: function(width, height, autoPosition) {
        this.options.width = width;
        this.options.height = height;
        if (this.domObj.getStyle('display') != 'none') {
            this.layoutContent();
            this.domObj.resize(this.options);
            this.fireEvent('resize');
            this.resizeChrome(this.domObj);
            if (autoPosition) {
                this.position(this.domObj, this.options.parent, this.options);
            }
        } else {
            this.firstShow = false;
        }
    },

    /**
     * Method: sizeChanged
     * overload panel's sizeChanged method
     */
    sizeChanged: function() {
        if (!this.options.closed) {
            this.layoutContent();
        }
    },

    /**
     * Method: toggleCollapse
     * sets or toggles the collapsed state of the panel.  If a
     * new state is passed, it is used, otherwise the current
     * state is toggled.
     *
     * Parameters:
     * state - optional, if passed then the state is used,
     * otherwise the state is toggled.
     */
    toggleCollapse: function(state) {
        if ($defined(state)) {
            this.options.closed = state;
        } else {
            this.options.closed = !this.options.closed;
        }
        if (this.options.closed) {
            if (!this.domObj.hasClass(this.options.collapsedClass)) {
                this.domObj.addClass(this.options.collapsedClass);
            }
            this.contentContainer.setStyle('display','none');
            if (this.resizeHandle) {
                this.resizeHandle.setStyle('display','none');
            }
        } else {
            if (this.domObj.hasClass(this.options.collapsedClass)) {
                this.domObj.removeClass(this.options.collapsedClass);
            }
            this.contentContainer.setStyle('display','block');
            if (this.resizeHandle) {
                this.resizeHandle.setStyle('display','block');
            }
        }

        if (this.options.closed) {
            var m = this.domObj.measure(function(){
                return this.getSizes(['margin'],['top','bottom']).margin;
            });
            var size = this.title.getMarginBoxSize();
            this.domObj.resize({height: m.top + size.height + m.bottom});
            this.fireEvent('collapse');
        } else {
            this.domObj.resize(this.options);
            this.fireEvent('expand');
        }
        this.showChrome(this.domObj);
    },

    /**
     * Method: maximize
     * Called when the maximize button of a dialog is clicked. It will maximize
     * the dialog to match the size of its parent.
     */
    maximize: function () {

        if (!this.maximized) {
            //get size of parent
            var p = this.options.parent;
            var size;

            if (p === document.body) {
                size = Jx.getPageDimensions();
            } else {
                size = p.getBorderBoxSize();
            }
            this.previousSettings = {
                width: this.options.width,
                height: this.options.height,
                horizontal: this.options.horizontal,
                vertical: this.options.vertical,
                left: this.options.left,
                right: this.options.right,
                top: this.options.top,
                bottom: this.options.bottom
            };
            this.options.width = size.width;
            this.options.height = size.height;
            this.options.vertical = '0 top';
            this.options.horizontal = '0 left';
            this.options.right = 0;
            this.options.left = 0;
            this.options.top = 0;
            this.options.bottom = 0;
            this.domObj.resize(this.options);
            this.fireEvent('resize');
            this.resizeChrome(this.domObj);
            this.maximized = true;
            this.domObj.addClass('jxDialogMaximized');
            this.fireEvent('maximize');
        } else {
            this.options = $merge(this.options, this.previousSettings);
            this.domObj.resize(this.options);
            this.fireEvent('resize');
            this.resizeChrome(this.domObj);
            this.maximized = false;
            if (this.domObj.hasClass('jxDialogMaximized')) {
                this.domObj.removeClass('jxDialogMaximized');
            }
            this.fireEvent('restore');
        }
    },

    /**
     * Method: show
     * show the dialog, external code should use the <Jx.Dialog::open> method
     * to make the dialog visible.
     */
    show : function( ) {
        /* prepare the dialog for display */
        this.domObj.setStyles({
            'display': 'block',
            'visibility': 'hidden'
        });
        this.toolbar.update();

        /* do the modal thing */
        if (this.options.modal && this.options.parent.mask) {
          var opts = $merge(this.options.maskOptions || {}, {
            style: {
              'z-index': Jx.getNumber(this.domObj.getStyle('z-index')) - 1
            }
          });
          this.options.parent.mask(opts);
          Jx.Stack.stack(this.options.parent.get('mask').element);
        }
        /* stack the dialog */
        this.stack();

        if (this.options.closed) {
            var m = this.domObj.measure(function(){
                return this.getSizes(['margin'],['top','bottom']).margin;
            });
            var size = this.title.getMarginBoxSize();
            this.domObj.resize({height: m.top + size.height + m.bottom});
        } else {
            this.domObj.resize(this.options);
        }

        if (this.firstShow) {
            this.contentContainer.resize({forceResize: true});
            this.layoutContent();
            this.firstShow = false;
            /* if the chrome got built before the first dialog show, it might
             * not have been properly created and we should clear it so it
             * does get built properly
             */
            if (this.chrome) {
                this.chrome.dispose();
                this.chrome = null;
            }
        }
        /* update or create the chrome */
        this.showChrome(this.domObj);
        /* put it in the right place using auto-positioning */
        this.position(this.domObj, this.options.parent, this.options);
        this.domObj.setStyle('visibility', 'visible');
    },
    /**
     * Method: hide
     * hide the dialog, external code should use the <Jx.Dialog::close>
     * method to hide the dialog.
     */
    hide : function() {
        this.domObj.setStyle('display','none');
        this.unstack();
        if (this.options.modal && this.options.parent.unmask) {
          Jx.Stack.unstack(this.options.parent.get('mask').element);
          this.options.parent.unmask();
        }
        if(this.options.useKeyboard && this.keyboard != null) {
          this.keyboard.deactivate();
        }
    },
    /**
     * Method: openURL
     * open the dialog and load content from the provided url.  If you don't
     * provide a URL then the dialog opens normally.
     *
     * Parameters:
     * url - <String> the url to load when opening.
     */
    openURL: function(url) {
        if (url) {
            this.options.contentURL = url;
            this.options.content = null;  //force Url loading
            this.setBusy();
            this.loadContent(this.content);
            this.addEvent('contentLoaded', this.openOnLoaded);
        } else {
            this.open();
        }
    },

    /**
     * Method: open
     * open the dialog.  This may be delayed depending on the
     * asynchronous loading of dialog content.  The onOpen
     * callback function is called when the dialog actually
     * opens
     */
    open: function() {
        if (!this.isOpening) {
            this.isOpening = true;
        }
        // COMMENT: this works only for onDemand -> NOT for cacheContent = false..
        // for this loading an URL everytime, use this.openURL(url)
        if(!this.contentIsLoaded && this.options.loadOnDemand) {
          this.loadContent(this.content);
        }
        if (this.contentIsLoaded) {
            this.removeEvent('contentLoaded', this.openOnLoaded);
            this.show();
            this.fireEvent('open', this);
            this.isOpening = false;
        } else {
            this.addEvent('contentLoaded', this.openOnLoaded);
        }
        if(this.options.useKeyboard && this.keyboard != null) {
          this.keyboard.activate();
        }
    },
    /**
     * Method: close
     * close the dialog and trigger the onClose callback function
     * if necessary
     */
    close: function() {
        this.isOpening = false;
        this.hide();
        this.fireEvent('close');
    },

    cleanup: function() { },

    /**
     * APIMethod: isOpen
     * returns true if the dialog is currently open, false otherwise
     */
    isOpen: function () {
        //check to see if we're visible
        return !((this.domObj.getStyle('display') === 'none') || (this.domObj.getStyle('visibility') === 'hidden'));
    },

    changeText: function (lang) {
    	this.parent();
    	if ($defined(this.maxM)) {
			if (this.maximize) {
				this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'restoreLabel'}));
	    	} else {
	    		this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'maximizeLabel'}));
	    	}
    	}
    	if ($defined(this.resizeHandle)) {
    		this.resizeHandle.set('title', this.getText({set:'Jx',key:'dialog',value:'resizeTooltip'}));
    	}
      this.toggleCollapse(false);
    },

    initializeKeyboard: function() {
      if(this.options.useKeyboard) {
        var self = this;
        this.keyboardEvents = {};
        this.keyboardMethods = {
          close : function(ev) {ev.preventDefault();self.close()}
        }
        this.keyboard = new Keyboard({
          events: this.getKeyboardEvents()
        });
      }
    },

    /**
     * Method: getKeyboardMethods
     * used by this and all child classes to have methods listen to keyboard events,
     * returned object will be parsed to the events object of a MooTools Keyboard instance
     *
     * @return Object
     */
    getKeyboardEvents : function() {
      var self = this;
      for(var i in this.options.keys) {
        // only add a reference once, otherwise keyboard events will be fired twice in subclasses
        if(!$defined(this.keyboardEvents[i])) {
          if($defined(this.keyboardMethods[this.options.keys[i]])) {
            this.keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
          }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
            this.keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
          }else if(Jx.type(this.options.keys[i]) == 'function') {
            this.keyboardEvents[i] = this.options.keys[i].bind(self);
          }else{
            // allow disabling of special keys by setting them to false or null with having a warning
            if(this.options.keyboardMethods[this.options.keys[i]] != false) {
              $defined(console) ? console.warn("keyboard method %o not defined for %o", this.options.keys[i], this) : false;
            }
          }
        }
      }
      return this.keyboardEvents;
    },

    /**
     * Method: setDragLimit
     * calculates the drag-dimensions of an given element to drag
     *
     * Parameters:
     * - reference {Object} (optional) the element|elementId|object to set the limits
     */
    setDragLimit : function(reference) {
      if($defined(reference)) this.options.limit = reference;

      // check drag limit if it is an container or string for an element and use dimensions
      var limitType = this.options.limit != null ? Jx.type(this.options.limit) : false;
      if(this.options.limit && limitType != 'object') {
        var coords = false;
        switch(limitType) {
          case 'string':
            if(document.id(this.options.limit)) {
              coords = document.id(this.options.limit).getCoordinates();
            }
            break;
          case 'element':
          case 'document':
          case 'window':
            coords = this.options.limit.getCoordinates();
            break;
        }
        if(coords) {
          this.options.limitOrig = this.options.limit;
          this.options.limit = {
            x : [coords.left, coords.right],
            y : [coords.top, coords.bottom]
          }
        }else{
          this.options.limit = false;
        }
      }
      return this.options.limit;
    },

    /**
     * gets called by parent class Jx.Panel and decides whether to load content or not
     */
    shouldLoadContent: function() {
      return !this.options.loadOnDemand;
    }
});

/*
---

name: Jx.Splitter

description: A Jx.Splitter creates two or more containers within a parent container and provides user control over the size of the containers.

license: MIT-style license.

requires:
 - Jx.Layout

optional:
 - More/Drag

provides: [Jx.Splitter]

css:
 - splitter

...
 */
// $Id: splitter.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Splitter
 *
 * Extends: <Jx.Object>
 *
 * a Jx.Splitter creates two or more containers within a parent container
 * and provides user control over the size of the containers.  The split
 * can be made horizontally or vertically.
 *
 * A horizontal split creates containers that divide the space horizontally
 * with vertical bars between the containers.  A vertical split divides
 * the space vertically and creates horizontal bars between the containers.
 *
 * Example:
 * (code)
 * (end)
 *
 * MooTools.lang Keys:
 * - splitter.barToolTip
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */

Jx.Splitter = new Class({
    Family: 'Jx.Splitter',
    Extends: Jx.Object,
    /**
     * Property: domObj
     * {HTMLElement} the element being split
     */
    domObj: null,
    /**
     * Property: elements
     * {Array} an array of elements that are displayed in each of the split
     * areas
     */
    elements: null,
    /**
     * Property: bars
     * {Array} an array of the bars between each of the elements used to
     * resize the split areas.
     */
    bars: null,
    /**
     * Property: firstUpdate
     * {Boolean} track the first resize event so that unexposed Jx things
     * can be forced to calculate their size the first time they are exposed.
     */
    firstUpdate: true,
    options: {
        /* Option: useChildren
         * {Boolean} if set to true, then the children of the
         * element to be split are used as the elements.  The default value is
         * false.  If this is set, then the elements and splitInto options
         * are ignored.
         */
        useChildren: false,
        /* Option: splitInto
         * {Integer} the number of elements to split the domObj into.
         * If not set, then the length of the elements option is used, or 2 if
         * elements is not specified.  If splitInto is specified and elements
         * is specified, then splitInto is used.  If there are more elements than
         * splitInto specifies, then the extras are ignored.  If there are less
         * elements than splitInto specifies, then extras are created.
         */
        splitInto: 2,
        /* Option: elements
         * {Array} an array of elements to put into the split areas.
         * If splitInto is not set, then it is calculated from the length of
         * this array.
         */
        elements: null,
        /* Option: containerOptions
         * {Array} an array of objects that provide options
         *  for the <Jx.Layout> constraints on each element.
         */
        containerOptions: [],
        /* Option: barOptions
         * {Array} an array of object that provide options for the bars,
         * this array should be one less than the number of elements in the
         * splitter.  The barOptions objects can contain a snap property indicating
         * that a default snap object should be created in the bar and the value
         * of 'before' or 'after' indicates which element it snaps open/shut.
         */
        barOptions: [],
        /* Option: layout
         * {String} either 'horizontal' or 'vertical', indicating the
         * direction in which the domObj is to be split.
         */
        layout: 'horizontal',
        /* Option: snaps
         * {Array} an array of objects which can be used to snap
         * elements open or closed.
         */
        snaps: [],
        /* Option: onStart
         * an optional function to call when a bar starts dragging
         */
        onStart: null,
        /* Option: onFinish
         * an optional function to call when a bar finishes dragging
         */
        onFinish: null
    },

    parameters: ['domObj','options'],

    /**
     * APIMethod: init
     * Create a new instance of Jx.Splitter
     */
    init: function() {
        this.domObj = document.id(this.options.domObj);
        this.domObj.addClass('jxSplitContainer');
        var jxLayout = this.domObj.retrieve('jxLayout');
        if (jxLayout) {
            jxLayout.addEvent('sizeChange', this.sizeChanged.bind(this));
        }

        this.elements = [];
        this.bars = [];
        var i;
        var nSplits = 2;
        if (this.options.useChildren) {
            this.elements = this.domObj.getChildren();
            nSplits = this.elements.length;
        } else {
            nSplits = this.options.elements ?
                            this.options.elements.length :
                            this.options.splitInto;
            for (i=0; i<nSplits; i++) {
                var el;
                if (this.options.elements && this.options.elements[i]) {
                    if (this.options.elements[i].domObj) {
                        el = this.options.elements[i].domObj;
                    } else {
                        el = document.id(this.options.elements[i]);
                    }
                    if (!el) {
                        el = this.prepareElement();
                        el.id = this.options.elements[i];
                    }
                } else {
                    el = this.prepareElement();
                }
                this.elements[i] = el;
                this.domObj.adopt(this.elements[i]);
            }
        }
        this.elements.each(function(el) { el.addClass('jxSplitArea'); });
        for (i=0; i<nSplits; i++) {
            var jxl = this.elements[i].retrieve('jxLayout');
            if (!jxl) {
                new Jx.Layout(this.elements[i], this.options.containerOptions[i]);
            } else {
                if (this.options.containerOptions[i]) {
                    jxl.resize($merge(this.options.containerOptions[i],
                        {position:'absolute'}));
                } else {
                    jxl.resize({position: 'absolute'});
                }
            }
        }

        for (i=1; i<nSplits; i++) {
            var bar;
            if (this.options.prepareBar) {
                bar = this.options.prepareBar(i-1);
            } else {
                bar = this.prepareBar();
            }
            bar.store('splitterObj', this);
            bar.store('leftSide',this.elements[i-1]);
            bar.store('rightSide', this.elements[i]);
            this.elements[i-1].store('rightBar', bar);
            this.elements[i].store('leftBar', bar);
            this.domObj.adopt(bar);
            this.bars[i-1] = bar;
        }

        //making dragging dependent on mootools Drag class
        if ($defined(Drag)) {
            this.establishConstraints();
        }

        for (i=0; i<this.options.barOptions.length; i++) {
            if (!this.bars[i]) {
                continue;
            }
            var opt = this.options.barOptions[i];
            if (opt && opt.snap && (opt.snap == 'before' || opt.snap == 'after')) {
                var element;
                if (opt.snap == 'before') {
                    element = this.bars[i].retrieve('leftSide');
                } else if (opt.snap == 'after') {
                    element = this.bars[i].retrieve('rightSide');
                }
                var snap;
                var snapEvents;
                if (opt.snapElement) {
                    snap = opt.snapElement;
                    snapEvents = opt.snapEvents || ['click', 'dblclick'];
                } else {
                    snap = this.bars[i];
                    snapEvents = opt.snapEvents || ['dblclick'];
                }
                if (!snap.parentNode) {
                    this.bars[i].adopt(snap);
                }
                new Jx.Splitter.Snap(snap, element, this, snapEvents);
            }
        }

        for (i=0; i<this.options.snaps.length; i++) {
            if (this.options.snaps[i]) {
                new Jx.Splitter.Snap(this.options.snaps[i], this.elements[i], this);
            }
        }

        this.sizeChanged();
    },
    /**
     * Method: prepareElement
     * Prepare a new, empty element to go into a split area.
     *
     * Returns:
     * {HTMLElement} an HTMLElement that goes into a split area.
     */
    prepareElement: function(){
        var o = new Element('div', {styles:{position:'absolute'}});
        return o;
    },

    /**
     * Method: prepareBar
     * Prepare a new, empty bar to go into between split areas.
     *
     * Returns:
     * {HTMLElement} an HTMLElement that becomes a bar.
     */
    prepareBar: function() {
        var o = new Element('div', {
            'class': 'jxSplitBar'+this.options.layout.capitalize(),
            'title': this.getText({set:'Jx',key:'splitter',value:'barToolTip'})
        });
        return o;
    },

    /**
     * Method: establishConstraints
     * Setup the initial set of constraints that set the behaviour of the
     * bars between the elements in the split area.
     */
    establishConstraints: function() {
        var modifiers = {x:null,y:null};
        var fn;
        if (this.options.layout == 'horizontal') {
            modifiers.x = "left";
            fn = this.dragHorizontal;
        } else {
            modifiers.y = "top";
            fn = this.dragVertical;
        }
        if (typeof Drag != 'undefined') {
            this.bars.each(function(bar){
                var mask;
                new Drag(bar, {
                    //limit: limit,
                    modifiers: modifiers,
                    onSnap : (function(obj) {
                        obj.addClass('jxSplitBarDrag');
                        this.fireEvent('snap',[obj]);
                    }).bind(this),
                    onCancel: (function(obj){
                        mask.destroy();
                        this.fireEvent('cancel',[obj]);
                    }).bind(this),
                    onDrag: (function(obj, event){
                        this.fireEvent('drag',[obj,event]);
                    }).bind(this),
                    onComplete : (function(obj) {
                        mask.destroy();
                        obj.removeClass('jxSplitBarDrag');
                        if (obj.retrieve('splitterObj') != this) {
                            return;
                        }
                        fn.apply(this,[obj]);
                        this.fireEvent('complete',[obj]);
                        this.fireEvent('finish',[obj]);
                    }).bind(this),
                    onBeforeStart: (function(obj) {
                        this.fireEvent('beforeStart',[obj]);
                        mask = new Element('div',{'class':'jxSplitterMask'}).inject(obj, 'after');
                    }).bind(this),
                    onStart: (function(obj, event) {
                        this.fireEvent('start',[obj, event]);
                    }).bind(this)
                });
            }, this);
        }
    },

    /**
     * Method: dragHorizontal
     * In a horizontally split container, handle a bar being dragged left or
     * right by resizing the elements on either side of the bar.
     *
     * Parameters:
     * obj - {HTMLElement} the bar that was dragged
     */
    dragHorizontal: function(obj) {
        var leftEdge = parseInt(obj.style.left,10);
        var leftSide = obj.retrieve('leftSide');
        var rightSide = obj.retrieve('rightSide');
        var leftJxl = leftSide.retrieve('jxLayout');
        var rightJxl = rightSide.retrieve('jxLayout');

        var paddingLeft = this.domObj.measure(function(){
            var m = this.getSizes(['padding'], ['left']);
            return m.padding.left;
        });

        /* process right side first */
        var rsLeft, rsWidth, rsRight;

        var size = obj.retrieve('size');
        if (!size) {
            size = obj.getBorderBoxSize();
            obj.store('size',size);
        }
        rsLeft = leftEdge + size.width - paddingLeft;

        var parentSize = this.domObj.getContentBoxSize();

        if (rightJxl.options.width != null) {
            rsWidth = rightJxl.options.width + rightJxl.options.left - rsLeft;
            rsRight = parentSize.width - rsLeft - rsWidth;
        } else {
            rsWidth = parentSize.width - rightJxl.options.right - rsLeft;
            rsRight = rightJxl.options.right;
        }

        /* enforce constraints on right side */
        if (rsWidth < 0) {
            rsWidth = 0;
        }

        if (rsWidth < rightJxl.options.minWidth) {
            rsWidth = rightJxl.options.minWidth;
        }
        if (rightJxl.options.maxWidth >= 0 && rsWidth > rightJxl.options.maxWidth) {
            rsWidth = rightJxl.options.maxWidth;
        }

        rsLeft = parentSize.width - rsRight - rsWidth;
        leftEdge = rsLeft - size.width;

        /* process left side */
        var lsLeft, lsWidth;
        lsLeft = leftJxl.options.left;
        lsWidth = leftEdge - lsLeft;

        /* enforce constraints on left */
        if (lsWidth < 0) {
            lsWidth = 0;
        }
        if (lsWidth < leftJxl.options.minWidth) {
            lsWidth = leftJxl.options.minWidth;
        }
        if (leftJxl.options.maxWidth >= 0 &&
            lsWidth > leftJxl.options.maxWidth) {
            lsWidth = leftJxl.options.maxWidth;
        }

        /* update the leftEdge to accomodate constraints */
        if (lsLeft + lsWidth != leftEdge) {
            /* need to update right side, ignoring constraints because left side
               constraints take precedence (arbitrary decision)
             */
            leftEdge = lsLeft + lsWidth;
            var delta = leftEdge + size.width - rsLeft;
            rsLeft += delta;
            rsWidth -= delta;
        }

        /* put bar in its final location based on constraints */
        obj.style.left = paddingLeft + leftEdge + 'px';

        /* update leftSide positions */
        if (leftJxl.options.width == null) {
            parentSize = this.domObj.getContentBoxSize();
            leftSide.resize({right: parentSize.width - lsLeft-lsWidth});
        } else {
            leftSide.resize({width: lsWidth});
        }

        /* update rightSide position */
        if (rightJxl.options.width == null) {
            rightSide.resize({left:rsLeft});
        } else {
            rightSide.resize({left: rsLeft, width: rsWidth});
        }
    },

    /**
     * Method: dragVertical
     * In a vertically split container, handle a bar being dragged up or
     * down by resizing the elements on either side of the bar.
     *
     * Parameters:
     * obj - {HTMLElement} the bar that was dragged
     */
    dragVertical: function(obj) {
        /* top edge of the bar */
        var topEdge = parseInt(obj.style.top,10);

        /* the containers on either side of the bar */
        var topSide = obj.retrieve('leftSide');
        var bottomSide = obj.retrieve('rightSide');
        var topJxl = topSide.retrieve('jxLayout');
        var bottomJxl = bottomSide.retrieve('jxLayout');

        var paddingTop = this.domObj.measure(function(){
            var m = this.getSizes(['padding'], ['top']);
            return m.padding.top;
        });


        /* measure the bar and parent container for later use */
        var size = obj.retrieve('size');
        if (!size) {
            size = obj.getBorderBoxSize();
            obj.store('size', size);
        }
        var parentSize = this.domObj.getContentBoxSize();

        /* process top side first */
        var bsTop, bsHeight, bsBottom;

        /* top edge of bottom side is the top edge of bar plus the height of the bar */
        bsTop = topEdge + size.height - paddingTop;

        if (bottomJxl.options.height != null) {
            /* bottom side height is fixed */
            bsHeight = bottomJxl.options.height + bottomJxl.options.top - bsTop;
            bsBottom = parentSize.height - bsTop - bsHeight;
        } else {
            /* bottom side height is not fixed. */
            bsHeight = parentSize.height - bottomJxl.options.bottom - bsTop;
            bsBottom = bottomJxl.options.bottom;
        }

        /* enforce constraints on bottom side */
        if (bsHeight < 0) {
            bsHeight = 0;
        }

        if (bsHeight < bottomJxl.options.minHeight) {
            bsHeight = bottomJxl.options.minHeight;
        }

        if (bottomJxl.options.maxHeight >= 0 && bsHeight > bottomJxl.options.maxHeight) {
            bsHeight = bottomJxl.options.maxHeight;
        }

        /* recalculate the top of the bottom side in case it changed
           due to a constraint.  The bar may have moved also.
         */
        bsTop = parentSize.height - bsBottom - bsHeight;
        topEdge = bsTop - size.height;

        /* process left side */
        var tsTop, tsHeight;
        tsTop = topJxl.options.top;
        tsHeight = topEdge - tsTop;

        /* enforce constraints on left */
        if (tsHeight < 0) {
            tsHeight = 0;
        }
        if (tsHeight < topJxl.options.minHeight) {
            tsHeight = topJxl.options.minHeight;
        }
        if (topJxl.options.maxHeight >= 0 &&
            tsHeight > topJxl.options.maxHeight) {
            tsHeight = topJxl.options.maxHeight;
        }

        /* update the topEdge to accomodate constraints */
        if (tsTop + tsHeight != topEdge) {
            /* need to update right side, ignoring constraints because left side
               constraints take precedence (arbitrary decision)
             */
            topEdge = tsTop + tsHeight;
            var delta = topEdge + size.height - bsTop;
            bsTop += delta;
            bsHeight -= delta;
        }

        /* put bar in its final location based on constraints */
        obj.style.top = paddingTop + topEdge + 'px';

        /* update topSide positions */
        if (topJxl.options.height == null) {
            topSide.resize({bottom: parentSize.height - tsTop-tsHeight});
        } else {
            topSide.resize({height: tsHeight});
        }

        /* update bottomSide position */
        if (bottomJxl.options.height == null) {
            bottomSide.resize({top:bsTop});
        } else {
            bottomSide.resize({top: bsTop, height: bsHeight});
        }
    },

    /**
     * Method: sizeChanged
     * handle the size of the container being changed.
     */
    sizeChanged: function() {
        if (this.options.layout == 'horizontal') {
            this.horizontalResize();
        } else {
            this.verticalResize();
        }
    },

    /**
     * Method: horizontalResize
     * Resize a horizontally layed-out container
     */
    horizontalResize: function() {
        var availableSpace = this.domObj.getContentBoxSize().width;
        var overallWidth = availableSpace;
        var i,e,jxo;
        for (i=0; i<this.bars.length; i++) {
            var bar = this.bars[i];
            var size = bar.retrieve('size');
            if (!size || size.width == 0) {
                size = bar.getBorderBoxSize();
                bar.store('size',size);
            }
            availableSpace -= size.width;
        }

        var nVariable = 0, w = 0;
        for (i=0; i<this.elements.length; i++) {
            e = this.elements[i];
            jxo = e.retrieve('jxLayout').options;
            if (jxo.width != null) {
                availableSpace -= parseInt(jxo.width,10);
            } else {
                w = 0;
                if (jxo.right != 0 ||
                    jxo.left != 0) {
                    w = e.getBorderBoxSize().width;
                }

                availableSpace -= w;
                nVariable++;
            }
        }

        if (nVariable == 0) { /* all fixed */
            /* stick all available space in the last one */
            availableSpace += jxo.width;
            jxo.width = null;
            nVariable = 1;
        }

        var amount = parseInt(availableSpace / nVariable,10);
        /* account for rounding errors */
        var remainder = availableSpace % nVariable;

        var leftPadding = this.domObj.measure(function(){
            var m = this.getSizes(['padding'], ['left']);
            return m.padding.left;
        });

        var currentPosition = 0;

        for (i=0; i<this.elements.length; i++) {
             e = this.elements[i];
             var jxl = e.retrieve('jxLayout');
             jxo = jxl.options;
             if (jxo.width != null) {
                 jxl.resize({left: currentPosition});
                 currentPosition += jxo.width;
             } else {
                 var a = amount;
                 if (nVariable == 1) {
                     a += remainder;
                 }
                 nVariable--;

                 if (jxo.right != 0 || jxo.left != 0) {
                     w = e.getBorderBoxSize().width + a;
                 } else {
                     w = a;
                 }

                 if (w < 0) {
                     if (nVariable > 0) {
                         amount = amount + w/nVariable;
                     }
                     w = 0;
                 }
                 if (w < jxo.minWidth) {
                     if (nVariable > 0) {
                         amount = amount + (w - jxo.minWidth)/nVariable;
                     }
                     w = jxo.minWidth;
                 }
                 if (jxo.maxWidth >= 0 && w > jxo.maxWidth) {
                     if (nVariable > 0) {
                         amount = amount + (w - jxo.maxWidth)/nVariable;
                     }
                     w = e.options.maxWidth;
                 }

                 var r = overallWidth - currentPosition - w;
                 jxl.resize({left: currentPosition, right: r});
                 currentPosition += w;
             }
             var rightBar = e.retrieve('rightBar');
             if (rightBar) {
                 rightBar.setStyle('left', leftPadding + currentPosition);
                 currentPosition += rightBar.retrieve('size').width;
             }
         }
    },

    /**
     * Method: verticalResize
     * Resize a vertically layed out container.
     */
    verticalResize: function() {
        var availableSpace = this.domObj.getContentBoxSize().height;
        var overallHeight = availableSpace;
        var i,e,jxo;
        for (i=0; i<this.bars.length; i++) {
            var bar = this.bars[i];
            var size = bar.retrieve('size');
            if (!size || size.height == 0) {
                size = bar.getBorderBoxSize();
                bar.store('size', size);
            }
            availableSpace -= size.height;
        }

        var nVariable = 0, h=0;
        for (i=0; i<this.elements.length; i++) {
            e = this.elements[i];
            jxo = e.retrieve('jxLayout').options;
            if (jxo.height != null) {
                availableSpace -= parseInt(jxo.height,10);
            } else {
                if (jxo.bottom != 0 || jxo.top != 0) {
                    h = e.getBorderBoxSize().height;
                }

                availableSpace -= h;
                nVariable++;
            }
        }

        if (nVariable == 0) { /* all fixed */
            /* stick all available space in the last one */
            availableSpace += jxo.height;
            jxo.height = null;
            nVariable = 1;
        }

        var amount = parseInt(availableSpace / nVariable,10);
        /* account for rounding errors */
        var remainder = availableSpace % nVariable;

        var paddingTop = this.domObj.measure(function(){
            var m = this.getSizes(['padding'], ['top']);
            return m.padding.top;
        });

        var currentPosition = 0;

        for (i=0; i<this.elements.length; i++) {
             e = this.elements[i];
             var jxl = e.retrieve('jxLayout');
             jxo = jxl.options;
             if (jxo.height != null) {
                 jxl.resize({top: currentPosition});
                 currentPosition += jxo.height;
             } else {
                 var a = amount;
                 if (nVariable == 1) {
                     a += remainder;
                 }
                 nVariable--;

                 h = 0;
                 if (jxo.bottom != 0 || jxo.top != 0) {
                     h = e.getBorderBoxSize().height + a;
                 } else {
                     h = a;
                 }

                 if (h < 0) {
                     if (nVariable > 0) {
                         amount = amount + h/nVariable;
                     }
                     h = 0;
                 }
                 if (h < jxo.minHeight) {
                     if (nVariable > 0) {
                         amount = amount + (h - jxo.minHeight)/nVariable;
                     }
                     h = jxo.minHeight;
                 }
                 if (jxo.maxHeight >= 0 && h > jxo.maxHeight) {
                     if (nVariable > 0) {
                         amount = amount + (h - jxo.maxHeight)/nVariable;
                     }
                     h = jxo.maxHeight;
                 }

                 var r = overallHeight - currentPosition - h;
                 jxl.resize({top: currentPosition, bottom: r});
                 currentPosition += h;
             }
             var rightBar = e.retrieve('rightBar');
             if (rightBar) {
                 rightBar.style.top = paddingTop + currentPosition + 'px';
                 currentPosition += rightBar.retrieve('size').height;
             }
         }
    },

    changeText: function (lang) {
    	this.parent();
    	this.bars.each(function(bar){
    		document.id(bar).set('title', this.getText({set:'Jx',key:'splitter',value:'barToolTip'}));
    	},this);
    }
});/*
---

name: Jx.PanelSet

description: A panel set manages a set of panels within a DOM element.

license: MIT-style license.

requires:
 - Jx.Splitter
 - Jx.Panel

provides: [Jx.PanelSet]

...
 */
// $Id: panelset.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.PanelSet
 *
 * Extends: <Jx.Widget>
 *
 * A panel set manages a set of panels within a DOM element.  The PanelSet
 * fills its container by resizing the panels in the set to fill the width and
 * then distributing the height of the container across all the panels.
 * Panels can be resized by dragging their respective title bars to make them
 * taller or shorter.  The maximize button on the panel title will cause all
 * other panels to be closed and the target panel to be expanded to fill the
 * remaining space.  In this respect, PanelSet works like a traditional
 * Accordion control.
 *
 * When creating panels for use within a panel set, it is important to use the
 * proper options.  You must override the collapse option and set it to false
 * and add a maximize option set to true.  You must also not include options
 * for menu and close.
 *
 * Example:
 * (code)
 * var p1 = new Jx.Panel({collapse: false, maximize: true, content: 'c1'});
 * var p2 = new Jx.Panel({collapse: false, maximize: true, content: 'c2'});
 * var p3 = new Jx.Panel({collapse: false, maximize: true, content: 'c3'});
 * var panelSet = new Jx.PanelSet('panels', [p1,p2,p3]);
 * (end)
 *
 * MooTools.lang Keys:
 * - panelset.barTooltip
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.PanelSet = new Class({
    Family: 'Jx.PanelSet',
    Extends: Jx.Widget,

    options: {
        /* Option: parent
         * the object to add the panel set to
         */
        parent: null,
        /* Option: panels
         * an array of <Jx.Panel> objects that will be managed by the set.
         */
        panels: []
    },

    /**
     * Property: panels
     * {Array} the panels being managed by the set
     */
    panels: null,
    /**
     * Property: height
     * {Integer} the height of the container, cached for speed
     */
    height: null,
    /**
     * Property: firstLayout
     * {Boolean} true until the panel set has first been resized
     */
    firstLayout: true,
    /**
     * APIMethod: render
     * Create a new instance of Jx.PanelSet.
     */
    render: function() {
        if (this.options.panels) {
            this.panels = this.options.panels;
            this.options.panels = null;
        }
        this.domObj = new Element('div');
        new Jx.Layout(this.domObj);

        //make a fake panel so we get the right number of splitters
        var d = new Element('div', {styles:{position:'absolute'}});
        new Jx.Layout(d, {minHeight:0,maxHeight:0,height:0});
        var elements = [d];
        this.panels.each(function(panel){
            elements.push(panel.domObj);
            panel.options.hideTitle = true;
            panel.contentContainer.resize({top:0});
            panel.toggleCollapse = this.maximizePanel.bind(this,panel);
            panel.domObj.store('Jx.Panel', panel);
            panel.manager = this;
        }, this);

        this.splitter = new Jx.Splitter(this.domObj, {
            splitInto: this.panels.length+1,
            layout: 'vertical',
            elements: elements,
            prepareBar: (function(i) {
                var bar = new Element('div', {
                    'class': 'jxPanelBar',
                    'title': this.getText({set:'Jx',key:'panelset',value:'barToolTip'})
                });

                var panel = this.panels[i];
                panel.title.setStyle('visibility', 'hidden');
                document.id(document.body).adopt(panel.title);
                var size = panel.title.getBorderBoxSize();
                bar.adopt(panel.title);
                panel.title.setStyle('visibility','');

                bar.setStyle('height', size.height);
                bar.store('size', size);

                return bar;
            }).bind(this)
        });
        this.addEvent('addTo', function() {
            document.id(this.domObj.parentNode).setStyle('overflow', 'hidden');
            this.domObj.resize();
        });
        if (this.options.parent) {
            this.addTo(this.options.parent);
        }
    },

    /**
     * Method: maximizePanel
     * Maximize a panel, taking up all available space (taking into
     * consideration any minimum or maximum values)
     */
    maximizePanel: function(panel) {
        var domHeight = this.domObj.getContentBoxSize().height;
        var space = domHeight;
        var panelSize = panel.domObj.retrieve('jxLayout').options.maxHeight;
        var panelIndex,i,p,thePanel,o,panelHeight;
        /* calculate how much space might be left after setting all the panels to
         * their minimum height (except the one we are resizing of course)
         */
        for (i=1; i<this.splitter.elements.length; i++) {
            p = this.splitter.elements[i];
            space -= p.retrieve('leftBar').getBorderBoxSize().height;
            if (p !== panel.domObj) {
                thePanel = p.retrieve('Jx.Panel');
                o = p.retrieve('jxLayout').options;
                space -= o.minHeight;
            } else {
                panelIndex = i;
            }
        }

        // calculate how much space the panel will take and what will be left over
        if (panelSize == -1 || panelSize >= space) {
            panelSize = space;
            space = 0;
        } else {
            space = space - panelSize;
        }
        var top = 0;
        for (i=1; i<this.splitter.elements.length; i++) {
            p = this.splitter.elements[i];
            top += p.retrieve('leftBar').getBorderBoxSize().height;
            if (p !== panel.domObj) {
                thePanel = p.retrieve('Jx.Panel');
                o = p.retrieve('jxLayout').options;
                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
                if (space > 0) {
                    if (space >= panelHeight) {
                        // this panel can stay open at its current height
                        space -= panelHeight;
                        p.resize({top: top, height: panelHeight});
                        top += panelHeight;
                    } else {
                        // this panel needs to shrink some
                        if (space > o.minHeight) {
                            // it can use all the space
                            p.resize({top: top, height: space});
                            top += space;
                            space = 0;
                        } else {
                            p.resize({top: top, height: o.minHeight});
                            top += o.minHeight;
                        }
                    }
                } else {
                    // no more space, just shrink away
                    p.resize({top:top, height: o.minHeight});
                    top += o.minHeight;
                }
                p.retrieve('rightBar').style.top = top + 'px';
            } else {
                break;
            }
        }

        /* now work from the bottom up */
        var bottom = domHeight;
        for (i=this.splitter.elements.length - 1; i > 0; i--) {
            p = this.splitter.elements[i];
            if (p !== panel.domObj) {
                o = p.retrieve('jxLayout').options;
                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
                if (space > 0) {
                    if (space >= panelHeight) {
                        // panel can stay open
                        bottom -= panelHeight;
                        space -= panelHeight;
                        p.resize({top: bottom, height: panelHeight});
                    } else {
                        if (space > o.minHeight) {
                            bottom -= space;
                            p.resize({top: bottom, height: space});
                            space = 0;
                        } else {
                            bottom -= o.minHeight;
                            p.resize({top: bottom, height: o.minHeight});
                        }
                    }
                } else {
                    bottom -= o.minHeight;
                    p.resize({top: bottom, height: o.minHeight, bottom: null});
                }
                bottom -= p.retrieve('leftBar').getBorderBoxSize().height;
                p.retrieve('leftBar').style.top = bottom + 'px';

            } else {
                break;
            }
        }
        panel.domObj.resize({top: top, height:panelSize, bottom: null});
        this.fireEvent('panelMaximize',panel);
    },

    createText: function (lang) {
      this.parent();
      //barTooltip is handled by the splitter's createText() function
    }
});/*
---

name: Jx.Dialog.Message

description: A subclass of jx.Dialog for displaying messages w/a single OK button.

license: MIT-style license.

requires:
 - Jx.Dialog
 - Jx.Button
 - Jx.Toolbar.Item

provides: [Jx.Dialog.Message]

css:
 - message

...
 */
// $Id: message.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Dialog.Message
 *
 * Extends: <Jx.Dialog>
 *
 * Jx.Dialog.Message is an extension of Jx.Dialog that allows the developer
 * to display a message to the user. It only presents an OK button.
 *
 * MooTools.lang Keys:
 * - message.okButton
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner
 *
 * This file is licensed under an MIT style license
 */
Jx.Dialog.Message = new Class({
    Family: 'Jx.Dialog.Message',
    Extends: Jx.Dialog,
    Binds: ['onOk'],
    options: {
        /**
         * Option: message
         * The message to display to the user
         */
        message: '',
        /**
         * Option: width
         * default width of message dialogs is 300px
         */
        width: 300,
        /**
         * Option: height
         * default height of message dialogs is 150px
         */
        height: 150,
        /**
         * Option: close
         * by default, message dialogs are closable
         */
        close: true,
        /**
         * Option: resize
         * by default, message dialogs are resizable
         */
        resize: true,
        /**
         * Option: collapse
         * by default, message dialogs are not collapsible
         */
        collapse: false,
        useKeyboard : true,
        keys : {
          'enter' : 'ok'
        }
    },
    /**
     * Method: render
     * constructs the dialog.
     */
    render: function () {
        //create content to be added
        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
        this.ok = new Jx.Button({
            label: this.getText({set:'Jx',key:'message',value:'okButton'}),
            onClick: this.onOk
        });
        this.buttons.add(this.ok);
        this.options.toolbars = [this.buttons];
        var type = Jx.type(this.options.message);
        if (type === 'string' || type == 'object' || type == 'element') {
            this.question = new Element('div', {
                'class': 'jxMessage'
            });
            switch(type) {
              case 'string':
              case 'object':
                this.question.set('html', this.getText(this.options.message));
              break;
              case 'element':
                this.options.message.inject(this.question);
                break;
            }
        } else {
            this.question = this.options.question;
            document.id(this.question).addClass('jxMessage');
        }
        this.options.content = this.question;
        if(this.options.useKeyboard) {
          var self = this;
          this.options.keyboardMethods.ok = function(ev) { ev.preventDefault(); self.close(); }
        }
        this.parent();
        if(this.options.useKeyboard) {
          this.keyboard.addEvents(this.getKeyboardEvents());
        }
    },
    /**
     * Method: onOk
     * Called when the OK button is clicked. Closes the dialog.
     */
    onOk: function () {
        this.close();
    },

    /**
     * APIMethod: setMessage
     * set the message of the dialog, useful for responding to language
     * changes on the fly.
     *
     * Parameters
     * message - {String} the new message
     */
    setMessage: function(message) {
      this.options.message = message;
      if ($defined(this.question)) {
        this.question.set('html',this.getText(message));
      }
    },

    /**
     * Method: createText
     * handle change in language
     */
    changeText: function (lang) {
      this.parent();
      if ($defined(this.ok)) {
        this.ok.setLabel({set:'Jx',key:'message',value:'okButton'});
      }
      if(Jx.type(this.options.message) === 'object') {
        this.question.set('html', this.getText(this.options.message))
      }
    }
});
/*
---

name: Jx.Dialog.Confirm

description: A subclass of Jx.dialog for asking a yes/no type question of the user.

license: MIT-style license.

requires:
 - Jx.Dialog
 - Jx.Button
 - Jx.Toolbar.Item

provides: [Jx.Dialog.Confirm]

css:
 - confirm

...
 */
// $Id: confirm.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Dialog.Confirm
 *
 * Extends: <Jx.Dialog>
 *
 * Jx.Dialog.Confirm is an extension of Jx.Dialog that allows the developer
 * to prompt their user with e yes/no question.
 *
 * MooTools.lang Keys:
 * - confirm.affirmitiveLabel
 * - confirm.negativeLabel
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner
 *
 * This file is licensed under an MIT style license
 */
Jx.Dialog.Confirm = new Class({

    Extends: Jx.Dialog,

    options: {
        /**
         * Option: question
         * The question to ask the user
         */
        question: '',
        /**
         * Jx.Dialog option defaults
         */
        useKeyboard : true,
        keys : {
          'esc'   : 'cancel',
          'enter' : 'ok'
        },
        width: 300,
        height: 150,
        close: false,
        resize: true,
        collapse: false
    },
    /**
     * Reference to MooTools keyboards Class for handling keypress events like Enter or ESC
     */
    keyboard : null,
    /**
     * APIMethod: render
     * creates the dialog
     */
    render: function () {
        //create content to be added
        //turn scrolling off as confirm only has 2 buttons.
        this.buttons = new Jx.Toolbar({position: 'bottom',scroll: false});

        // COMMENT: returning boolean would be more what people expect instead of a localized label of a button?
        this.ok = new Jx.Button({
            label: this.getText({set:'Jx',key:'confirm',value:'affirmativeLabel'}),
            onClick: this.onClick.bind(this, true)
        }),
        this.cancel = new Jx.Button({
            label: this.getText({set:'Jx',key:'confirm',value:'negativeLabel'}),
            onClick: this.onClick.bind(this, false)
        })
        this.buttons.add(this.ok, this.cancel);
        this.options.toolbars = [this.buttons];
        var type = Jx.type(this.options.question);
        if (type === 'string' || type === 'object' || type == 'element'){
            this.question = new Element('div', {
                'class': 'jxConfirmQuestion'
            });
            switch(type) {
              case 'string':
              case 'object':
                this.question.set('html', this.getText(this.options.question));
              break;
              case 'element':
                this.options.question.inject(this.question);
                break;
            }
        } else {
            this.question = this.options.question;
            document.id(this.question).addClass('jxConfirmQuestion');
        }
        this.options.content = this.question;

        // add default key functions
        if(this.options.useKeyboard) {
          var self = this;
          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
        }
        this.parent();
        // add new ones
        if(this.options.useKeyboard) {
          this.keyboard.addEvents(this.getKeyboardEvents());
        }
    },
    /**
     * Method: onClick
     * called when any button is clicked. It hides the dialog and fires
     * the close event passing it the value of the button that was pressed.
     */
    onClick: function (value) {
        this.isOpening = false;
        this.hide();
        this.fireEvent('close', [this, value]);
    },

    changeText: function (lang) {
    	this.parent();
    	if ($defined(this.ok)) {
    		this.ok.setLabel({set:'Jx',key:'confirm',value:'affirmativeLabel'});
    	}
    	if ($defined(this.cancel)) {
    		this.cancel.setLabel({set:'Jx',key:'confirm',value:'negativeLabel'});
    	}
      if(Jx.type(this.options.question) === 'object') {
        this.question.set('html', this.getText(this.options.question))
      }
    }

});/*
---

name: Jx.Tooltip

description: These are very simple tooltips that are designed to be instantiated in javascript and directly attached to the object that they are the tip for.

license: MIT-style license.

requires:
 - Jx.Widget

provides: [Jx.Tooltip]

css:
 - tooltip

...
 */
// $Id: tooltip.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Tooltip
 *
 * Extends: <Jx.Widget>
 *
 * An implementation of tooltips. These are very simple tooltips that are
 * designed to be instantiated in javascript and directly attached to the
 * object that they are the tip for. We can only have one Tip per element so
 * we use element storage to store the tip object and check for it's presence
 * before creating a new tip. If one is there we remove it and create this new
 * one.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Tooltip = new Class({
    Family: 'Jx.Widget',
    Extends : Jx.Widget,
    Binds: ['enter', 'leave', 'move'],
    options : {
        /**
         * Option: offsets
         * An object with x and y components for where to put the tip related
         * to the mouse cursor.
         */
        offsets : {
            x : 15,
            y : 15
        },
        /**
         * Option: showDelay
         * The amount of time to delay before showing the tip. This ensures we
         * don't show a tip if we're just passing over an element quickly.
         */
        showDelay : 100,
        /**
         * Option: cssClass
         * a class to be added to the tip's container. This can be used to
         * style the tip.
         */
        cssClass : null
    },

    /**
     * Parameters:
     * target - The DOM element that triggers the toltip when moused over.
     * tip - The contents of the tip itself. This can be either a string or
     *       an Element.
     * options - <Jx.Tooltip.Options> and <Jx.Widget.Options>
     */
    parameters: ['target','tip','options'],

    /**
     * Method: render
     * Creates the tooltip
     *
     */
    render : function () {
        this.parent();
        this.target = document.id(this.options.target);

        var t = this.target.retrieve('Tip');
        if (t) {
            this.target.eliminate('Tip');
        }

        //set up the tip options
        this.domObj = new Element('div', {
            styles : {
                'position' : 'absolute',
                'top' : 0,
                'left' : 0,
                'visibility' : 'hidden'
            }
        }).inject(document.body);

        if (Jx.type(this.options.tip) === 'string' || Jx.type(this.options.tip) == 'object') {
            this.domObj.set('html', this.getText(this.options.tip));
        } else {
            this.domObj.grab(this.options.tip);
        }

        this.domObj.addClass('jxTooltip');
        if ($defined(this.options.cssClass)) {
            this.domObj.addClass(this.options.cssClass);
        }

        this.options.target.store('Tip', this);

        //add events
        this.options.target.addEvent('mouseenter', this.enter);
        this.options.target.addEvent('mouseleave', this.leave);
        this.options.target.addEvent('mousemove', this.move);
    },

    /**
     * Method: enter
     * Method run when the cursor passes over an element with a tip
     *
     * Parameters:
     * event - the event object
     */
    enter : function (event) {
        this.timer = $clear(this.timer);
        this.timer = (function () {
            this.domObj.setStyle('visibility', 'visible');
            this.position(event);
        }).delay(this.options.delay, this);
    },
    /**
     * Method: leave
     * Executed when the mouse moves out of an element with a tip
     *
     * Parameters:
     * event - the event object
     */
    leave : function (event) {
        this.timer = $clear(this.timer);
        this.timer = (function () {
            this.domObj.setStyle('visibility', 'hidden');
        }).delay(this.options.delay, this);
    },
    /**
     * Method: move
     * Called when the mouse moves over an element with a tip.
     *
     * Parameters:
     * event - the event object
     */
    move : function (event) {
        this.position(event);
    },
    /**
     * Method: position
     * Called to position the tooltip.
     *
     * Parameters:
     * event - the event object
     */
    position : function (event) {
        var size = window.getSize(), scroll = window.getScroll();
        var tipSize = this.domObj.getMarginBoxSize();
        var tip = {
            x : this.domObj.offsetWidth,
            y : this.domObj.offsetHeight
        };
        var tipPlacement = {
            x: event.page.x + this.options.offsets.x,
            y: event.page.y + this.options.offsets.y
        };

        if (event.page.y + this.options.offsets.y + tip.y + tipSize.height - scroll.y > size.y) {
            tipPlacement.y = event.page.y - this.options.offsets.y - tipSize.height - scroll.y;
        }

        if (event.page.x + this.options.offsets.x + tip.x + tipSize.width - scroll.x > size.x) {
            tipPlacement.x = event.page.x - this.options.offsets.x - tipSize.width - scroll.x;
        }

        this.domObj.setStyle('top', tipPlacement.y);
        this.domObj.setStyle('left', tipPlacement.x);
    },
    /**
     * APIMethod: detach
     * Called to manually remove a tooltip.
     */
    detach : function () {
        this.target.eliminate('Tip');
        this.destroy();
    }
});
/*
---

name: Jx.Fieldset

description: Used to create fieldsets in Forms

license: MIT-style license.

requires:
 - Jx.Widget

provides: [Jx.Fieldset]

...
 */
// $Id: fieldset.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Fieldset
 *
 * Extends: <Jx.Widget>
 *
 * This class represents a fieldset. It can be used to group fields together.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 *
 */
Jx.Fieldset = new Class({
    Family: 'Jx.Fieldset',
    Extends : Jx.Widget,

    options : {
        /**
         * Option: legend
         * The text for the legend of a fieldset. Default is null
         * or no legend.
         */
        legend : null,
        /**
         * Option: id
         * The id to assign to this element
         */
        id : null,
        /**
         * Option: fieldsetClass
         * A CSS class to assign to the fieldset. Useful for custom styling of
         * the element
         */
        fieldsetClass : null,
        /**
         * Option: legendClass
         * A CSS class to assign to the legend. Useful for custom styling of
         * the element
         */
        legendClass : null,
        /**
         * Option: template
         * a template for how this element should be rendered
         */
        template : '<fieldset class="jxFieldset"><legend><span class="jxFieldsetLegend"></span></legend></fieldset>',
        /**
         * Option: form
         * The <Jx.Form> that this fieldset should be added to
         */
        form : null
    },

    classes: new Hash({
        domObj: 'jxFieldset',
        legend: 'jxFieldsetLegend'
    }),

    /**
     * Property: legend
     * a holder for the legend Element
     */
    legend : null,

    /**
     * APIMethod: render
     * Creates a fieldset.
     */
    render : function () {
        this.parent();

        this.id = this.options.id;

        if ($defined(this.options.form)
                && this.options.form instanceof Jx.Form) {
            this.form = this.options.form;
        }

        //FIELDSET
        if (this.domObj) {
            if ($defined(this.options.id)) {
                this.domObj.set('id', this.options.id);
            }
            if ($defined(this.options.fieldsetClass)) {
                this.domObj.addClass(this.options.fieldsetClass);
            }
        }

        if (this.legend) {
            if ($defined(this.options.legend)) {
                this.legend.set('html', this.getText(this.options.legend));
                if ($defined(this.options.legendClass)) {
                    this.legend.addClass(this.options.legendClass);
                }
            } else {
                this.legend.destroy();
            }
        }
    },
    /**
     * APIMethod: add
     * Adds fields to this fieldset
     *
     * Parameters:
     * pass as many fields to this method as you like. They should be
     * <Jx.Field> objects
     */
    add : function () {
        var field;
        for (var x = 0; x < arguments.length; x++) {
            field = arguments[x];
            //add form to the field and field to the form if not already there
            if ($defined(field.jxFamily) && !$defined(field.form) && $defined(this.form)) {
                field.form = this.form;
                this.form.addField(field);
            }
            this.domObj.grab(field);
        }
        return this;
    },

    /**
     * APIMethod: addTo
     *
     */
    addTo: function(what) {
        if (what instanceof Jx.Form) {
            this.form = what;
        } else if (what instanceof Jx.Fieldset) {
            this.form = what.form;
        }
        return this.parent(what);
    }

});
/*
---

name: Jx.Form

description: Represents a HTML Form

license: MIT-style license.

requires:
 - Jx.Widget
 - More/String.QueryString
 - More/Form.Validator

provides: [Jx.Form]

css:
 - form

...
 */
// $Id: form.js 1010 2011-01-04 00:09:39Z jonlb@comcast.net $
/**
 * Class: Jx.Form
 *
 * Extends: <Jx.Widget>
 *
 * A class that represents an HTML form. You add fields using either
 * Jx.Form.add() or by using the field's .addTo() method. You can get all form
 * values or set them using this class. It also handles validation of fields
 * through the use of a plugin (Jx.Plugin.Form.Validator).
 *
 * Jx.Form has the ability to submit itself via normal HTTP submit as well as
 * via AJAX. To submit normally you simply call the submit() function. To submit by
 * AJAX, call ajaxSubmit().  If the form contains Jx.Field.File instances it will
 * either submit all of the files individually and then the data, or it will submit
 * data with the last File instance it finds. This behavior is dependant on the
 * uploadFilesFirst option (which defaults to false).
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Form = new Class({
    Family: 'Jx.Form',
    Extends: Jx.Widget,

    options: {
        /**
         * Option: method
         * the method used to submit the form
         */
        method: 'post',
        /**
         * Option: action
         * where to submit it to
         */
        action: '',
        /**
         * Option: fileUpload
         * whether this form handles file uploads or not.
         */
        fileUpload: false,
        /**
         * Option: formClass
         */
        formClass: null,
        /**
         * Option: name
         * the name property for the form
         */
        name: '',
        /**
         * Option: acceptCharset
         * the character encoding to be used. Defaults to utf-8.
         */
        acceptCharset: 'utf-8',
        /**
         * Option: uploadFilesFirst
         * Whether to upload all of the files in the form before
         * uploading the rest of the form. If set to false the form will
         * upload the data with the last file that it finds,
         */
        uploadFilesFirst: false,

        template: '<form class="jxForm"></form>'
    },

    /**
     * Property: defaultAction
     * the default field to activate if the user hits the enter key in this
     * form.  Set by specifying default: true as an option to a field.  Will
     * only work if the default is a Jx button field or an input of a type
     * that is a button
     */
    defaultAction: null,

    /**
     * Property: fields
     * An array of all of the single fields (not contained in a fieldset) for
     * this form
     */
    fields : null,
    /**
     * Property: pluginNamespace
     * required variable for plugins
     */
    pluginNamespace: 'Form',

    classes: $H({
        domObj: 'jxForm'
    }),

    init: function() {
      this.parent();
      this.fields = new Hash();
      this.data = {};
    },
    /**
     * APIMethod: render
     * Constructs the form but does not add it to anything to be shown. The
     * caller should use form.addTo() to add the form to the DOM.
     */
    render : function () {
        this.parent();
        //create the form first
        this.domObj.set({
            'method' : this.options.method,
            'action' : this.options.action,
            'name' : this.options.name,
            'accept-charset': this.options.acceptCharset,
            events: {
                keypress: function(e) {
                    if (e.key == 'enter' &&
                        e.target.tagName != "TEXTAREA" &&
                        this.defaultAction &&
                        this.defaultAction.click) {
                        document.id(this.defaultAction).focus();
                        this.defaultAction.click();
                        e.stop();
                    }
                }.bind(this)
            }
        });

        if (this.options.fileUpload) {
            this.domObj.set('enctype', 'multipart/form-data');
        }

        if ($defined(this.options.formClass)) {
            this.domObj.addClass(this.options.formClass);
        }
    },

    /**
     * APIMethod: addField
     * Adds a <Jx.Field> subclass to this form's fields hash
     *
     * Parameters:
     * field - <Jx.Field> to add
     */
    addField : function (field) {
        this.fields.set(field.id, field);
        if (field.options.defaultAction) {
            this.defaultAction = field;
        }
    },

    /**
     * Method: isValid
     * Determines if the form passes validation
     *
     * Parameters:
     * evt - the MooTools event object
     */
    isValid : function (evt) {
        return true;
    },

    /**
     * APIMethod: getValues
     * Gets the values of all the fields in the form as a Hash object. This
     * uses the mootools function Element.toQueryString to get the values and
     * will either return the values as a querystring or as an object (using
     * mootools-more's String.parseQueryString method).
     *
     * Parameters:
     * asQueryString - {boolean} indicates whether to return the value as a
     *                  query string or an object.
     */
    getValues : function (asQueryString) {
        var queryString = this.domObj.toQueryString();
        if ($defined(asQueryString) && asQueryString) {
            return queryString;
        } else {
            return queryString.parseQueryString();
        }
    },
    /**
     * APIMethod: setValues
     * Used to set values on the form
     *
     * Parameters:
     * values - A Hash of values to set keyed by field name.
     */
    setValues : function (values) {
        if (Jx.type(values) === 'object') {
            values = new Hash(values);
        }
        this.fields.each(function (item) {
            item.setValue(values.get(item.name));
        }, this);
    },

    /**
     * APIMethod: add
     *
     * Parameters:
     * Pass as many parameters as you like. However, they should all be
     * <Jx.Field> objects.
     */
    add : function () {
        var field;
        for (var x = 0; x < arguments.length; x++) {
            field = arguments[x];
            //add form to the field and field to the form if not already there
            if (field instanceof Jx.Field && !$defined(field.form)) {
                field.form = this;
                this.addField(field);
            } else if (field instanceof Jx.Fieldset && !$defined(field.form)) {
                field.form = this;
            }

            this.domObj.grab(field);
        }
        return this;
    },

    /**
     * APIMethod: reset
     * Resets all fields back to their original value
     */
    reset : function () {
        this.fields.each(function (field, name) {
            field.reset();
        }, this);
        this.fireEvent('reset',this);
    },
    /**
     * APIMethod: getFieldsByName
     * Allows retrieving a field from a form by the name of the field (NOT the
     * ID).
     *
     * Parameters:
     * name - {string} the name of the field to find
     */
    getFieldsByName: function (name) {
        var fields = [];
        this.fields.each(function(val, id){
            if (val.name === name) {
                fields.push(val);
            }
        },this);
        return fields;
    },
    /**
     * APIMethod: getField
     * Returns a Jx.Field object by its ID.
     *
     * Parameters:
     * id - {string} the id of the field to find.
     */
    getField: function (id) {
        if (this.fields.has(id)) {
            return this.fields.get(id);
        }
        return null;
    },
    /**
     * APIMethod: setBusy
     * Sets the busy state of the Form and all of it's fields.
     *
     * Parameters:
     * state - {boolean} indicated whether the form is busy or not.
     */
    setBusy: function(state) {
      if (this.busy == state) {
        return;
      }
      this.parent(state);
      this.fields.each(function(field) {
        field.setBusy(state, true);
      });
    },

    submit: function() {
        //are there any files in this form?
        var opts = this.options;
        if (opts.fileUpload) {
            //grab all of the files and pull them into the main domObj
            var files = this.findFiles();
            files.each(function(file){
                var inputs = file.getFileInputs();
                if (inputs.length > 1) {
                    //we need to make these an array...
                    inputs.each(function(input){
                        input.set('name',input.get('name') + '[]');
                    },this);
                }
                file.destroy();
                this.domObj.adopt(inputs);
            },this);
        }
        this.domObj.submit();
    },

    ajaxSubmit: function() {
        var opts = this.options;
        if (opts.fileUpload) {
            var files = this.findFiles();
            this.files = files.length;
            this.completed = 0;
            files.each(function(file, index){
                file.addEvent('onFileUploadComplete',this.fileUploadComplete.bind(this));
                if (index==(this.files - 1) && !opts.uploadFilesFirst) {
                    file.upload(this);
                } else {
                    file.upload();
                }
            },this);
        } else {
            this.submitForm();
        }
    },

    submitForm: function() {
        //otherwise if no file field(s) present, just get the values and
        //submit to the action via the method
        var data = this.getValues();
        var req = new Request.JSON({
            url: this.action,
            method: this.method,
            data: data,
            urlEncoded: true,
            onSuccess: function(responseJSON, responseText) {
                this.fileUploadComplete(responseJSON, true);
            }.bind(this)
        });
        req.send();
    },

    findFiles: function() {
        var files = [];
        this.fields.each(function(field){
            if (field instanceof Jx.Field.File) {
                files.push(field);
            }
        },this);
        return files;
    },

    fileUploadComplete: function(data){
        this.completed++;
        $each(data,function(value,key){
            this.data[key] = value;
        },this);
        if (this.completed == this.files && this.options.uploadFilesFirst) {
            this.submitForm();
        } else {
            this.fireEvent('formSubmitComplete',[this.data]);
        }
    }

});
/*
---

name: Jx.Field

description: Base class for all inputs

license: MIT-style license.

requires:
 - Jx.Fieldset
 - Jx.Form

provides: [Jx.Field]


...
 */
// $Id: field.js 969 2010-08-20 12:14:54Z pagameba $
/**
 * Class: Jx.Field
 *
 * Extends: <Jx.Widget>
 *
 * This class is the base class for all form fields.
 *
 *
 * Example:
 * (code)
 * (end)
 *
 * MooTools.lang Keys:
 * - field.requiredText
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Field = new Class({
    Family: 'Jx.Field',
    Extends : Jx.Widget,
    pluginNamespace: 'Field',
    Binds: ['changeText'],

    options : {
        /**
         * Option: id
         * The ID assigned to the container of the Jx.Field element, this is
         * not the id of the input element (which is internally computed to be
         * unique)
         */
        id : null,
        /**
         * Option: name
         * The name of the field (used when submitting to the server). Will also be used for the
         * name attribute of the field.
         */
        name : null,
        /**
         * Option: label
         * The text that goes next to the field.
         */
        label : null,
        /**
         * Option: labelSeparator
         * A character to use as the separator between the label and the input.
         * Make it an empty string for no separator.
         */
        labelSeparator : ":",
        /**
         * Option: value
         * A default value to populate the field with.
         */
        value : null,
        /**
         * Option: tag
         * a string to use as the HTML of the tag element (default is a
         * <span> element).
         */
        tag : null,
        /**
         * Option: tip
         * A string that will eventually serve as a tooltip for an input field.
         * Currently only implemented as OverText for text fields.
         */
        tip : null,
        /**
         * Option: template
         * A string holding the template for the field.
         */
        template : null,
        /**
         * Option: containerClass
         * a CSS class that will be added to the containing element.
         */
        containerClass : null,
        /**
         * Option: labelClass
         * a CSS to add to the label
         */
        labelClass : null,
        /**
         * Option: fieldClass
         * a CSS class to add to the input field
         */
        fieldClass : null,
        /**
         * Option: tagClass
         * a CSS class to add to the tag field
         */
        tagClass : null,
        /**
         * Option: required
         * Whether the field is required. Setting this to true will trigger
         * the addition of a "required" validator class and the form
         * will not submit until it is filled in and validates provided
         * that the plugin Jx.Plugin.Field.Validator has been added to this
         * field.
         */
        required : false,
        /**
         * Option: readonly
         * {True|False} defaults to false. Whether this field is readonly.
         */
        readonly : false,
        /**
         * Option: disabled
         * {True|False} defaults to false. Whether this field is disabled.
         */
        disabled : false,
        /**
         * Option: defaultAction
         * {Boolean} defaults to false, if true and this field is a button
         * of some kind (Jx.Button, a button or an input of type submit) then
         * if the user hits the enter key on any field in the form except a
         * textarea, this field will be activated as if clicked
         */
        defaultAction: false
    },

    /**
     * Property: overtextOptions
     * The default options Jx uses for mootools-more's OverText
     * plugin
     */
    overtextOptions : {
        element : 'label'
    },

    /**
     * Property: field
     * An element representing the input field itself.
     */
    field : null,
    /**
     * Property: label
     * A reference to the label element for this field
     */
    label : null,
    /**
     * Property: tag
     * A reference to the "tag" field of this input if available
     */
    tag : null,
    /**
     * Property: id
     * A computed, unique id attached to the input element of this field.
     */
    id : null,
    /**
     * Property: overText
     * The overText instance for this field.
     */
    overText : null,
    /**
     * Property: type
     * Indicates that this is a field type
     */
    type : 'field',
    /**
     * Property: classes
     * The classes to search for in the template. Not
     * required, but we look for them.
     */
    classes : new Hash({
        domObj: 'jxInputContainer',
        label: 'jxInputLabel',
        tag: 'jxInputTag'
    }),

    /**
     * APIMethod: render
     */
    render : function () {
        this.classes.set('field', 'jxInput'+this.type);
        var name = $defined(this.options.name) ? this.options.name : '';
        this.options.template = this.options.template.substitute({name:name});
        this.parent();

        this.id = this.generateId();
        this.name = this.options.name;

        if ($defined(this.type)) {
            this.domObj.addClass('jxInputContainer'+this.type);
        }

        if ($defined(this.options.containerClass)) {
            this.domObj.addClass(this.options.containerClass);
        }
        if ($defined(this.options.required) && this.options.required) {
            this.domObj.addClass('jxFieldRequired');
            if ($defined(this.options.validatorClasses)) {
                this.options.validatorClasses = 'required ' + this.options.validatorClasses;
            } else {
                this.options.validatorClasses = 'required';
            }
        }


        // FIELD
        if (this.field) {
            if ($defined(this.options.fieldClass)) {
                this.field.addClass(this.options.fieldClass);
            }

            if ($defined(this.options.value)) {
                this.field.set('value', this.options.value);
            }

            this.field.set('id', this.id);

            if ($defined(this.options.readonly)
                    && this.options.readonly) {
                this.field.set("readonly", "readonly");
                this.field.addClass('jxFieldReadonly');
            }

            if ($defined(this.options.disabled)
                    && this.options.disabled) {
                this.field.set("disabled", "disabled");
                this.field.addClass('jxFieldDisabled');
            }

            //add events
            this.field.addEvents({
              'focus': this.onFocus.bind(this),
              'blur': this.onBlur.bind(this),
              'change': this.onChange.bind(this)
            });

            this.field.store('field', this);

            // add click event to label to set the focus to the field
            // COMMENT: tried it without a function using addEvent('click', this.field.focus.bind(this)) but crashed in IE
            if(this.label) {
              this.label.addEvent('click', function() {
                this.field.focus();
              }.bind(this));
            }
        }
        // LABEL
        if (this.label) {
            if ($defined(this.options.labelClass)) {
                this.label.addClass(this.options.labelClass);
            }
            if ($defined(this.options.label)) {
                this.label.set('html', this.getText(this.options.label)
                        + this.options.labelSeparator);
            }

            this.label.set('for', this.id);

            if (this.options.required) {
                this.requiredText = new Element('em', {
                    'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
                    'class' : 'required'
                });
                this.requiredText.inject(this.label);
            }

        }

        // TAG
        if (this.tag) {
            if ($defined(this.options.tagClass)) {
                this.tag.addClass(this.options.tagClass);
            }
            if ($defined(this.options.tag)) {
                this.tag.set('html', this.options.tag);
            }
        }

        if ($defined(this.options.form)
                && this.options.form instanceof Jx.Form) {
            this.form = this.options.form;
            this.form.addField(this);
        }

    },
    /**
     * APIMethod: setValue
     * Sets the value property of the field
     *
     * Parameters:
     * v - The value to set the field to.
     */
    setValue : function (v) {
        if (!this.options.readonly) {
            this.field.set('value', v);
        }
    },

    /**
     * APIMethod: getValue
     * Returns the current value of the field.
     */
    getValue : function () {
        return this.field.get("value");
    },

    /**
     * APIMethod: reset
     * Sets the field back to the value passed in the
     * original options
     */
    reset : function () {
        this.setValue(this.options.value);
        this.fireEvent('reset', this);
    },
    /**
     * APIMethod: disable
     * Disabled the field
     */
    disable : function () {
        this.options.disabled = true;
        this.field.set("disabled", "disabled");
        this.field.addClass('jxFieldDisabled');
    },
    /**
     * APIMethod: enable
     * Enables the field
     */
    enable : function () {
        this.options.disabled = false;
        this.field.erase("disabled");
        this.field.removeClass('jxFieldDisabled');
    },

    /**
     * APIMethod: addTo
     * Overrides default Jx.Widget AddTo() so that we can call .add() if
     * adding to a Jx.Form or Jx.Fieldset object.
     *
     * Parameters:
     * what - the element or object to add this field to.
     * where - where in the object to place it. Not valid if adding to Jx.Form
     *      or Jx.Fieldset.
     */
    addTo: function(what, where) {
        if (what instanceof Jx.Fieldset || what instanceof Jx.Form) {
            what.add(this);
        } else {
            this.parent(what, where);
        }
        return this;
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     *    translations changed.
     */
    changeText: function (lang) {
        this.parent();
        if ($defined(this.options.label) && this.label) {
          this.label.set('html', this.getText(this.options.label) + this.options.labelSeparator);
        }
        if(this.options.required) {
          this.requiredText = new Element('em', {
              'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
              'class' : 'required'
          });
          this.requiredText.inject(this.label);
        }
        if ($defined(this.requiredText)) {
          this.requiredText.set('html',this.getText({set:'Jx',key:'field',value:'requiredText'}));
        }
    },

    onFocus: function() {
      this.fireEvent('focus', this);
    },

    onBlur: function () {
      this.fireEvent('blur',this);
    },

    onChange: function () {
      this.fireEvent('change', this);
    },

    setBusy: function(state, withoutMask) {
      if (!withoutMask) {
        this.parent(state);
      }
      this.field.set('readonly', state || this.options.readonly);
    }

});
/*
---

name: Jx.Field.Text

description: Represents a text input

license: MIT-style license.

requires:
 - Jx.Field

optional:
 - More/OverText

provides: [Jx.Field.Text]

...
 */
// $Id: text.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Field.Text
 *
 * Extends: <Jx.Field>
 *
 * This class represents a text input field.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Field.Text = new Class({

    Extends: Jx.Field,

    options: {
        /**
         * Option: overText
         * an object holding options for mootools-more's OverText class. Leave it null to
         * not enable it, make it an object to enable.
         */
        overText: null,
        /**
         * Option: template
         * The template used to render this field
         */
        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><input class="jxInputText" type="text" name="{name}"/><span class="jxInputTag"></span></span>'
    },
    /**
     * Property: type
     * The type of this field
     */
    type: 'Text',

    /**
     * APIMethod: render
     * Creates a text input field.
     */
    render: function () {
        this.parent();

        //create the overText instance if needed
        if ($defined(this.options.overText)) {
            var opts = $extend({}, this.options.overText);
            this.field.set('alt', this.options.tip);
            this.overText = new OverText(this.field, opts);
            this.overText.show();
        }

    }

});/*
---

name: Jx.Dialog.Prompt

description: A subclass of Jx.dialog for prompting the user for text input.

license: MIT-style license.

requires:
 - Jx.Dialog
 - Jx.Button
 - Jx.Toolbar.Item
 - Jx.Field.Text

provides: [Jx.Dialog.Prompt]

...
 */
// $Id: prompt.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Dialog.Prompt
 *
 * Extends: <Jx.Dialog>
 *
 * Jx.Dialog.Prompt is an extension of Jx.Dialog that allows the developer
 * to display a message to the user and ask for a text response.
 *
 * MooTools.lang Keys:
 * - prompt.okButton
 * - prompt.cancelButton
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner
 *
 * This file is licensed under an MIT style license
 */
Jx.Dialog.Prompt = new Class({

    Extends: Jx.Dialog,

    options: {
        /**
         * Option: prompt
         * The message to display to the user
         */
        prompt: '',
        /**
         * Option: startingValue
         * The startingvalue to place in the input field
         */
        startingValue: '',
        /**
         * Option: fieldOptions,
         * Object with various
         */
        fieldOptions: {
          type : 'Text',
          options: {},
          validate : true,
          validatorOptions: {
            validators: ['required'],
            validateOnBlur: true,
            validateOnChange : false
          },
          showErrorMsg : true
        },
        /**
         * Jx.Dialog option defaults
         */
        width: 400,
        height: 200,
        close: true,
        resize: true,
        collapse: false,
        useKeyboard : true,
        keys : {
          'esc'   : 'cancel',
          'enter' : 'ok'
        }
    },
    /**
     * APIMethod: render
     * constructs the dialog.
     */
    render: function () {
        //create content to be added
        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
        this.ok = new Jx.Button({
                label: this.getText({set:'Jx',key:'prompt',value:'okButton'}),
                onClick: this.onClick.bind(this, true)
            });
        this.cancel = new Jx.Button({
                label: this.getText({set:'Jx',key:'prompt',value:'cancelButton'}),
                onClick: this.onClick.bind(this, false)
            });
        this.buttons.add(this.ok, this.cancel);
        this.options.toolbars = [this.buttons];

        var fOpts = this.options.fieldOptions;
            fOpts.options.label = this.getText(this.options.prompt);
            fOpts.options.value = this.options.startingValue;
            fOpts.options.containerClass = 'jxPrompt';

        if(Jx.type(fOpts.type) === 'string' && $defined(Jx.Field[fOpts.type.capitalize()])) {
          this.field = new Jx.Field[fOpts.type.capitalize()](fOpts.options);
        }else if(Jx.type(fOpts.type) === 'Jx.Object'){
          this.field = fOpts.type;
        }else{
          // warning and fallback?
          window.console ? console.warn("Field type does not exist %o, using Jx.Field.Text", fOpts.type) : false;
          this.field = new Jx.Field.Text(fOpts.options);
        }

        if(this.options.fieldOptions.validate) {
          this.validator = new Jx.Plugin.Field.Validator(this.options.fieldOptions.validatorOptions);
          this.validator.attach(this.field);
        }

        this.options.content = document.id(this.field);

        if(this.options.useKeyboard) {
          var self = this;
          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
        }
        this.parent();
        if(this.options.useKeyboard) {
          this.keyboard.addEvents(this.getKeyboardEvents());
        }
    },
    /**
     * Method: onClick
     * Called when the OK button is clicked. Closes the dialog.
     */
    onClick: function (value) {
        if(value && $defined(this.validator)) {
          if(this.validator.isValid()) {
            this.isOpening = false;
            this.hide();
            this.fireEvent('close', [this, value, this.field.getValue()]);
          }else{
            //this.options.content.adopt(this.validator.getError());
            this.field.field.focus.delay(50, this.field.field);
            //todo: show error messages ?
          }
        }else{
          this.isOpening = false;
          this.hide();
          this.fireEvent('close', [this, value, this.field.getValue()]);
        }
    },

    changeText: function (lang) {
    	this.parent();
    	if ($defined(this.ok)) {
    		this.ok.setLabel({set:'Jx',key:'prompt',value:'okButton'});
    	}
    	if ($defined(this.cancel)) {
    		this.cancel.setLabel({set:'Jx',key:'prompt',value:'cancelButton'});
    	}
      this.field.label.set('html', this.getText(this.options.prompt));
    }


});
/*
---

name: Jx.Panel.DataView

description: A panel used for displaying records from a store in a list-style interface rather than a grid.

license: MIT-style license.

requires:
 - Jx.Panel
 - Jx.Store
 - Jx.List

provides: [Jx.Panel.DataView]

...
 */
// $Id: dataview.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Panel.DataView
 *
 * Extends: <Jx.Panel>
 *
 * This panel extension takes a standard Jx.Store (or subclass) and displays
 * each record as an item using a provided template. It sorts the store as requested
 * before doing so. The class only creates the HTML and has no default CSS display. All
 * styling must be done by the developer using the control.
 *
 *
 * Events:
 * renderDone - fires when the panel completes creating all of the items.
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Panel.DataView = new Class({

    Extends: Jx.Panel,

    options: {
        /**
         * Option: data
         * The store containing the data
         */
        data: null,
        /**
         * Option: sortColumns
         * An array of columns to sort the store by.
         */
        sortColumns: null,
        /**
         * Option: itemTemplate
         * The template to use in rendering records
         */
        itemTemplate: null,
        /**
         * Option: emptyTemplate
         * the template that is displayed when there are no records in the
         * store.
         */
        emptyTemplate: null,
        /**
         * Option: containerClass
         * The class added to the container. It can be used to target the items
         * in the panel.
         */
        containerClass: null,
        /**
         * Option: itemClass
         * The class to add to each item. Used for styling purposes
         */
        itemClass: null,
        /**
         * Option: itemOptions
         * Options to pass to the list object
         */
        listOptions: {
            select: true,
            hover: true
        }
    },

    init: function () {
        this.domA = new Element('div');
        this.list = this.createList(this.domA, this.options.listOptions);
        this.parent();
    },
    /**
     * APIMethod: render
     * Renders the dataview. If the store already has data loaded it will be rendered
     * at the end of the method.
     */
    render: function () {
        if (!$defined(this.options.data)) {
            //we can't do anything without data
            return;
        }

        this.options.content = this.domA;

        //pass to parent
        this.parent();

        this.domA.addClass(this.options.containerClass);

        //parse templates so we know what values are needed in each
        this.itemCols = this.parseTemplate(this.options.itemTemplate);

        this.bound.update = this.update.bind(this);
        //listen for data updates
        this.options.data.addEvent('storeDataLoaded', this.bound.update);
        this.options.data.addEvent('storeSortFinished', this.bound.update);
        this.options.data.addEvent('storeDataLoadFailed', this.bound.update);

        if (this.options.data.loaded) {
            this.update();
        }

    },

    /**
     * Method: draw
     * begins the process of creating the items
     */
    draw: function () {
        var n = this.options.data.count();
        if ($defined(n) && n > 0) {
            for (var i = 0; i < n; i++) {
                this.options.data.moveTo(i);

                var item = this.createItem();
                this.list.add(item);
            }
        } else {
            var empty = new Element('div', {html: this.options.emptyTemplate});
            this.list.add(item);
        }
        this.fireEvent('renderDone', this);
    },
    /**
     * Method: createItem
     * Actually does the work of getting the data from the store
     * and creating a single item based on the provided template
     */
    createItem: function () {
        //create the item
        var itemObj = {};
        this.itemCols.each(function (col) {
            itemObj[col] = this.options.data.get(col);
        }, this);
        var itemTemp = this.options.itemTemplate.substitute(itemObj);
        var item = new Element('div', {
            'class': this.options.itemClass,
            html: itemTemp
        });
        return item;
    },
    /**
     * APIMethod: update
     * This method begins the process of creating the items. It is called when
     * the store is loaded or can be called to manually recreate the view.
     */
    update: function () {
        if (!this.updating) {
            this.updating = true;
            this.list.empty();
            this.options.data.sort(this.options.sortColumns);
            this.draw();
            this.updating = false;
        }
    },
    /**
     * Method: parseTemplate
     * parses the provided template to determine which store columns are
     * required to complete it.
     *
     * Parameters:
     * template - the template to parse
     */
    parseTemplate: function (template) {
        //we parse the template based on the columns in the data store looking
        //for the pattern {column-name}. If it's in there we add it to the
        //array of ones to look for
        var columns = this.options.data.getColumns();
        var arr = [];
        columns.each(function (col) {
            var s = '{' + col.name + '}';
            if (template.contains(s)) {
                arr.push(col.name);
            }
        }, this);
        return arr;
    },
    /**
     * Method: enterItem
     * Fires mouseenter event
     *
     * Parameters:
     * item - the item that is the target of the event
     * list - the list this item is in.
     */
    enterItem: function(item, list){
        this.fireEvent('mouseenter', item, list);
    },
    /**
     * Method: leaveItem
     * Fires mouseleave event
     *
     * Parameters:
     * item - the item that is the target of the event
     * list - the list this item is in.
     */
    leaveItem: function(item, list){
        this.fireEvent('mouseleave', item, list);
    },
    /**
     * Method: selectItem
     * Fires select event
     *
     * Parameters:
     * item - the item that is the target of the event
     * list - the list this item is in.
     */
    selectItem: function(item, list){
        this.fireEvent('select', item, list);
    },
    /**
     * Method: unselectItem
     * Fires unselect event
     *
     * Parameters:
     * item - the item that is the target of the event
     * list - the list this item is in.
     */
    unselectItem: function(item, list){
        this.fireEvent('unselect', item, list);
    },
    /**
     * Method: addItem
     * Fires add event
     *
     * Parameters:
     * item - the item that is the target of the event
     * list - the list this item is in.
     */
    addItem: function(item, list) {
        this.fireEvent('add', item, list);
    },
    /**
     * Method: removeItem
     * Fires remove event
     *
     * Parameters:
     * item - the item that is the target of the event
     * list - the list this item is in.
     */
    removeItem: function(item, list) {
        this.fireEvent('remove', item, list);
    },
    /**
     * Method: createList
     * Creates the list object
     *
     * Parameters:
     * container - the container to use in the list
     * options - the options for the list
     */
    createList: function(container, options){
        return new Jx.List(container, $extend({
            onMouseenter: this.enterItem.bind(this),
            onMouseleave: this.leaveItem.bind(this),
            onSelect:  this.selectItem.bind(this),
            onAdd: this.addItem.bind(this),
            onRemove: this.removeItem.bind(this),
            onUnselect: this.unselectItem.bind(this)
        }, options));
    }
});
/*
---

name: Jx.Panel.DataView.Group

description: A subclass of Dataview that can display records in groups.

license: MIT-style license.

requires:
 - Jx.Panel.DataView
 - Jx.Selection

provides: [Jx.Panel.DataView.Group]

...
 */
// $Id: group.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Panel.DataView.Group
 *
 * Extends: <Jx.Panel.DataView>
 *
 * This extension of Jx.Panel.DataView that provides for grouping the items
 * by a particular column.
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Panel.DataView.Group = new Class({

    Extends: Jx.Panel.DataView,

    options: {
        /**
         * Option: groupTemplate
         * The template used to render the group heading
         */
        groupTemplate: null,
        /**
         * Option: groupContainerClass
         * The class added to the group container. All of the items and header
         * for a single grouping is contained by a div that has this class added.
         */
        groupContainerClass: null,
        /**
         * Option: groupHeaderClass
         * The class added to the heading. Used for styling.
         */
        groupHeaderClass: null,
        /**
         * Option: listOption
         * Options to pass to the main list
         */
        listOptions: {
            select: false,
            hover: false
        },
        /**
         * Option: itemOption
         * Options to pass to the item lists
         */
        itemOptions: {
            select: true,
            hover: true,
            hoverClass: 'jxItemHover',
            selectClass: 'jxItemSelect'
        }
    },

    init: function() {
        this.groupCols = this.parseTemplate(this.options.groupTemplate);
        this.itemManager = new Jx.Selection({
            eventToFire: {
                select: 'itemselect',
                unselect: 'itemunselect'
            },
            selectClass: 'jxItemSelected'
        });
        this.groupManager = new Jx.Selection({
            eventToFire: {
                select: 'groupselect',
                unselect: 'groupunselect'
            },
            selectClass: 'jxGroupSelected'
        });
        this.parent();

    },
    /**
     * APIMethod: render
     * sets up the list container and calls the parent class' render function.
     */
    render: function () {
        this.list = this.createList(this.domA, this.listOptions, this.groupManager);
        this.parent();

    },
    /**
     * Method: draw
     * actually does the work of creating the view
     */
    draw: function () {
        var d = this.options.data;
        var n = d.count();

        if ($defined(n) && n > 0) {
            var currentGroup = '';
            var itemList = null;

            for (var i = 0; i < n; i++) {
                d.moveTo(i);
                var group = d.get(this.options.sortColumns[0]);

                if (group !== currentGroup) {
                    //we have a new grouping

                    //group container
                    var container =  new Element('div', {
                        'class': this.options.groupContainerClass
                    });
                    var l = this.createList(container,{
                        select: false,
                        hover: false
                    });
                    this.list.add(l.container);

                    //group header
                    currentGroup = group;
                    var obj = {};
                    this.groupCols.each(function (col) {
                        obj[col] = d.get(col);
                    }, this);
                    var temp = this.options.groupTemplate.substitute(obj);
                    var g = new Element('div', {
                        'class': this.options.groupHeaderClass,
                        'html': temp,
                        id: 'group-' + group.replace(" ","-","g")
                    });
                    l.add(g);

                    //items container
                    var currentItemContainer = new Element('div', {
                        'class': this.options.containerClass
                    });
                    itemList = this.createList(currentItemContainer, this.options.itemOptions, this.itemManager);
                    l.add(itemList.container);
                }

                var item = this.createItem();
                itemList.add(item);
            }
        } else {
            var empty = new Element('div', {html: this.options.emptyTemplate});
            this.list.add(empty);
        }
        this.fireEvent('renderDone', this);
    },

    /**
     * Method: createList
     * Creates the list object
     *
     * Parameters:
     * container - the container to use in the list
     * options - the options for the list
     * manager - <Jx.Selection> which selection obj to connect to this list
     */
    createList: function(container, options, manager){
        return new Jx.List(container, $extend({
            onMouseenter: this.enterItem.bind(this),
            onMouseleave: this.leaveItem.bind(this),
            onAdd: this.addItem.bind(this),
            onRemove: this.removeItem.bind(this)
        }, options), manager);
    }

});
/*
---

name: Jx.ListItem

description: Represents a single item in a listview.

license: MIT-style license.

requires:
 - Jx.Widget

provides: [Jx.ListItem]

...
 */
// $Id: listitem.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.ListItem
 *
 * Extends: <Jx.Widget>
 *
 * Events:
 *
 * License:
 * Copyright (c) 2009, DM Solutions Group.
 *
 * This file is licensed under an MIT style license
 */
Jx.ListItem = new Class({
    Family: 'Jx.ListItem',
    Extends: Jx.Widget,

    options: {
        enabled: true,
        template: '<li class="jxListItemContainer jxListItem"></li>'
    },

    classes: new Hash({
        domObj: 'jxListItemContainer',
        domContent: 'jxListItem'
    }),

    /**
     * APIMethod: render
     */
    render: function () {
        this.parent();
        this.domContent.store('jxListItem', this);
        this.domObj.store('jxListTarget', this.domContent);
        this.loadContent(this.domContent);
    },

    enable: function(state) {

    }
});/*
---

name: Jx.ListView

description: A widget that displays items in a list format.

license: MIT-style license.

requires:
 - Jx.List
 - Jx.ListItem

provides: [Jx.ListView]

css:
 - list

images:
 - listitem.png
...
 */
// $Id: listview.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.ListView
 *
 * Extends: <Jx.Widget>
 *
 * Events:
 *
 * License:
 * Copyright (c) 2009, DM Solutions Group.
 *
 * This file is licensed under an MIT style license
 */
Jx.ListView = new Class({
    Family: 'Jx.Widget',
    Extends: Jx.Widget,

    pluginNamespace: 'ListView',

    options: {
        template: '<ul class="jxListView jxList"></ul>',
        /**
         * Option: listOptions
         * control the behaviour of the list, see <Jx.List>
         */
        listOptions: {
            hover: true,
            press: true,
            select: true
        }
    },

    classes: new Hash({
        domObj: 'jxListView',
        listObj: 'jxList'
    }),

    /**
     * APIMethod: render
     */
    render: function () {
        this.parent();

        if (this.options.selection) {
            this.selection = this.options.selection;
        } else if (this.options.select) {
            this.selection = new Jx.Selection(this.options);
            this.ownsSelection = true;
        }

        this.list = new Jx.List(this.listObj, this.options.listOptions, this.selection);

    },

    cleanup: function() {
        if (this.ownsSelection) {
            this.selection.destroy();
        }
        this.list.destroy();
    },

    add: function(item, where) {
        this.list.add(item, where);
        return this;
    },

    remove: function(item) {
        this.list.remove(item);
        return this;
    },

    replace: function(item, withItem) {
        this.list.replace(item, withItem);
        return this;
    },

    empty: function () {
        this.list.empty();
        return this;
    }
});/*
---

name: Jx.Field.Hidden

description: Represents a hidden input

license: MIT-style license.

requires:
 - Jx.Field

provides: [Jx.Field.Hidden]

...
 */
// $Id: hidden.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Field.Hidden
 *
 * Extends: <Jx.Field>
 *
 * This class represents a hidden input field.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Field.Hidden = new Class({

    Extends: Jx.Field,

    options: {
        /**
         * Option: template
         * The template used to render this field
         */
        template: '<span class="jxInputContainer"><input class="jxInputHidden" type="hidden" name="{name}"/></span>'
    },
    /**
     * Property: type
     * The type of this field
     */
    type: 'Hidden'

});




/*
---

name: Jx.Field.File

description: Represents a file input w/upload and progress tracking capabilities (requires APC for progress)

license: MIT-style license.

requires:
 - Jx.Field.Text
 - Jx.Button
 - Core/Request.JSON
 - Jx.Field.Hidden
 - Jx.Form

provides: [Jx.Field.File]

css:
 - file


...
 */
/**
 * Class: Jx.Field.File
 *
 * Extends: <Jx.Field>
 *
 * This class is designed to work with an iFrame and APC upload progress.
 * APC is a php specific technology but any server side implementation that
 * works in the same manner should work. You can then wire this class to the
 * progress bar class to show progress.
 *
 * The other option is to not use progress tracking and just use the base
 * upload which works through a hidden iFrame. In order to use this with Jx.Form
 * you'll need to add it normally but keep a reference to it. When you call
 * Jx.Form.getValues() it will not return any file information. You can then
 * call the Jx.Field.File.upload() method for each file input directly and
 * then submit the rest of the form via ajax.
 *
 * MooTools.lang Keys:
 * - file.browseLabel
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Field.File = new Class({

    Extends: Jx.Field,

    options: {
        /**
         * Option: template
         * The template used to render the field
         */
        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxFileInputs"><input class="jxInputFile" type="file" name="{name}" /></div><span class="jxInputTag"></span></span>',
        /**
         * Option: autoUpload
         * Whether to upload the file immediatelly upon selection
         */
        autoUpload: false,
        /**
         * Option: Progress
         * Whether to use the APC, or similar, progress method.
         */
        progress: false,
        /**
         * Option: progressIDUrl
         * The url to call in order to get the ID, or key, to use
         * with the APC upload process
         */
        progressIDUrl: '',
        /**
         * Option: progressName
         * The name to give the field that holds the generated progress ID retrieved
         * from the server. Defaults to 'APC_UPLOAD_PROGRESS' which is the default
         * for APC.
         */
        progressName: 'APC_UPLOAD_PROGRESS',
        /**
         * Option: progressId
         * The id to give the form element that holds the generated progress ID
         * retrieved from the server. Defaults to 'progress_key'.
         */
        progressId: 'progress_key',
        /**
         * Option: handlerUrl
         * The url to send the file to.
         */
        handlerUrl: '',
        /**
         * Option: progressUrl
         * The url used to retrieve the upload prgress of the file.
         */
        progressUrl: '',
        /**
         * Option: debug
         * Defaults to false. If set to true it will prevent the hidden form
         * and IFrame from being destroyed at the end of the upload so it can be
         * inspected during development
         */
        debug: false,
        /**
         * Option: mode
         * determines whether this file field acts in single upload mode or
         * multiple file upload mode. The multiple upload mode was done to work with
         * Jx.Panel.FileUpload. When in multiple mode, this field will remove the actual
         * file control after a file is selected, fires an event to signify the selection but will
         * hold on to the files until told to upload them. Obviously 'multiple' mode isn't designed
         * to be used when the control is outside of the Upload Panel (unless the user designs
         * their own upload panel around it).
         */
        mode: 'single'

    },
    /**
     * Property: type
     * The Field type used in rendering
     */
    type: 'File',
    /**
     * Property: forms
     * holds all form references when we're in multiple mode
     */
    forms: null,

    init: function () {
        this.parent();

        this.forms = new Hash();
        //create the iframe
        //we use the same iFrame for each so we don't have to recreate it each time
        this.isIFrameSetup = true;
        this.iframe = new Element('iframe', {
          name: this.generateId(),
          styles: {
            'display':'none',
            'visibility':'hidden'
          }
        });
        // this.iframe = new IFrame(null, {
        //     styles: {
        //         'display':'none',
        //         'visibility':'hidden'
        //     },
        //     name : this.generateId()
        // });
        this.iframe.inject(document.body);
        this.iframe.addEvent('load', this.processIFrameUpload.bind(this));

    },

    /**
     * APIMethod: render
     * renders the file input
     */
    render: function () {
        this.parent();

        //add a unique ID if no id is defined
        if (!$defined(this.options.id)) {
            this.field.set('id', this.generateId());
        }

        //now, create the fake inputs

        this.fake = new Element('div', {
            'class' : 'jxFileFake'
        });
        this.text = new Jx.Field.Text({
            template : '<span class="jxInputContainer"><input class="jxInputText" type="text" /></span>'
        });
        this.browseButton = new Jx.Button({
            label: this.getText({set:'Jx',key:'file',value:'browseLabel'})
        });

        this.fake.adopt(this.text, this.browseButton);
        this.field.grab(this.fake, 'after');

        this.field.addEvents({
            change : this.copyValue.bind(this),
            //mouseout : this.copyValue.bind(this),
            mouseenter : this.mouseEnter.bind(this),
            mouseleave : this.mouseLeave.bind(this)
        });

    },
    /**
     * Method: copyValue
     * Called when the value in the actual file input changes and when
     * the mouse moves out of it to copy the value into the "fake" text box.
     */
    copyValue: function () {
        if (this.options.mode=='single' && this.field.value !== '' && (this.text.field.value !== this.field.value)) {
            this.text.field.value = this.field.value;
            this.fireEvent('fileSelected', this);
            this.forms.set(this.field.value, this.prepForm());
            if (this.options.autoUpload) {
                this.uploadSingle();
            }
        } else if (this.options.mode=='multiple') {
            var filename = this.field.value;
            var form = this.prepForm();
            this.forms.set(filename, form);
            this.text.setValue('');
            //fire the selected event.
            this.fireEvent('fileSelected', filename);
        }
    },
    /**
     * Method: mouseEnter
     * Called when the mouse enters the actual file input to make the
     * fake button highlight.
     */
    mouseEnter: function () {
        this.browseButton.domA.addClass('jxButtonPressed');
    },
    /**
     * Method: mouseLeave
     * called when the mouse leaves the actual file input to turn off
     * the highlight of the fake button.
     */
    mouseLeave: function () {
        this.browseButton.domA.removeClass('jxButtonPressed');
    },

    prepForm: function () {
        //load in the form
        var form = new Jx.Form({
            action : this.options.handlerUrl,
            name : 'jxUploadForm',
            fileUpload: true
        });

        //move the form input into it (cloneNode)
        var parent = document.id(this.field.getParent());
        var sibling = document.id(this.field).getPrevious();
        var clone = this.field.clone().cloneEvents(this.field);
        document.id(form).grab(this.field);
        // tried clone.replaces(this.field) but it didn't seem to work
        if (sibling) {
          clone.inject(sibling, 'after');
        } else if (parent) {
            clone.inject(parent, 'top');
        }
        this.field = clone;

        this.mouseLeave();

        return form;
    },

    upload: function (externalForm) {
        //do we have files to upload?
        if (this.forms.getLength() > 0) {
            var keys = this.forms.getKeys();
            this.currentKey = keys[0];
            var form = this.forms.get(this.currentKey);
            this.forms.erase(this.currentKey);
            if ($defined(externalForm) && this.forms.getLength() == 0) {
                var fields = externalForm.fields;
                fields.each(function(field){
                    if (!(field instanceof Jx.Field.File)) {
                        document.id(field).clone().inject(form);
                    }
                },this);
            }
            this.uploadSingle(form);
        } else {
            //fire finished event...
            this.fireEvent('allUploadsComplete', this);
        }
    },
    /**
     * APIMethod: uploadSingle
     * Call this to upload the file to the server
     */
    uploadSingle: function (form) {
        this.form = $defined(form) ? form : this.prepForm();

        this.fireEvent('fileUploadBegin', [this.currentKey, this]);

        this.text.setValue('');

        document.id(this.form).set('target', this.iframe.get('name')).setStyles({
            visibility: 'hidden',
            display: 'none'
        }).inject(document.body);

        //if polling the server we need an APC_UPLOAD_PROGRESS id.
        //get it from the server.
        if (this.options.progress) {
            var req = new Request.JSON({
                url: this.options.progressIDUrl,
                method: 'get',
                onSuccess: this.submitUpload.bind(this)
            });
            req.send();
        } else {
            this.submitUpload();
        }
    },
    /**
     * Method: submitUpload
     * Called either after upload() or as a result of a successful call
     * to get a progress ID.
     *
     * Parameters:
     * data - Optional. The data returned from the call for a progress ID.
     */
    submitUpload: function (data) {
        //check for ID in data
        if ($defined(data) && data.success && $defined(data.id)) {
            this.progressID = data.id;
            //if have id, create hidden progress field
            var id = new Jx.Field.Hidden({
                name : this.options.progressName,
                id : this.options.progressId,
                value : this.progressID
            });
            id.addTo(this.form, 'top');
        }

        //submit the form
        document.id(this.form).submit();
        //begin polling if needed
        if (this.options.progress && $defined(this.progressID)) {
            this.pollUpload();
        }
    },
    /**
     * Method: pollUpload
     * polls the server for upload progress information
     */
    pollUpload: function () {
        var d = { id : this.progressID };
        var r = new Request.JSON({
            data: d,
            url : this.options.progressUrl,
            method : 'get',
            onSuccess : this.processProgress.bind(this),
            onFailure : this.uploadFailure.bind(this)
        });
        r.send();
    },

    /**
     * Method: processProgress
     * process the data returned from the request
     *
     * Parameters:
     * data - The data from the request as an object.
     */
    processProgress: function (data) {
        if ($defined(data)) {
            this.fireEvent('fileUploadProgress', [data, this.currentKey, this]);
            if (data.current < data.total) {
                this.polling = true;
                this.pollUpload();
            } else {
                this.polling = false;
            }
        }
    },
    /**
     * Method: uploadFailure
     * called if there is a problem getting progress on the upload
     */
    uploadFailure: function (xhr) {
        this.fireEvent('fileUploadProgressError', [this, xhr]);
    },
    /**
     * Method: processIFrameUpload
     * Called if we are not using progress and the IFrame finished loading the
     * server response.
     */
    processIFrameUpload: function () {
        //the body text should be a JSON structure
        //get the body
        if (this.isIFrameSetup) {
            if (this.iframe.contentDocument  && this.iframe.contentDocument.defaultView) {
              var iframeBody = this.iframe.contentDocument.defaultView.document.body.innerHTML;
            } else {
              // seems to be needed for ie7
              var iframeBody = this.iframe.contentWindow.document.body.innerHTML;
            }

            var data = JSON.decode(iframeBody);
            if ($defined(data) && $defined(data.success) && data.success) {
                this.done = true;
                this.doneData = data;
                this.uploadCleanUp();
                this.fireEvent('fileUploadComplete', [data, this.currentKey, this]);
            } else {
                this.fireEvent('fileUploadError', [data , this.currentKey, this]);
            }

            if (this.options.mode == 'multiple') {
                this.upload();
            } else {
                this.fireEvent('allUploadsComplete', this);
            }
        }
    },
    /**
     * Method: uploadCleanUp
     * Cleans up the hidden form and IFrame after a completed upload. Set
     * this.options.debug to true to keep this from happening
     */
    uploadCleanUp: function () {
        if (!this.options.debug) {
            this.form.destroy();
        }
    },
    /**
     * APIMethod: remove
     * Removes a file from the hash of forms to upload.
     *
     * Parameters:
     * filename - the filename indicating which file to remove.
     */
    remove: function (filename) {
        if (this.forms.has(filename)) {
            this.forms.erase(filename);
        }
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     * 		translations changed.
     */
    changeText: function (lang) {
    	this.parent();
    	if ($defined(this.browseButton)) {
    		this.browseButton.setLabel( this.getText({set:'Jx',key:'file',value:'browseLabel'}) );
    	}
    },

    /**
     * APIMethod: getFileInputs
     * Used to get an array of all of the basic file inputs. This is mainly
     * here for use by Jx.Form to be able to suck in the file inputs
     * before a standard submit.
     *
     */
    getFileInputs: function () {
        var inputs = [];
        this.forms.each(function(form){
            var input = document.id(form).getElement('input[type=file]');
            inputs.push(input);
        },this);
        return inputs;
    }
});/*
---

name: Jx.Progressbar

description: A css-based progress bar.

license: MIT-style license.

requires:
 - Jx.Widget
 - Core/Fx.Tween

provides: [Jx.Progressbar]

css:
 - progressbar

images:
 - progressbar.png

...
 */
/**
 * Class: Jx.Progressbar
 *
 *
 * Example:
 * The following just uses the defaults.
 * (code)
 * var progressBar = new Jx.Progressbar();
 * progressBar.addEvent('update',function(){alert('updated!');});
 * progressBar.addEvent('complete',function(){
 *      alert('completed!');
 *      this.destroy();
 * });
 *
 * progressbar.addTo('container');
 *
 * var total = 90;
 * for (i=0; i < total; i++) {
 *      progressbar.update(total, i);
 * }
 * (end)
 *
 * Events:
 * onUpdate - Fired when the bar is updated
 * onComplete - fires when the progress bar completes it's fill
 *
 * MooTools.lang keys:
 * - progressbar.messageText
 * - progressbar.progressText
 *
 * Copyright (c) 2010 by Jonathan Bomgardner
 * Licensed under an mit-style license
 */
Jx.Progressbar = new Class({
    Family: 'Jx.Progressbar',
    Extends: Jx.Widget,

    options: {
        onUpdate: $empty,
        onComplete: $empty,
        /**
         * Option: parent
         * The element to put this progressbar into
         */
        parent: null,
        /**
         * Option: progressText
         * Text to show while processing, uses
         * {progress} von {total}
         */
        progressText : null,
        /**
         * Option: template
         * The template used to create the progressbar
         */
        template: '<div class="jxProgressBar-container"><div class="jxProgressBar-message"></div><div class="jxProgressBar"><div class="jxProgressBar-outline"></div><div class="jxProgressBar-fill"></div><div class="jxProgressBar-text"></div></div></div>'
    },
    /**
     * Property: classes
     * The classes used in the template
     */
    classes: new Hash({
        domObj: 'jxProgressBar-container',
        message: 'jxProgressBar-message',
        container: 'jxProgressBar',
        outline: 'jxProgressBar-outline',
        fill: 'jxProgressBar-fill',
        text: 'jxProgressBar-text'
    }),
    /**
     * Property: bar
     * the bar that is filled
     */
    bar: null,
    /**
     * Property: text
     * the element that contains the text that's shown on the bar (if any).
     */
    text: null,

    /**
     * APIMethod: render
     * Creates a new progressbar.
     */
    render: function () {
        this.parent();

        if ($defined(this.options.parent)) {
            this.domObj.inject(document.id(this.options.parent));
        }

        this.domObj.addClass('jxProgressStarting');

        //we need to know the width of the bar
        this.width = document.id(this.domObj).getContentBoxSize().width;

        //Message
        if (this.message) {
            if ($defined(MooTools.lang.get('Jx','progressbar').messageText)) {
                this.message.set('html', this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
            } else {
                this.message.destroy();
            }
        }

        //Fill
        if (this.fill) {
            this.fill.setStyles({
                'width': 0
            });
        }

        //TODO: check for {progress} and {total} in progressText
        var obj = {};
        var progressText = this.options.progressText == null ?
                              this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
                              this.getText(this.options.progressText);
        if (progressText.contains('{progress}')) {
            obj.progress = 0;
        }
        if (progressText.contains('{total}')) {
            obj.total = 0;
        }

        //Progress text
        if (this.text) {
            this.text.set('html', progressText.substitute(obj));
        }

    },
    /**
     * APIMethod: update
     * called to update the progress bar with new percentage.
     *
     * Parameters:
     * total - the total # to progress up to
     * progress - the current position in the progress (must be less than or
     *              equal to the total)
     */
    update: function (total, progress) {

    	//check for starting class
    	if (this.domObj.hasClass('jxProgressStarting')) {
    		this.domObj.removeClass('jxProgressStarting').addClass('jxProgressWorking');
    	}

        var newWidth = (progress * this.width) / total;

        //update bar width
        this.text.get('tween', {property:'width', onComplete: function() {
            var obj = {};
            var progressText = this.options.progressText == null ?
                                  this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
                                  this.getText(this.options.progressText);
            if (progressText.contains('{progress}')) {
                obj.progress = progress;
            }
            if (progressText.contains('{total}')) {
                obj.total = total;
            }
            var t = progressText.substitute(obj);
            this.text.set('text', t);
        }.bind(this)}).start(newWidth);

        this.fill.get('tween', {property: 'width', onComplete: (function () {

            if (total === progress) {
                this.complete = true;
                this.domObj.removeClass('jxProgressWorking').addClass('jxProgressFinished');
                this.fireEvent('complete');
            } else {
                this.fireEvent('update');
            }
        }).bind(this)}).start(newWidth);

    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     * 		translations changed.
     */
    changeText: function (lang) {
    	this.parent();
    	if (this.message) {
    		this.message.set('html',this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
    	}
        //progress text will update on next update.
    }

});/*
---

name: Jx.Panel.FileUpload

description: A panel subclass that is designed to be a multiple file upload panel with a queue listing.

license: MIT-style license.

requires:
 - Jx.Panel
 - Jx.ListView
 - Jx.Field.File
 - Jx.Progressbar
 - Jx.Button
 - Jx.Toolbar.Item
 - Jx.Tooltip

provides: [Jx.Panel.FileUpload]

css:
 - upload

images:
 - icons.png
...
 */
// $Id: upload.js 1000 2010-12-06 01:58:47Z jonlb@comcast.net $
/**
 * Class: Jx.Panel.FileUpload
 *
 * Extends: <Jx.Panel>
 *
 * This class extends Jx.Panel to provide a consistent interface for uploading
 * files in an application.
 *
 * MooTools.lang Keys:
 * - upload.buttonText
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Panel.FileUpload = new Class({

    Family: 'Jx.Panel.FileUpload',
    Extends: Jx.Panel,
    Binds: ['moveToQueue','fileUploadBegin', 'fileUploadComplete','allUploadsComplete', 'fileUploadProgressError,', 'fileUploadError', 'fileUploadProgress'],

    options: {
        /**
         * Option: file
         * An object containing the options for Jx.Field.File
         */
        file: {
            autoUpload: false,
            progress: false,
            progressIDUrl: '',
            handlerUrl: '',
            progressUrl: ''
        },

        progressOptions: {
            template: "<li class='jxListItemContainer jxProgressBar-container' id='{id}'><div class='jxProgressBar'><div class='jxProgressBar-outline'></div><div class='jxProgressBar-fill'></div><div class='jxProgressBar-text'></div></div></li>",
            containerClass: 'progress-container',
            messageText: null,
            messageClass: 'progress-message',
            progressText: 'Uploading {filename}',
            progressClass: 'progress-bar'
        },
        /**
         * Option: onFileComplete
         * An event handler that is called when a file has been uploaded
         */
        onFileComplete: $empty,
        /**
         * Option: onComplete
         * An event handler that is called when all files have been uploaded
         */
        onComplete: $empty,
        /**
         * Option: prompt
         * The prompt to display at the top of the panel - before the
         * file input
         */
        prompt: null,
        /**
         * Option: removeOnComplete
         * Determines whether a file is removed from the queue after uploading
         */
        removeOnComplete: false
    },
    /**
     * Property: domObjA
     * An HTML Element used to hold the interface while it is being
     * constructed.
     */
    domObjA: null,
    /**
     * Property: fileQueue
     * An array holding Jx.Field.File elements that are to be uploaded
     */
    fileQueue: [],

    listTemplate: "<li class='jxListItemContainer' id='{id}'><a class='jxListItem' href='javascript:void(0);'><span class='itemLabel jxUploadFileName'>{name}</span><span class='jxUploadFileDelete' title='Remove this file from the queue.'></span></a></li>",
    /**
     * Method: render
     * Sets up the upload panel.
     */
    render: function () {
        //first create panel content
        this.domObjA = new Element('div', {'class' : 'jxFileUploadPanel'});


        if ($defined(this.options.prompt)) {
            var desc;
            if (Jx.type(this.options.prompt === 'string')) {
                desc = new Element('p', {
                    html: this.options.prompt
                });
            } else {
                desc = this.options.prompt;
            }
            desc.inject(this.domObjA);
        }

        //add the file field
        this.fileOpt = this.options.file;
        this.fileOpt.template = '<div class="jxInputContainer jxFileInputs"><input class="jxInputFile" type="file" name={name} /></div>';

        this.file = new Jx.Field.File(this.fileOpt);
        this.file.addEvent('fileSelected', this.moveToQueue);
        this.file.addTo(this.domObjA);

        this.listView = new Jx.ListView({
            template: '<ul class="jxListView jxList jxUploadQueue"></ul>'

        }).addTo(this.domObjA);

        if (!this.options.file.autoUpload) {
            //this is the upload button at the bottom of the panel.
            this.uploadBtn = new Jx.Button({
                label : this.getText({set:'Jx',key:'upload',value:'buttonText'}),
                onClick: this.upload.bind(this)
            });
            var tlb = new Jx.Toolbar({position: 'bottom', scroll: false}).add(this.uploadBtn);
            this.uploadBtn.setEnabled(false);
            this.options.toolbars = [tlb];
        }
        //then pass it on to the Panel constructor
        this.options.content = this.domObjA;
        this.parent(this.options);
    },
    /**
     * Method: moveToQueue
     * Called by Jx.Field.File's fileSelected event. Moves the selected file
     * into the upload queue.
     */
    moveToQueue: function (filename) {
        var theTemplate = new String(this.listTemplate).substitute({
            name: filename,
            id: filename
        });
        var item = new Jx.ListItem({template:theTemplate, enabled: true});

        $(item).getElement('.jxUploadFileDelete').addEvent('click', function(){
            this.listView.remove(item);
            this.file.remove(filename);
            if (this.listView.list.count() == 0) {
                this.uploadBtn.setEnabled(false);
            }
        }.bind(this));
        this.listView.add(item);

        if (!this.uploadBtn.isEnabled()) {
            this.uploadBtn.setEnabled(true);
        }

    },
    /**
     * Method: upload
     * Called when the user clicks the upload button. Runs the upload process.
     */
    upload: function () {

        this.file.addEvents({
            'fileUploadBegin': this.fileUploadBegin ,
            'fileUploadComplete': this.fileUploadComplete,
            'allUploadsComplete': this.allUploadsComplete,
            'fileUploadError': this.fileUploadError,
            'fileUploadProgress': this.fileUploadProgress,
            'fileUploadProgressError': this.fileUploadError
        });


        this.file.upload();
    },

    fileUploadBegin: function (filename) {
        if (this.options.file.progress) {
            //progressbar
            //setup options
            // TODO: should (at least some of) these options be available to
            // the developer?
            var options = $merge({},this.options.progressOptions);
            options.progressText = options.progressText.substitute({filename: filename});
            options.template = options.template.substitute({id: filename});
            this.pb = new Jx.Progressbar(options);
            var item = document.id(filename);
            this.oldContents = item;
            this.listView.replace(item,$(this.pb));
        } else {
            var icon = document.id(filename).getElement('.jxUploadFileDelete')
            icon.addClass('jxUploadFileProgress').set('title','File Uploading...');
        }
    },

    /**
     * Method: fileUploadComplete
     * Called when a single file is uploaded completely .
     *
     * Parameters:
     * data - the data returned from the event
     * filename - the filename of the file we're tracking
     */
    fileUploadComplete: function (data, file) {
        if ($defined(data.success) && data.success ){
            this.removeUploadedFile(file);
        } else {
            this.fileUploadError(data, file);
        }
    },
    /**
     * Method: fileUploadError
     * Called when there is an error uploading a file.
     *
     * Parameters:
     * data - the data passed back from the server, if any.
     * file - the file we're tracking
     */
    fileUploadError: function (data, filename) {

        if (this.options.file.progress) {
            //show this old contents...
            this.listView.replace(document.id(filename),this.oldContents);
        }
        var icon = document.id(filename).getElement('.jxUploadFileDelete');
        icon.erase('title');
        if (icon.hasClass('jxUploadFileProgress')) {
            icon.removeClass('jxUploadFileProgress').addClass('jxUploadFileError');
        } else {
            icon.addClass('jxUploadFileError');
        }
        if ($defined(data.error) && $defined(data.error.message)) {
            var tt = new Jx.Tooltip(icon, data.error.message, {
                cssClass : 'jxUploadFileErrorTip'
            });
        }
    },
    /**
     * Method: removeUploadedFile
     * Removes the passed file from the upload queue upon it's completion.
     *
     * Parameters:
     * file - the file we're tracking
     */
    removeUploadedFile: function (filename) {

        if (this.options.removeOnComplete) {
           this.listView.remove(document.id(filename));
        } else {
            if (this.options.file.progress) {
                this.listView.replace(document.id(filename),this.oldContents);
            }
            var l = document.id(filename).getElement('.jxUploadFileDelete');
            if (l.hasClass('jxUploadFileDelete')) {
                l.addClass('jxUploadFileComplete');
            } else if (l.hasClass('jxUploadFileProgress')) {
                l.removeClass('jxUploadFileProgress').addClass('jxUploadFileComplete');
            }
        }

        this.fireEvent('fileUploadComplete', filename);
    },
    /**
     * Method: fileUploadProgress
     * Function to pass progress information to the progressbar instance
     * in the file. Only used if we're tracking progress.
     */
    fileUploadProgress: function (data, file) {
        if (this.options.progress) {
            this.pb.update(data.total, data.current);
        }
    },
    /**
     * Method: allUploadCompleted
     * Called when the Jx.Field.File completes uploading
     * all files. Sets upload button to disabled and fires the allUploadCompleted
     * event.
     */
    allUploadsComplete: function () {
        this.uploadBtn.setEnabled(false);
        this.fireEvent('allUploadsCompleted',this);
    },
    /**
     * Method: createText
     * handle change in language
     */
    changeText: function (lang) {
      this.parent();
      if ($defined(this.uploadBtn)) {
        this.uploadBtn.setLabel({set:'Jx',key:'upload',value:'buttonText'});
      }
    }
});
/*
---

name: Jx.Column

description: A representation of a single grid column

license: MIT-style license.

requires:
 - Jx.Widget

provides: [Jx.Column]

...
 */
// $Id: column.js 992 2010-10-07 19:28:37Z pagameba $
/**
 * Class: Jx.Column
 *
 * Extends: <Jx.Object>
 *
 * The class used for defining columns for grids.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Column = new Class({

    Family: 'Jx.Column',
    Extends: Jx.Widget,

    options: {
        /**
         * Option: renderMode
         * The mode to use in rendering this column to determine its width.
         * Valid options include
         *
         * fit - auto calculates the width for the best fit to the text. This
         *      is the default.
         * fixed - uses the value passed in the width option, doesn't
         *      auto calculate.
         * expand - uses the value in the width option as a minimum width and
         *      allows this column to expand and take up any leftover space.
         *      NOTE: there can be only 1 expand column in a grid. The
         *      Jx.Columns object will only take the first column with this
         *      option as the expanding column. All remaining columns marked
         *      "expand" will be treated as "fixed".
         */
        renderMode: 'fixed',
        /**
         * Option: width
         * Determines the width of the column when using 'fixed' rendering mode
         * or acts as a minimum width when using 'expand' mode.
         */
        width: 100,

        /**
         * Option: isEditable
         * allows/disallows editing of the column contents
         */
        isEditable: false,
        /**
         * Option: isSortable
         * allows/disallows sorting based on this column
         */
        isSortable: false,
        /**
         * Option: isResizable
         * allows/disallows resizing this column dynamically
         */
        isResizable: false,
        /**
         * Option: isHidden
         * determines if this column can be shown or not
         */
        isHidden: false,
        /**
         * Option: name
         * The name given to this column
         */
        name: '',

        /**
         * Option: template
         */
        template: null,
        /**
         * Option: renderer
         * an instance of a Jx.Grid.Renderer to use in rendering the content
         * of this column or a config object for creating one like so:
         *
         * (code)
         * {
         *     name: 'Text',
         *     options: { ... renderer options ... }
         * }
         */
        renderer: null
    },

    classes: $H({
      domObj: 'jxGridCellContent'
    }),

    /**
     * Property: grid
     * holds a reference to the grid (an instance of <Jx.Grid>)
     */
    grid: null,

    parameters: ['options','grid'],

    /**
     * Constructor: Jx.Column
     * initializes the column object
     */
    init : function () {

        this.name = this.options.name;

        //adjust header for column
        if (!$defined(this.options.template)) {
            this.options.template = '<span class="jxGridCellContent">' + this.name.capitalize() + '</span>';
        }

        this.parent();
        if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
            this.grid = this.options.grid;
        }

        //check renderer
        if (!$defined(this.options.renderer)) {
            //set a default renderer
            this.options.renderer = new Jx.Grid.Renderer.Text({
                textTemplate: '{' + this.name + '}'
            });
        } else {
            if (!(this.options.renderer instanceof Jx.Grid.Renderer)) {
                var t = Jx.type(this.options.renderer);
                if (t === 'object') {
                    if(!$defined(this.options.renderer.options.textTemplate)) {
                      this.options.renderer.options.textTemplate = '{' + this.name + '}';
                    }
                    if(!$defined(this.options.renderer.name)) {
                      this.options.renderer.name = 'Text';
                    }
                    this.options.renderer = new Jx.Grid.Renderer[this.options.renderer.name.capitalize()](
                            this.options.renderer.options);
                }
            }
        }

        this.options.renderer.setColumn(this);
    },

    getTemplate: function(idx) {
      return "<span class='jxGridCellContent' title='{col"+idx+"}'>{col"+idx+"}</span>";
    },

    /**
     * APIMethod: getHeaderHTML
     */
    getHeaderHTML : function () {
      if (this.isSortable() && !this.sortImage) {
        this.sortImage = new Element('img', {
            src: Jx.aPixel.src
        });
        this.sortImage.inject(this.domObj);
      } else {
        if (!this.isSortable() && this.sortImage) {
          this.sortImage.dispose();
          this.sortImage = null;
        }
      }
      return this.domObj;
    },

    setWidth: function(newWidth, asCellWidth) {
        asCellWidth = $defined(asCellWidth) ? asCellWidth : false;

        var delta = this.cellWidth - this.width;
        if (!asCellWidth) {
          this.width = parseInt(newWidth,10);
          this.cellWidth = this.width + delta;
          this.options.width = newWidth;
        } else {
            this.width = parseInt(newWidth,10) - delta;
            this.cellWidth = newWidth;
            this.options.width = this.width;
        }
      if (this.rule && parseInt(this.width,10) >= 0) {
          this.rule.style.width = parseInt(this.width,10) + "px";
      }
      if (this.cellRule && parseInt(this.cellWidth,10) >= 0) {
          this.cellRule.style.width = parseInt(this.cellWidth,10) + "px";
      }
    },

    /**
     * APIMethod: getWidth
     * return the width of the column
     */
    getWidth: function () {
      return this.width;
    },

    /**
     * APIMethod: getCellWidth
     * return the cellWidth of the column
     */
    getCellWidth: function() {
      return this.cellWidth;
    },

    /**
     * APIMethod: calculateWidth
     * returns the width of the column.
     *
     * Parameters:
     * rowHeader - flag to tell us if this calculation is for the row header
     */
    calculateWidth : function (rowHeader) {
        //if this gets called then we assume that we want to calculate the width
      rowHeader = $defined(rowHeader) ? rowHeader : false;
      var maxWidth,
          maxCellWidth,
          store = this.grid.getStore(),
          t,
          s,
          oldPos,
          text,
          klass;
      store.first();
      if ((this.options.renderMode == 'fixed' ||
           this.options.renderMode == 'expand') &&
          store.valid()) {
        t = new Element('span', {
          'class': 'jxGridCellContent',
          html: 'a',
          styles: {
            width: this.options.width
          }
        });
        s = this.measure(t,'jxGridCell');
        maxWidth = s.content.width;
        maxCellWidth = s.cell.width;
      } else {
          //calculate the width
          oldPos = store.getPosition();
          maxWidth = maxCellWidth = 0;
          while (store.valid()) {
              //check size by placing text into a TD and measuring it.
              this.options.renderer.render();
              text = document.id(this.options.renderer);
              klass = 'jxGridCell';
              if (this.grid.row.useHeaders()
                      && this.options.name === this.grid.row
                      .getRowHeaderColumn()) {
                  klass = 'jxGridRowHead';
              }
              s = this.measure(text, klass, rowHeader, store.getPosition());
              if (s.content.width > maxWidth) {
                  maxWidth = s.content.width;
              }
              if (s.cell.width > maxCellWidth) {
                maxCellWidth = s.cell.width;
              }
              if (store.hasNext()) {
                  store.next();
              } else {
                  break;
              }
          }

          //check the column header as well (unless this is the row header)
          if (!(this.grid.row.useHeaders() &&
              this.options.name === this.grid.row.getRowHeaderColumn())) {
              klass = 'jxGridColHead';
              if (this.isEditable()) {
                  klass += ' jxColEditable';
              }
              if (this.isResizable()) {
                  klass += ' jxColResizable';
              }
              if (this.isSortable()) {
                  klass += ' jxColSortable';
              }
              s = this.measure(this.domObj.clone(), klass);
              if (s.content.width > maxWidth) {
                  maxWidth = s.content.width;
              }
              if (s.cell.width > maxCellWidth) {
                maxCellWidth = s.cell.width;
              }
          }
      }

      this.width = maxWidth;
      this.cellWidth = maxCellWidth;
      store.moveTo(oldPos);
      return this.width;
    },
    /**
     * Method: measure
     * This method does the dirty work of actually measuring a cell
     *
     * Parameters:
     * text - the text to measure
     * klass - a string indicating and extra classes to add so that
     *          css classes can be taken into account.
     * rowHeader -
     * row -
     */
    measure : function (text, klass, rowHeader, row) {
        var d = new Element('span', {
            'class' : klass
        }),
        s;
        text.inject(d);
        //d.setStyle('height', this.grid.row.getHeight(row));
        d.setStyles({
            'visibility' : 'hidden',
            'width' : 'auto'
        });

        d.inject(document.body, 'bottom');
        s = d.measure(function () {
            var el = this;
            //if not rowHeader, get size of innner span
            if (!rowHeader) {
                el = el.getFirst();
            }
            return {
              content: el.getMarginBoxSize(),
              cell: el.getMarginBoxSize()
            };
        });
        d.destroy();
        return s;
    },
    /**
     * APIMethod: isEditable
     * Returns whether this column can be edited
     */
    isEditable : function () {
        return this.options.isEditable;
    },
    /**
     * APIMethod: isSortable
     * Returns whether this column can be sorted
     */
    isSortable : function () {
        return this.options.isSortable;
    },
    /**
     * APIMethod: isResizable
     * Returns whether this column can be resized
     */
    isResizable : function () {
        return this.options.isResizable;
    },
    /**
     * APIMethod: isHidden
     * Returns whether this column is hidden
     */
    isHidden : function () {
        return this.options.isHidden;
    },
    /**
     * APIMethod: isAttached
     * returns whether this column is attached to a store.
     */
    isAttached: function () {
        return this.options.renderer.attached;
    },

    /**
     * APIMethod: getHTML
     * calls render method of the renderer object passed in.
     */
    getHTML : function () {
        this.options.renderer.render();
        return document.id(this.options.renderer);
    }

});/*
---

name: Jx.Columns

description: A container for defining and holding individual columns

license: MIT-style license.

requires:
 - Jx.Column
 - Jx.Object

provides: [Jx.Columns]

...
 */
// $Id: columns.js 992 2010-10-07 19:28:37Z pagameba $
/**
 * Class: Jx.Columns
 *
 * Extends: <Jx.Object>
 *
 * This class is the container for all columns needed for a grid. It
 * consolidates many functions that didn't make sense to put directly
 * in the column class. Think of it as a model for columns.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Columns = new Class({

  Family: 'Jx.Columns',
    Extends : Jx.Object,

    options : {
        /**
         * Option: headerRowHeight
         * the default height of the header row. Set to null or 'auto' to
         * have this class attempt to figure out a suitable height.
         */
        headerRowHeight : 20,
        /**
         * Option: useHeaders
         * Determines if the column headers should be displayed or not
         */
        useHeaders : false,
        /**
         * Option: columns
         * an array holding all of the column instances or objects containing
         * configuration info for the column
         */
        columns : []
    },
    /**
     * Property: columns
     * an array holding the actual instantiated column objects
     */
    columns : [],

    /**
     * Property: rowTemplate
     * a string holding a template for a single row of cells to be populated
     * when rendering the store into a grid.  The template is constructed from
     * the individual column templates once the store has been loaded.
     */
    rowTemplate: null,

    parameters: ['options','grid'],
    /**
     * Property: hasExpandable
     * boolean indicates whether any of the columns are expandable or not,
     * which affects some calculations for column widths
     */
    hasExpandable: null,

    /**
     * APIMethod: init
     * Creates the class.
     */
    init : function () {
        this.parent();

        if ($defined(this.options.grid) &&
            this.options.grid instanceof Jx.Grid) {
          this.grid = this.options.grid;
        }

        this.hasExpandable = false;

        this.options.columns.each(function (col) {
            //check the column to see if it's a Jx.Grid.Column or an object
            if (col instanceof Jx.Column) {
                this.columns.push(col);
            } else if (Jx.type(col) === "object") {
                this.columns.push(new Jx.Column(col,this.grid));
            }
            var c = this.columns[this.columns.length - 1 ];
        }, this);

        this.buildTemplates();
    },

    /**
     * APIMethod: addColumns
     * add new columns to the columns object after construction.  Causes
     * the template to change.
     *
     * Parameters:
     * columns - {Array} an array of columns to add
     */
    addColumns: function(columns) {
      this.columns.extend(columns);
      this.buildTemplates();
    },

    /**
     * Method: buildTemplates
     * create the row template based on the current columns
     */
    buildTemplates: function() {
      if (!this.grid) {
        return;
      }
      var rowTemplate = '',
          hasExpandable = false,
          grid = this.grid,
          row = grid.row,
          rhc = grid.row.useHeaders() ? this.getByName(row.options.headerColumn) : null,
          colTemplate;

      this.columns.each(function(col, idx) {
        var colTemplate = '';
        if (!col.isHidden() && col != rhc) {
          hasExpandable |= col.options.renderMode == 'expand';
          if (!col.options.renderer || !col.options.renderer.domInsert) {
            colTemplate = col.getTemplate(idx);
          }
          rowTemplate += "<td class='jxGridCell jxGridCol"+idx+" jxGridCol"+col.options.name+"'>" + colTemplate + "</td>";
        }
      });
      if (!hasExpandable) {
        rowTemplate += "<td><span class='jxGridCellUnattached'></span></td>";
      }
      this.rowTemplate = rowTemplate;
      this.hasExpandable = hasExpandable;
    },
    /**
     * APIMethod: getHeaderHeight
     * returns the height of the column header row
     *
     * Parameters:
     * recalculate - determines if we should recalculate the height. Currently
     * does nothing.
     */
    getHeaderHeight : function (recalculate) {
        if (!$defined(this.height) || recalculate) {
            if ($defined(this.options.headerRowHeight)
                    && this.options.headerRowHeight !== 'auto') {
                this.height = this.options.headerRowHeight;
            } //else {
                //figure out a height.
            //}
        }
        return this.height;
    },
    /**
     * APIMethod: useHeaders
     * returns whether the grid is/should display headers or not
     */
    useHeaders : function () {
        return this.options.useHeaders;
    },
    /**
     * APIMethod: getByName
     * Used to get a column object by the name of the column
     *
     * Parameters:
     * colName - the name of the column
     */
    getByName : function (colName) {
        var ret;
        this.columns.each(function (col) {
            if (col.name === colName) {
                ret = col;
            }
        }, this);
        return ret;
    },
    /**
     * APIMethod: getByField
     * Used to get a column by the model field it represents
     *
     *  Parameters:
     *  field - the field name to search by
     */
    getByField : function (field) {
        var ret;
        this.columns.each(function (col) {
            if (col.options.modelField === field) {
                ret = col;
            }
        }, this);
        return ret;
    },
    /**
     * APIMethod: getByGridIndex
     * Used to get a column when all you know is the cell index in the grid
     *
     * Parameters:
     * index - an integer denoting the placement of the column in the grid
     * (zero-based)
     */
    getByGridIndex : function (index) {
        var headers = this.options.useHeaders ?
                        this.grid.colTableBody.getFirst().getChildren() :
                        this.grid.gridTableBody.getFirst().getChildren();
        var cell = headers[index];
          var hClasses = cell.get('class').split(' ').filter(function (cls) {
            if(this.options.useHeaders)
              return cls.test('jxColHead-');
            else
              return cls.test('jxCol-');
          }.bind(this));
        var parts = hClasses[0].split('-');
        return this.getByName(parts[1]);
    },

    /**
     * APIMethod: getHeaders
     * Returns a row with the headers in it.
     *
     * Parameters:
     * row - the row to add the headers to.
     */
    getHeaders : function (tr) {
      var grid = this.grid,
          row = grid.row,
          rhc = grid.row.useHeaders() ? this.getByName(row.options.headerColumn) : null;
      if (this.useHeaders()) {
        this.columns.each(function(col, idx) {
          if (!col.isHidden() && col != rhc) {
            var classes = ['jxGridColHead', 'jxGridCol'+idx, 'jxCol-'+col.options.name, 'jxColHead-'+col.options.name],
                th;
            if (col.isEditable()) { classes.push('jxColEditable'); }
            if (col.isResizable()) { classes.push('jxColResizable'); }
            if (col.isSortable()) { classes.push('jxColSortable'); }
            th = new Element('th', {
              'class': classes.join(' ')
            });
            th.store('jxCellData', {
              column: col,
              colHeader: true,
              index: idx
            });
            th.adopt(col.getHeaderHTML());
            th.inject(tr);
          }
        });
        if (!this.hasExpandable) {
          new Element('th', {
            'class': 'jxGridColHead jxGridCellUnattached'
          }).inject(tr);
        }
      }
    },

    /**
     * Method: getRow
     * create a single row in the grid for a single record and populate
     * the DOM elements for it.
     *
     * Parameters:
     * tr - {DOMElement} the TR element to insert the row into
     * record - {<Jx.Record>} the record to create the row for
     */
    getRow: function(tr, record) {
      var data = {},
          grid = this.grid,
          store = grid.store,
          row = grid.row,
          rhc = grid.row.useHeaders() ?
                     this.getByName(row.options.headerColumn) : null,
          domInserts = [],
          i = 0;
      this.columns.each(function(column, index) {
        if (!column.isHidden() && column != rhc) {
          if (column.options.renderer && column.options.renderer.domInsert) {
            domInserts.push({column: column, index: i});
          } else {
            var renderer = column.options.renderer,
                formatter = renderer.options.formatter,
                text = '';
            if (renderer.options.textTemplate) {
              text = store.fillTemplate(null, renderer.options.textTemplate, renderer.columnsNeeded);
            } else {
              text = record.data.get(column.name);
            }
            if (formatter) {
              text = formatter.format(text);
            }
            data['col'+index] = text;
          }
          i++;
        }
      });
      tr.set('html', this.rowTemplate.substitute(data));
      domInserts.each(function(obj) {
        tr.childNodes[obj.index].adopt(obj.column.getHTML());
      });
    },

    /**
     * APIMethod: calculateWidths
     * force calculation of column widths.  For columns with 'fit' this will
     * cause the column to test every value in the store to compute the
     * optimal width of the column.  Columns marked as 'expand' will get
     * any extra space left over between the column widths and the width
     * of the grid container (if any).
     */
    calculateWidths: function () {
      //to calculate widths we loop through each column
      var expand = null,
          totalWidth = 0,
          rowHeaderWidth = 0,
          gridSize = this.grid.contentContainer.getContentBoxSize(),
          leftOverSpace = 0;
      this.columns.each(function(col,idx){
        //are we checking the rowheader?
        var rowHeader = false;
        // if (col.name == this.grid.row.options.headerColumn) {
        //   rowHeader = true;
        // }
        //if it's fixed, set the width to the passed in width
        if (col.options.renderMode == 'fixed') {
          col.calculateWidth(); //col.setWidth(col.options.width);

        } else if (col.options.renderMode == 'fit') {
          col.calculateWidth(rowHeader);
        } else if (col.options.renderMode == 'expand' && !$defined(expand)) {
          expand = col;
        } else {
          //treat it as fixed if has width, otherwise as fit
          if ($defined(col.options.width)) {
            col.setWidth(col.options.width);
          } else {
            col.calculateWidth(rowHeader);
          }
        }
        if (!col.isHidden() /* && !(col.name == this.grid.row.options.headerColumn) */) {
            totalWidth += Jx.getNumber(col.getCellWidth());
            if (rowHeader) {
                rowHeaderWidth = col.getWidth();
            }
        }
      },this);

      // width of the container
      if (gridSize.width > totalWidth) {
        //now figure the expand column
        if ($defined(expand)) {
          // var leftOverSpace = gridSize.width - totalWidth + rowHeaderWidth;
          leftOverSpace = gridSize.width - totalWidth;
          //account for right borders in firefox...
          if (Browser.Engine.gecko) {
            leftOverSpace -= this.getColumnCount(true);
          } else {
            // -2 is for the right hand border on the cell and the table for all other browsers
            leftOverSpace -= 2;
          }
          if (leftOverSpace >= expand.options.width) {
            //in order for this to be set properly the cellWidth must be the
            //leftover space. we need to figure out the delta value and
            //subtract it from the leftover width
            expand.options.width = leftOverSpace;
            expand.calculateWidth();
            expand.setWidth(leftOverSpace, true);
            totalWidth += leftOverSpace;
          } else {
            expand.setWidth(expand.options.width);
          }
        }
      }
      this.grid.gridObj.setContentBoxSize({'width': totalWidth});
      this.grid.colObj.setContentBoxSize({'width': totalWidth});
    },

    /**
     * Method: createRules
     * create CSS rules for the current grid object
     */
    createRules: function(styleSheet, scope) {
      var autoRowHeight = this.grid.row.options.rowHeight == 'auto';
      this.columns.each(function(col, idx) {
        var selector = scope+' .jxGridCol'+idx,
            dec = '';
        if (autoRowHeight) {
          //set the white-space to 'normal !important'
          dec = 'white-space: normal !important';
        }
        col.cellRule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
        col.cellRule.style.width = col.getCellWidth() + "px";

        selector = scope+" .jxGridCol" + idx + " .jxGridCellContent";
        col.rule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
        col.rule.style.width = col.getWidth() + "px";
      }, this);
    },

    updateRule: function(column) {
        var col = this.getByName(column);
        if (col.options.renderMode === 'fit') {
          col.calculateWidth();
        }
        col.rule.style.width = col.getWidth() + "px";
        col.cellRule.style.width = col.getCellWidth() + "px";
    },

    /**
     * APIMethod: getColumnCount
     * returns the number of columns in this model (including hidden).
     */
    getColumnCount : function (noHidden) {
        noHidden = $defined(noHidden) ? false : true;
        var total = this.columns.length;
        if (noHidden) {
            this.columns.each(function(col){
                if (col.isHidden()) {
                    total -= 1;
                }
            },this);
        }
        return total;
    },
    /**
     * APIMethod: getIndexFromGrid
     * Gets the index of a column from its place in the grid.
     *
     * Parameters:
     * name - the name of the column to get an index for
     */
    getIndexFromGrid : function (name) {
        var headers = this.options.useHeaders ?
                        this.grid.colTableBody.getFirst().getChildren() :
                        this.grid.gridTableBody.getFirst().getChildren(),
            c,
            i = -1,
            self = this;
        headers.each(function (h) {
            i++;
            var hClasses = h.get('class').split(' ').filter(function (cls) {
                if(self.options.useHeaders)
                  return cls.test('jxColHead-');
                else
                  return cls.test('jxCol-');
            });
            hClasses.each(function (cls) {
                if (cls.test(name)) {
                    c = i;
                }
            });
        }, this);
        return c;
    }

});
/*
---

name: Jx.Row

description: Holds information related to display of rows in the grid.

license: MIT-style license.

requires:
 - Jx.Object

provides: [Jx.Row]

...
 */
// $Id: row.js 986 2010-09-15 19:01:47Z pagameba $
/**
 * Class: Jx.Row
 *
 * Extends: <Jx.Object>
 *
 * A class defining a grid row.
 *
 * Inspired by code in the original Jx.Grid class
 *
 * License:
 * Original Copyright (c) 2008, DM Solutions Group Inc.
 * This version Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Row = new Class({

  Family: 'Jx.Row',
    Extends : Jx.Object,

    options : {
        /**
         * Option: useHeaders
         * defaults to false.  If set to true, then a column of row header
         * cells are displayed.
         */
        useHeaders : false,
        /**
         * Option: alternateRowColors
         * defaults to false.  If set to true, then alternating CSS classes
         * are used for rows.
         */
        alternateRowColors : false,
        /**
         * Option: rowClasses
         * object containing class names to apply to rows
         */
        rowClasses : {
            odd : 'jxGridRowOdd',
            even : 'jxGridRowEven',
            all : 'jxGridRowAll'
        },
        /**
         * Option: rowHeight
         * The height of the row. Make it null or 'auto' to auto-calculate.
         */
        rowHeight : 20,
        /**
         * Option: headerWidth
         * The width of the row header. Make it null or 'auto' to auto-calculate
         */
        headerWidth : 40,
        /**
         * Option: headerColumn
         * The name of the column in the model to use as the header
         */
        headerColumn : null
    },
    /**
     * Property: grid
     * A reference to the grid that this row model belongs to
     */
    grid : null,
    /**
     * Property: heights
     * This will hold the calculated height of each row in the grid.
     */
    heights: [],
    /**
     * Property: rules
     * A hash that will hold all of the CSS rules for the rows.
     */
    rules: $H(),

    parameters: ['options','grid'],

    /**
     * APIMethod: init
     * Creates the row model object.
     */
    init : function () {
        this.parent();

        if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
            this.grid = this.options.grid;
        }
    },
    /**
     * APIMethod: getGridRowElement
     * Used to create the TR for the main grid row
     */
    getGridRowElement : function (row, text) {
        var o = this.options,
            rc = o.rowClasses,
            c = o.alternateRowColors ?(row % 2 ? rc.even : rc.odd) : rc.all,
            tr = new Element('tr', {
              'class' : 'jxGridRow'+row+' '+ c,
              html: text || ''
            });
        return tr;
    },
    /**
     * Method: getRowHeaderCell
     * creates the TH for the row's header
     */
    getRowHeaderCell : function (text) {
      text = text ? '<span class="jxGridCellContent">'+text + '</span>' : '';
      return new Element('th', {
        'class' : 'jxGridRowHead',
        html: text
      });
    },
    /**
     * APIMethod: getRowHeaderWidth
     * determines the row header's width.
     */
    getRowHeaderWidth : function () {
      var col, width;
      if (this.options.headerColumn) {
        col = this.grid.columns.getByName(this.options.headerColumn);
        if (!$defined(col.getWidth())) {
          col.calculateWidth(true);
        }
        width = col.getWidth();
      } else {
        width = this.options.headerWidth;
      }
      return width;
    },

    /**
     * APIMethod: getHeight
     * determines and returns the height of a row
     */
    getHeight : function (row) {
      var h = this.options.rowHeight,
          rowEl;
      //this should eventually compute a height, however, we would need
      //a fixed width to do so reliably. For right now, we use a fixed height
      //for all rows.
      if ($defined(this.heights[row])) {
        h = this.heights[row];
      } else if ($defined(this.options.rowHeight)) {
        if (this.options.rowHeight == 'auto') {
          // this.calculateHeight(row);
          h = 20; // TODO calculate?
          rowEl = this.grid.gridTableBody.rows[row]
          if (rowEl) {
            h = rowEl.getContentBoxSize().height;
          }
        } else if (Jx.type(this.options.rowHeight) !== 'number') {
          h = 20; // TODO calculate?
        }
      }
      return h;
    },
    /**
     * Method: calculateHeights
     */
    calculateHeights : function () {
      if (this.options.rowHeight === 'auto' ||
          !$defined(this.options.rowHeight)) {
        //grab all rows in the grid body
        document.id(this.grid.gridTableBody).getChildren().each(function(row){
          row = document.id(row);
          var data = row.retrieve('jxRowData');
          var s = row.getContentBoxSize();
          this.heights[data.row] = s.height;
        },this);
        document.id(this.grid.rowTableHead).getChildren().each(function(row){
          row = document.id(row);
          var data = row.retrieve('jxRowData');
          if (data) {
            var s = row.getContentBoxSize();
            this.heights[data.row] = Math.max(this.heights[data.row],s.height);
            if (Browser.Engine.webkit) {
                //for some reason webkit (Safari and Chrome)
                this.heights[data.row] -= 1;
            }
          }
        },this);
      } else {
        document.id(this.grid.rowTableHead).getChildren().each(function(row,idx){
          this.heights[idx] = this.options.rowHeight;
        }, this);
      }
    },

    /**
     * APIMethod: useHeaders
     * determines and returns whether row headers should be used
     */
    useHeaders : function () {
        return this.options.useHeaders;
    },
    /**
     * APIMethod: getRowHeader
     * creates and returns the header for the current row
     *
     * Parameters:
     * list - Jx.List instance to add the header to
     */
    getRowHeader : function (list) {
        var th = this.getRowHeaderCell();
        //if (this.grid.model.getPosition() === 0) {
        //    var rowWidth = this.getRowHeaderWidth();
        //    th.setStyle("width", rowWidth);
        //}
        th.store('jxCellData', {
            rowHeader: true,
            row: this.grid.model.getPosition()
        });
        list.add(th);
    },
    /**
     * APIMethod: getRowHeaderColumn
     * returns the name of the column that is used for the row header
     */
    getRowHeaderColumn : function () {
        return this.options.headerColumn;
    }
});
/*
---

name: Jx.Plugin

description: Base class for all plugins

license: MIT-style license.

requires:
 - Jx.Object

provides: [Jx.Plugin]

...
 */
// $Id: plugin.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Plugin
 *
 * Extend: <Jx.Object>
 *
 * Base class for all plugins. In order for a plugin to be used it must
 * extend from this class.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin = new Class({

    Family: "Jx.Plugin",

    Extends: Jx.Object,

    options: {},

    /**
     * APIMethod: attach
     * Empty method that must be overridden by subclasses. It is
     * called by the user of the plugin to setup the plugin for use.
     */
    attach: function(obj){
        obj.registerPlugin(this);
    },

    /**
     * APIMethod: detach
     * Empty method that must be overridden by subclasses. It is
     * called by the user of the plugin to remove the plugin.
     */
    detach: function(obj){
        obj.deregisterPlugin(this);
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     *    translations changed.
     */
    changeText: function (lang) {
        //if the mask is being used then recreate it. The code will pull
        //the new text automatically
        if (this.busy) {
            this.setBusy(false);
            this.setBusy(true);
        }
    }
});/*
---

name: Jx.Plugin.Grid

description: Namespace for grid plugins

license: MIT-style license.

requires:
 - Jx.Plugin

provides: [Jx.Plugin.Grid]

...
 */
// $Id: plugin.grid.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Plugin.Grid
 * Grid plugin namespace
 *
 *
 * License:
 * This version Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Grid = {};/*
---

name: Jx.Grid

description: A tabular control that has fixed scrolling headers on the rows and columns like a spreadsheet.

license: MIT-style license.

requires:
 - Jx.Widget
 - Jx.Styles
 - Jx.Layout
 - Jx.Columns
 - Jx.Row
 - Jx.Plugin.Grid
 - Jx.Store
 - Jx.List

provides: [Jx.Grid]

css:
 - grid

images:
 - table_col.png
 - table_row.png

...
 */
// $Id: grid.js 995 2010-10-25 14:47:15Z pagameba $
/**
 * Class: Jx.Grid
 *
 * Extends: <Jx.Widget>
 *
 * A tabular control that has fixed, optional, scrolling headers on the rows
 * and columns like a spreadsheet.
 *
 * Jx.Grid is a tabular control with convenient controls for resizing columns,
 * sorting, and inline editing.  It is created inside another element,
 * typically a div.  If the div is resizable (for instance it fills the page
 * or there is a user control allowing it to be resized), you must call the
 * resize() method of the grid to let it know that its container has been
 * resized.
 *
 * When creating a new Jx.Grid, you can specify a number of options for the
 * grid that control its appearance and functionality. You can also specify
 * plugins to load for additional functionality. Currently Jx provides the
 * following plugins
 *
 * Prelighter - prelights rows, columns, and cells
 * Selector - selects rows, columns, and cells
 * Sorter - sorts rows by specific column
 * Editor - allows editing of cells if the column permits editing
 *
 * Jx.Grid renders data that comes from an external source.  This external
 * source, called the store, must be a Jx.Store or extended from it.
 *
 * Events:
 * gridCellEnter(cell, list) - called when the mouse enters a cell
 * gridCellLeave(cell, list) - called when the mouse leaves a cell
 * gridCellClick(cell) - called when a cell is clicked
 * gridRowEnter(cell, list) - called when the mouse enters a row header
 * gridRowLeave(cell, list) - called when the mouse leaves a row header
 * gridRowClick(cell) - called when a row header is clicked
 * gridColumnEnter(cell, list) - called when the mouse enters a column header
 * gridColumnLeave(cell, list) - called when the mouse leaves a column header
 * gridColumnClick(cell) - called when a column header is clicked
 * gridMouseLeave() - called when the mouse leaves the grid at any point.
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 * This version Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Grid = new Class({
  Family : 'Jx.Grid',
  Extends: Jx.Widget,
  Binds: ['storeLoaded', 'clickColumnHeader', 'moveColumnHeader', 'clickRowHeader', 'moveRowHeader', 'clickCell', 'dblclickCell', 'moveCell', 'leaveGrid', 'resize', 'drawStore', 'scroll', 'addRow', 'removeRow', 'removeRows', 'updateRow', 'storeChangesCompleted'],

  /**
   * Property: pluginNamespace
   * the required variable for plugins
   */
  pluginNamespace: 'Grid',

  options: {
    /**
     * Option: parent
     * the HTML element to create the grid inside. The grid will resize
     * to fill the domObj.
     */
    parent: null,

    template: "<div class='jxWidget'><div class='jxGridContainer jxGridRowCol'></div><div class='jxGridContainer jxGridColumnsContainer'><table class='jxGridTable jxGridHeader jxGridColumns'><thead class='jxGridColumnHead'></thead></table></div><div class='jxGridContainer jxGridHeader jxGridRowContainer'><table class='jxGridTable jxGridRows'><thead class='jxGridRowBody'></thead></table></div><div class='jxGridContainer jxGridContentContainer'><table class='jxGridTable jxGridContent'><tbody class='jxGridTableBody'></tbody></table></div></div>",

    /**
     * Options: columns
     * an object consisting of a columns array that defines the individuals
     * columns as well as containing any options for Jx.Grid.Columns or
     * a Jx.Grid.Columns object itself.
     */
    columns: null,

    /**
     * Option: row
     * Either a Jx.Grid.Row object or a json object defining options for
     * the class
     */
    row : null,

    /**
     * Option: store
     * An instance of Jx.Store
     */
    store: null
  },

  classes: $H({
    domObj: 'jxWidget',
    columnContainer: 'jxGridColumnsContainer',
    colObj: 'jxGridColumns',
    colTableBody: 'jxGridColumnHead',
    rowContainer: 'jxGridRowContainer',
    rowObj: 'jxGridRows',
    rowColContainer: 'jxGridRowCol',
    rowTableBody: 'jxGridRowBody',
    contentContainer: 'jxGridContentContainer',
    gridObj: 'jxGridContent',
    gridTableBody: 'jxGridTableBody'
  }),

  /**
   * Property: columns
   * holds a reference to the columns object
   */
  columns: null,

  /**
   * Property: row
   * Holds a reference to the row object
   */
  row: null,

  parameters: ['store', 'options'],

  /**
   * Property: store
   * holds a reference to the <Jx.Store> that is the store for this
   * grid
   */
  store: null,

  /**
   * Property: styleSheet
   * the name of the dynamic style sheet to use for manipulating styles
   */
  styleSheet: 'JxGridStyles',

  /**
   * Property: hooks
   * a {Hash} of event names for tracking which events have actually been attached
   * to the grid.
   */
  hooks: null,

  /**
   * Property: uniqueId
   * an auto-generated id that is assigned as a class name to the grid's
   * container for scoping generated CSS rules to just this grid
   */
  uniqueId: null,

  /**
   * Constructor: Jx.Grid
   */
  init: function() {
    this.uniqueId = this.generateId('jxGrid_');
    this.store = this.options.store;
    var options = this.options,
        opts;

    if ($defined(options.row)) {
      if (options.row instanceof Jx.Row) {
        this.row = options.row;
        this.row.grid = this;
      } else if (Jx.type(options.row) == 'object') {
        this.row = new Jx.Row($extend({grid: this}, options.row));
      }
    } else {
      this.row = new Jx.Row({grid: this});
    }

    if ($defined(options.columns)) {
        if (options.columns instanceof Jx.Columns) {
            this.columns = options.columns;
            this.columns.grid = this;
        } else if (Jx.type(options.columns) === 'object') {
            this.columns = new Jx.Columns($extend({grid:this}, options.columns));
        }
    } else {
      this.columns = new Jx.Columns({grid: this});
    }

    this.hooks = $H({
      'gridScroll': false,
      'gridColumnEnter': false,
      'gridColumnLeave': false,
      'gridColumnClick': false,
      'gridRowEnter': false,
      'gridRowLeave': false,
      'gridRowClick': false,
      'gridCellClick': false,
      'gridCellDblClick': false,
      'gridCellEnter': false,
      'gridCellLeave': false,
      'gridMouseLeave': false
    });

    this.storeEvents = {
      'storeDataLoaded': this.storeLoaded,
      // 'storeSortFinished': this.drawStore,
      'storeRecordAdded': this.addRow,
      'storeColumnChanged': this.updateRow,
      'storeRecordRemoved': this.removeRow,
      'storeMultipleRecordsRemoved': this.removeRows,
      'storeChangesCompleted': this.storeChangesCompleted
    };


    this.parent();
  },

  wantEvent: function(eventName) {
    var hook = this.hooks.get(eventName);
    if (hook === false) {
      switch(eventName) {
        case 'gridColumnEnter':
        case 'gridColumnLeave':
          this.colObj.addEvent('mousemove', this.moveColumnHeader);
          this.hooks.set({
            'gridColumnEnter': true,
            'gridColumnLeave': true
          });
          break;
        case 'gridColumnClick':
          this.colObj.addEvent('click', this.clickColumnHeader);
          this.hooks.set({
            'gridColumnClick': true
          });
          break;
        case 'gridRowEnter':
        case 'gridRowLeave':
          this.rowObj.addEvent('mousemove', this.moveRowHeader);
          this.hooks.set({
            'gridRowEnter': true,
            'gridRowLeave': true
          });
          break;
        case 'gridRowClick':
          this.rowObj.addEvent('click', this.clickRowHeader);
          this.hooks.set({
            'gridRowClick': true
          });
          break;
        case 'gridCellEnter':
        case 'gridCellLeave':
          this.gridObj.addEvent('mousemove', this.moveCell);
          this.hooks.set({
            'gridCellEnter': true,
            'gridCellLeave': true
          });
          break;
        case 'gridCellClick':
          this.gridObj.addEvent('click', this.clickCell);
          this.hooks.set('gridCellClick', true);
          break;
        case 'gridCellDblClick':
          this.gridObj.addEvent('dblclick', this.dblclickCell);
          this.hooks.set('gridCellDblClick', true);
          break;
        case 'gridMouseLeave':
          this.rowObj.addEvent('mouseleave', this.leaveGrid);
          this.colObj.addEvent('mouseleave', this.leaveGrid);
          this.gridObj.addEvent('mouseleave', this.leaveGrid);
          this.hooks.set('gridMouseLeave', true);
          break;
        case 'gridScroll':
          this.contentContainer.addEvent('scroll', this.scroll);
        default:
          break;
      }
    }
  },

  /**
   * Method: scroll
   * handle the grid scrolling by updating the position of the headers
   */
  scroll : function () {
      this.columnContainer.scrollLeft = this.contentContainer.scrollLeft;
      this.rowContainer.scrollTop = this.contentContainer.scrollTop;
  },

  /**
   * APIMethod: render
   * Create the grid for the current model
   */
  render: function() {
    if (this.domObj) {
      this.redraw();
      return;
    }
    this.parent();
    var store = this.store;

    this.domObj.addClass(this.uniqueId);
    new Jx.Layout(this.domObj, {
      onSizeChange: this.resize
    });

    if (store instanceof Jx.Store) {
      store.addEvents(this.storeEvents);
      if (store.loaded) {
        this.storeLoaded(store);
      }
    }
    if (!this.columns.useHeaders()) {
      this.columnContainer.dispose();
    } else {
      this.wantEvent('gridScroll');
    }

    if (!this.row.useHeaders()) {
      this.rowContainer.dispose();
    } else {
      this.wantEvent('gridScroll');
    }

    this.contentContainer.setStyle('overflow', 'auto');

    // todo: very hacky!  can plugins 'wantEvent' between init and render?
    this.hooks.each(function(value, key) {
      if (value) {
        this.hooks.set(key, false);
        this.wantEvent(key);
      }
    }, this);

    if (document.id(this.options.parent)) {
      this.addTo(this.options.parent);
      this.resize();
    }
  },

  /**
   * APIMethod: resize
   * resize the grid to fit inside its container.  This involves knowing
   * something about the model it is displaying (the height of the column
   * header and the width of the row header) so nothing happens if no model is
   * set
   */
  resize: function() {
    var p = this.domObj.getParent(),
        parentSize = p.getSize(),
        colHeaderHeight = 0,
        rowHeaderWidth = 0;

    if (this.columns.useHeaders()) {
      colHeaderHeight = this.columns.getHeaderHeight();
    }

    if (this.row.useHeaders()) {
      rowHeaderWidth = this.row.getRowHeaderWidth();
    }

    this.rowColContainer.setBorderBoxSize({
        width : rowHeaderWidth,
        height : colHeaderHeight
    });

    this.columnContainer.setStyles({
      top: 0,
      left: rowHeaderWidth
    }).setBorderBoxSize({
      width: parentSize.x - rowHeaderWidth,
      height: colHeaderHeight
    });

    this.rowContainer.setStyles({
      top: colHeaderHeight,
      left: 0
    }).setBorderBoxSize({
      width: rowHeaderWidth,
      height: parentSize.y - colHeaderHeight
    });

    this.contentContainer.setStyles({
      top: colHeaderHeight,
      left: rowHeaderWidth
    }).setBorderBoxSize({
      width: parentSize.x - rowHeaderWidth,
      height: parentSize.y - colHeaderHeight
    });
  },

  /**
   * APIMethod: setStore
   * set the store for the grid to display.  If a store is attached to the
   * grid it is removed and the new store is displayed.
   *
   * Parameters:
   * store - {Object} the store to use for this grid
   */
  setStore: function(store) {
    if (this.store) {
      this.store.removeEvents(this.storeEvents);
    }
    if (store instanceof Jx.Store) {
      this.store = store;
      store.addEvents(this.storeEvents);
      if (store.loaded) {
        this.storeLoaded(store);
      }
      this.render();
      this.domObj.resize();
    } else {
      this.destroyGrid();
    }
  },

  /**
   * APIMethod: getStore
   * gets the store set for this grid.
   */
  getStore: function() {
    return this.store;
  },

  storeLoaded: function(store) {
    this.redraw();
  },

  /**
   */
  storeChangesCompleted: function(results) {
    if (results && results.successful) {

    }
  },

  redraw: function() {
    var store = this.store,
        template = '',
        tr,
        columns = [],
        useRowHeaders = this.row.useHeaders();
    this.fireEvent('beginCreateGrid');

    this.gridObj.getElement('tbody').empty();

    this.hoverColumn = this.hoverRow = this.hoverCell = null;

    // TODO: consider moving whole thing into Jx.Columns ??
    // create a suitable column representation for everything
    // in the store that doesn't already have a representation
    store.options.columns.each(function(col, index) {
      if (!this.columns.getByName(col.name)) {
        var renderer = new Jx.Grid.Renderer.Text(),
            format = $defined(col.format) ? col.format : null,
            template = "<span class='jxGridCellContent'>"+ ($defined(col.label) ? col.label : col.name).capitalize() + "</span>",
            column;
        if ($defined(col.renderer)) {
          if ($type(col.renderer) == 'string') {
            if (Jx.Grid.Renderer[col.renderer.capitalize()]) {
              renderer = new Jx.Grid.Renderer[col.renderer.capitalize()]();
            }
          } else if ($type(col.renderer) == 'object' &&
                     $defined(col.renderer.type) &&
                     Jx.Grid.Renderer[col.renderer.type.capitalize()]) {
            renderer = new Jx.Grid.Renderer[col.renderer.type.capitalize()](col.renderer);
          }
        }
        if (format) {
          if ($type(format) == 'string' &&
              $defined(Jx.Formatter[format.capitalize()])) {
            renderer.options.formatter = new Jx.Formatter[format.capitalize()]();
          } else if ($type(format) == 'object' &&
                     $defined(format.type) &&
                     $defined(Jx.Formatter[format.type.capitalize()])) {
             renderer.options.formatter = new Jx.Formatter[format.type.capitalize()](format);
          }
        }
        column = new Jx.Column({
          grid: this,
          template: template,
          renderMode: $defined(col.renderMode) ? col.renderMode : $defined(col.width) ? 'fixed' : 'fit',
          width: $defined(col.width) ? col.width : null,
          isEditable: $defined(col.editable) ? col.editable : false,
          isSortable: $defined(col.sortable) ? col.sortable : false,
          isResizable: $defined(col.resizable) ? col.resizable : false,
          isHidden: $defined(col.hidden) ? col.hidden : false,
          name: col.name || '',
          renderer: renderer
        });
        columns.push(column);
      }
    }, this);
    this.columns.addColumns(columns);
    if (this.columns.useHeaders()) {
      tr = new Element('tr');
      this.columns.getHeaders(tr);
      tr.adopt(new Element('th', {
        'class': 'jxGridColHead',
        'html': '&nbsp',
        styles: {
          width: 1000
        }
      }))
      this.colObj.getElement('thead').empty().adopt(tr);
    }
    this.columns.calculateWidths();
    this.columns.createRules(this.styleSheet+'Columns', '.'+this.uniqueId);
    this.drawStore();
    this.fireEvent('doneCreateGrid');
  },

  /**
   * APIMethod: addRow
   * Adds a row to the table. Can add to either the beginning or the end
   * based on passed flag
   */
  addRow: function (store, record, position) {
    if (this.store.loaded) {
      if (position === 'bottom') {
        this.store.last();
      } else {
        this.store.first();
      }
      this.drawRow(record, this.store.index, position);
    }
  },

  /**
   * APIMethod: updateRow
   * update a single row in the grid
   *
   * Parameters:
   * index - the row to update
   */
  updateRow: function(index) {
    var record = this.store.getRecord(index);
    this.drawRow(record, index, 'replace');
  },

  /**
   * APIMethod: removeRow
   * remove a single row from the grid
   *
   * Parameters:
   * store
   * index
   */
  removeRow: function (store, index) {
    this.gridObj.deleteRow(index);
    this.rowObj.deleteRow(index);
  },

  /**
   * APIMethod: removeRows
   * removes multiple rows from the grid
   *
   * Parameters:
   * store
   * index
   */
  removeRows: function (store, first, last) {
    for (var i = first; i <= last; i++) {
        this.removeRow(store, first);
    }
  },

  /**
   * APIMethod: setColumnWidth
   * set the width of a column in pixels
   *
   * Parameters:
   * column
   * width
   */
  setColumnWidth: function(column, width) {
    if (column) {
      column.width = width;
      if (column.rule) {
        column.rule.style.width = width + 'px';
      }
      if (column.cellRule) {
        column.cellRule.style.width = width + 'px';
      }
    }
  },

  /**
   * Method: drawStore
   * clears the grid and redraws the store.  Does not draw the column headers,
   * that is handled by the render() method
   */
  drawStore: function() {
    var useHeaders = this.row.useHeaders(),
        blank;
    this.domObj.resize();
    this.gridTableBody.empty();
    if (useHeaders) {
      this.rowTableBody.empty();
    }
    this.store.each(function(record,index) {
      this.store.index = index;
      this.drawRow(record, index);
    }, this);
    if (useHeaders) {
      blank = new Element('tr', {
        styles: { height: 1000 }
      });
      blank.adopt(new Element('th', {
        'class':'jxGridRowHead',
        html: '&nbsp'
      }));
      this.rowTableBody.adopt(blank);
    }
  },

  /**
   * Method: drawRow
   * this method does the heavy lifting of drawing a single record into the
   * grid
   *
   * Parameters:
   * record - {Jx.Record} the record to render
   * index - {Integer} the row index of the record in the store
   * position - {String} 'top' or 'bottom' (default 'bottom') position to put
   *     the new row in the grid.
   */
  drawRow: function(record, index, position) {
    var columns = this.columns,
        body = this.gridTableBody,
        row = this.row,
        store = this.store,
        rowHeaders = row.useHeaders(),
        autoRowHeight = row.options.rowHeight == 'auto',
        rowBody = this.rowTableBody,
        rowHeaderColumn,
        rowHeaderColumnIndex,
        renderer,
        formatter,
        getData,
        tr,
        th,
        text = index + 1,
        rh;
    if (!$defined(position) || !['top','bottom','replace'].contains(position)) {
      position = 'bottom';
    }
    tr = row.getGridRowElement(index, '');
    if (position == 'replace' && index < body.childNodes.length) {
      tr.inject(body.childNodes[index], 'after');
      body.childNodes[index].dispose();
    } else {
      tr.inject(body, position);
    }
    columns.getRow(tr, record);
    if (rowHeaders) {
      if (row.options.headerColumn) {
        rowHeaderColumn = columns.getByName(row.options.headerColumn);
        renderer = rowHeaderColumn.options.renderer;
        if (!renderer.domInsert) {
          formatter = rowHeaderColumn.options.formatter;
          rowHeaderColumnIndex = columns.columns.indexOf(rowHeaderColumn);
          getData = function(record) {
            var data = {},
                text = '';
            if (renderer.options.textTemplate) {
              text = store.fillTemplate(null, renderer.options.textTemplate, renderer.columnsNeeded);
            } else {
              text = record.data.get(rowHeaderColumn.name);
            }
            data['col'+rowHeaderColumnIndex] = text;
            return data;
          };
          text = rowHeaderColumn.getTemplate(rowHeaderColumnIndex).substitute(getData(record));
        } else {
          text = '';
        }
      }
      th = row.getRowHeaderCell(text);
      if (row.options.headerColumn && renderer.domInsert) {
        th.adopt(rowHeaderColumn.getHTML());
      }
      rh = new Element('tr').adopt(th);
      if (position == 'replace' && index < rowBody.childNodes.length) {
        rh.inject(rowBody.childNodes[index], 'after');
        rowBody.childNodes[index].dispose();
      } else {
        rh.inject(rowBody, position);
      }
      if (autoRowHeight) {
        // th.setBorderBoxSize({height: tr.childNodes[0].getBorderBoxSize().height});
        rh.setBorderBoxSize({height: tr.getBorderBoxSize().height});
      }
    }
    this.fireEvent('gridDrawRow', [index, record]);
  },

  /**
   * Method: clickColumnHeader
   * handle clicks on the column header
   */
  clickColumnHeader: function(e) {
    var target = e.target;
    if (target.getParent('thead')) {
      target = target.tagName == 'TH' ? target : target.getParent('th');
      this.fireEvent('gridColumnClick', target);
    }
  },

  /**
   * Method: moveColumnHeader
   * handle the mouse moving over the column header
   */
  moveColumnHeader: function(e) {
    var target = e.target;
    target = target.tagName == 'TH' ? target : target.getParent('th.jxGridColHead');
    if (target) {
      if (this.hoverColumn != target) {
        if (this.hoverColumn) {
          this.fireEvent('gridColumnLeave', this.hoverColumn);
        }
        if (!target.hasClass('jxGridColHead')) {
          this.leaveGrid(e);
        } else {
          this.hoverColumn = target;
          this.fireEvent('gridColumnEnter', target);
        }
      }
    }
  },

  /**
   * Method: clickRowHeader
   * handle clicks on the row header
   */
  clickRowHeader: function(e) {
    var target = e.target;
    if (target.getParent('tbody')) {
      target = target.tagName == 'TH' ? target : target.getParent('th');
      this.fireEvent('gridRowClick', target);
    }
  },

  /**
   * Method: moveRowHeader
   * handle the mouse moving over the row header
   */
  moveRowHeader: function(e) {
    var target = e.target;
    target = target.tagName == 'TH' ? target : target.getParent('th.jxGridRowHead');
    if (target) {
      if (this.hoverRow != target) {
        if (this.hoverRow) {
          this.fireEvent('gridRowLeave', this.hoverRow);
        }
        if (!target.hasClass('jxGridRowHead')) {
          this.leaveGrid(e);
        } else {
          this.hoverRow = target;
          this.fireEvent('gridRowEnter', target);
        }
      }
    }
  },

  /**
   * Method: clickCell
   * handle clicks on cells in the grid
   */
  clickCell: function(e) {
    var target = e.target;
    if (target.getParent('tbody')) {
      target = target.tagName == 'TD' ? target : target.getParent('td');
      this.fireEvent('gridCellClick', target);
    }
  },

  /**
   * Method: dblclickCell
   * handle doubleclicks on cells in the grid
   */
  dblclickCell: function(e) {
    var target = e.target;
    if (target.getParent('tbody')) {
      target = target.tagName == 'TD' ? target : target.getParent('td');
      this.fireEvent('gridCellDblClick', target);
    }
  },

  /**
   * Method: moveCell
   * handle the mouse moving over cells in the grid
   */
  moveCell: function(e) {
    var target = e.target,
        data,
        body,
        row,
        index,
        column;
    target = target.tagName == 'TD' ? target : target.getParent('td.jxGridCell');
    if (target) {
      if (this.hoverCell != target) {
        if (this.hoverCell) {
          this.fireEvent('gridCellLeave', this.hoverCell);
        }
        if (!target.hasClass('jxGridCell')) {
          this.leaveGrid(e);
        } else {
          this.hoverCell = target;
          this.getCellData(target);
          this.fireEvent('gridCellEnter', target);
        }
      }
    }
  },

  getCellData: function(cell) {
    var data = null,
        index,
        column,
        row;
    if (!cell.hasClass('jxGridCell')) {
      cell = cell.getParent('td.jxGridCell');
    }
    if (cell) {
      body = this.gridTableBody;
      row = body.getChildren().indexOf(cell.getParent('tr'));
      this.columns.columns.some(function(col,idx){
        if (cell.hasClass('jxGridCol'+idx)) {
          index = idx;
          column = col;
          return true;
        }
        return false;
      });
      data = {
        row: row,
        column: column,
        index: index
      };
      cell.store('jxCellData', data);
    }
    return data;
  },

  /**
   * Method: leaveGrid
   * handle the mouse leaving the grid
   */
  leaveGrid: function(e) {
    this.hoverCell = null;
    this.fireEvent('gridMouseLeave');
  },

  /**
   * Method: changeText
   * rerender the grid when the language changes
   */
  changeText : function(lang) {
      this.parent();
      this.render();
  },

  /**
   * Method: addEvent
   * override default addEvent to also trigger wanting the event
   * which will then cause the underlying events to be registered
   */
  addEvent: function(name, fn) {
    this.wantEvent(name);
    this.parent(name, fn);
  }
});
/*
---

name: Jx.Grid.Renderer

description: Base class for all renderers. Used to create the contents of column.

license: MIT-style license.

requires:
 - Jx.Grid

provides: [Jx.Grid.Renderer]

...
 */
/**
 * Class: Jx.Grid.Renderer
 * This is the base class and namespace for all grid renderers.
 *
 * Extends: <Jx.Widget>
 * We extended Jx.Widget to take advantage of templating support.
 */
Jx.Grid.Renderer = new Class({

  Family: 'Jx.Grid.Renderer',
  Extends: Jx.Widget,

  parameters: ['options'],

  options: {
    deferRender: true,
    /**
     * Option: template
     * The template for rendering this cell. Will be processed as per
     * the Jx.Widget standard.
     */
    template: '<span class="jxGridCellContent"></span>'
  },
    /**
     * APIProperty: attached
     * tells whether this renderer is used in attached mode
     * or not. Should be set by renderers that get a reference to
     * the store.
     */
  attached: null,

  /**
   * Property: domInsert
   * boolean, indicates if the renderer needs to insert a DOM element
   * instead of just outputing some templated HTML.  Renderers that
   * do use domInsert will be slower.
   */
  domInsert: false,

  classes: $H({
    domObj: 'jxGridCellContent'
  }),

  column: null,

  init: function () {
    this.parent();
    this.attached = false;
  },

  render: function () {
    this.parent();
  },

  setColumn: function (column) {
    if (column instanceof Jx.Column) {
      this.column = column;
    }
  }

});/*
---

name: Jx.Grid.Renderer.Text

description: Renders data as straight text.

license: MIT-style license.

requires:
 - Jx.Grid.Renderer

provides: [Jx.Grid.Renderer.Text]

...
 */
/**
 * Class: Jx.Grid.Renderer.Text
 * This is the default renderer for grid cells. It works the same as the
 * original column implementation. It needs a store, a field name, and an
 * optional formatter as well as other options.
 *
 * Extends: <Jx.Grid.Renderer>
 *
 */
Jx.Grid.Renderer.Text = new Class({

  Family: 'Jx.Grid.Renderer.Text',
  Extends: Jx.Grid.Renderer,

  options: {
        /**
         * Option: formatter
         * an instance of <Jx.Formatter> or one of its subclasses which
         * will be used to format the data in this column. It can also be
         * an object containing the name (This should be the part after
         * Jx.Formatter in the class name. For instance, to get a currency
         * formatter, specify 'Currency' as the name.) and options for the
         * needed formatter (see individual formatters for options).
         * (code)
         * {
         *    name: 'formatter name',
         *    options: {}
         * }
         * (end)
         */
        formatter: null,
        /**
         * Option: textTemplate
         * Will be used for creating the text that goes iside the template. Use
         * placeholders for indicating the field(s). You can add as much text
         * as you want. for example, if you wanted to display someone's full
         * name that is brokem up in the model with first and last names you
         * can write a template like '{lastName}, {firstName}' and as long as
         * the text between { and } are field names in the store they will be
         * substituted properly.
         */
        textTemplate: null,
        /**
         * Option: css
         * A string or function to use in adding classes to the text
         */
        css: null
  },

  store: null,

  columnsNeeded: null,

  init: function () {
      this.parent();
      var options = this.options,
          t;
      //check the formatter
      if ($defined(options.formatter) &&
          !(options.formatter instanceof Jx.Formatter)) {
          t = Jx.type(options.formatter);
          if (t === 'object') {
              // allow users to leave the options object blank
              if(!$defined(options.formatter.options)) {
                  options.formatter.options = {};
              }
              options.formatter = new Jx.Formatter[options.formatter.name](
                      options.formatter.options);
          }
      }
  },

  setColumn: function (column) {
    this.parent();

    this.store = column.grid.getStore();
    this.attached = true;

    if ($defined(this.options.textTemplate)) {
      this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
    }
  },

  render: function () {
    this.parent();

    var text = '';
    if ($defined(this.options.textTemplate)) {
        if (!$defined(this.columnsNeeded) || (Jx.type(this.columnsNeeded) === 'array' && this.columnsNeeded.length === 0)) {
            this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
        }
        text = this.store.fillTemplate(null,this.options.textTemplate,this.columnsNeeded);
    }
    if ($defined(this.options.formatter)) {
        text = this.options.formatter.format(text);
    }

    this.domObj.set('html',text);

    if ($defined(this.options.css) && Jx.type(this.options.css) === 'function') {
      this.domObj.addClass(this.options.css.run(text));
    } else if ($defined(this.options.css) && Jx.type(this.options.css) === 'string'){
      this.domObj.addClass(this.options.css);
    }

  }

});/*
---

name: Jx.Grid.Renderer.Checkbox

description: Renders a checkbox in a column. Can be connected to a store column or as a standalone check column.

license: MIT-style license.

requires:
 - Jx.Grid.Renderer
 - Jx.Field.Checkbox

provides: [Jx.Grid.Renderer.Checkbox]

...
 */
/**
 * Class: Jx.Grid.Renderer.CheckBox
 * Renders a checkbox into the cell. Allows options for connecting the cell
 * to a model field and propogating changes back to the store.
 *
 * Extends: <Jx.Grid.Renderer>
 *
 */
Jx.Grid.Renderer.Checkbox = new Class({

  Family: 'Jx.Grid.Renderer.Checkbox',
  Extends: Jx.Grid.Renderer,

  Binds: ['onBlur','onChange'],

  options: {
    useStore: false,
    field: null,
    updateStore: false,
    checkboxOptions: {
      template : '<input class="jxInputContainer jxInputCheck" type="checkbox" name="{name}"/>',
      name: ''
    }
  },

  domInsert: true,

  init: function () {
    this.parent();
  },

  render: function () {
    this.parent();
    var checkbox = new Jx.Field.Checkbox(this.options.checkboxOptions);
    this.domObj.adopt(document.id(checkbox));

    if (this.options.useStore) {
      //set initial state
      checkbox.setValue(this.store.get(this.options.field));
    }

    //hook up change and blur events to change store field
    checkbox.addEvents({
      'blur': this.onBlur,
      'change': this.onChange
    });
  },

  setColumn: function (column) {
    this.column = column;

    if (this.options.useStore) {
      this.store = this.column.grid.getStore();
      this.attached = true;
    }
  },

  onBlur: function (field) {
    if (this.options.updateStore) {
      this.updateStore(field);
    }
    this.column.grid.fireEvent('checkBlur',[this.column, field]);
  },

  onChange: function (field) {
    if (this.options.updateStore) {
      this.updateStore(field);
    }
    this.fireEvent('change',[this.column, field]);
  },

  updateStore: function (field) {
    var newValue = field.getValue();

    var data = document.id(field).getParent().retrieve('jxCellData');
    var row = data.row;

    if (this.store.get(this.options.field, row) !== newValue) {
      this.store.set(this.options.field, newValue, row);
    }
  }


});/*
---

name: Jx.Grid.Renderer.Button

description: "Renders one or more buttons in a single column.

license: MIT-style license.

requires:
 - Jx.Grid.Renderer
 - Jx.Button


provides: [Jx.Grid.Renderer.Button]

...
 */
/**
 * Class: Jx.Grid.Renderer.Button
 * Renders a <Jx.Button> into the cell. You can add s many buttons as you'd like per column by passing button configs
 * in as an array option to options.buttonOptions
 *
 * Extends: <Jx.Grid.Renderer>
 *
 */
Jx.Grid.Renderer.Button = new Class({

    Family: 'Jx.Grid.Renderer.Button',
    Extends: Jx.Grid.Renderer,

    Binds: [],

    options: {
        template: '<span class="buttons"></span>',
        /**
         * Option: buttonOptions
         * an array of option configurations for <Jx.Button>
         */
        buttonOptions: null
    },

    domInsert: true,

    classes:  $H({
        domObj: 'buttons'
    }),

    init: function () {
        this.parent();
    },

    render: function () {
        this.parent();

        $A(this.options.buttonOptions).each(function(opts){
            var button = new Jx.Button(opts);
            this.domObj.grab(document.id(button));
        },this);

    }
});/*
---

name: Jx.Plugin.Grid.Selector

description: Allows selecting rows, columns, and cells in grids

license: MIT-style license.

requires:
 - Jx.Plugin.Grid

provides: [Jx.Plugin.Grid.Selector]

...
 */
// $Id: grid.selector.js 1003 2010-12-17 20:58:01Z pagameba $
/**
 * Class: Jx.Plugin.Grid.Selector
 *
 * Extends: <Jx.Plugin>
 *
 * Grid plugin to select rows, columns, and/or cells.
 *
 * Original selection code from Jx.Grid's original class
 *
 * License:
 * Original Copyright (c) 2008, DM Solutions Group Inc.
 * This version Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Grid.Selector = new Class({

    Family: 'Jx.Plugin.Grid.Selector',
    Extends : Jx.Plugin,

    name: 'Selector',

    Binds: ['select','checkSelection','checkAll','afterGridRender','onCellClick', 'sort', 'updateCheckColumn', 'updateSelectedRows'],

    options : {
        /**
         * Option: cell
         * determines if cells are selectable
         */
        cell : false,
        /**
         * Option: row
         * determines if rows are selectable
         */
        row : false,
        /**
         * Option: column
         * determines if columns are selectable
         */
        column : false,
        /**
         * Option: multiple
         * Allow multiple selections
         */
        multiple: false,
        /**
         * Option: useCheckColumn
         * Whether to use a check box column as the row header or as the
         * first column in the grid and use it for manipulating selections.
         */
        useCheckColumn: false,
        /**
         * Option: checkAsHeader
         * Determines if the check column is the header of the rows
         */
        checkAsHeader: false,
        /**
         * Option: sortableColumn
         * Determines if the check column is sortable
         */
        sortableColumn: false
    },

    domInsert: true,

    /**
     * Property: selected
     * Holds arrays of selected rows and/or columns and their headers
     */
    selected: null,

    /**
     * APIMethod: init
     * construct a new instance of the plugin.  The plugin must be attached
     * to a Jx.Grid instance to be useful though.
     */
    init: function() {
        this.parent();
        this.selected = $H({
            cells: [],
            columns: [],
            rows: [],
            rowHeads: [],
            columnHeads: []
        });
    },
    /**
     * APIMethod: attach
     * Sets up the plugin and attaches the plugin to the grid events it
     * will be monitoring
     *
     * Parameters:
     * grid - The instance of Jx.Grid to attach to
     */
    attach: function (grid) {
        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
            return;
        }
        this.parent(grid);
        var options = this.options,
            template;
        this.grid = grid;

        this.grid.addEvent('gridSortFinished', this.updateSelectedRows);

        //setup check column if needed
        if (options.useCheckColumn) {
          grid.addEvent('gridDrawRow', this.updateCheckColumn);
          template = '<span class="jxGridCellContent">';
          if (options.multiple) {
            template += '<span class="jxInputContainer jxInputContainerCheck"><input class="jxInputCheck" type="checkbox" name="checkAll" id="checkAll"/></span>';
          } else {
            template += '</span>';
          }

          template += "</span>";

          this.checkColumn = new Jx.Column({
            template: template,
            renderMode: 'fixed',
            width: 20,
            renderer: null,
            name: 'selection',
            isSortable: options.sortableColumn || false,
            sort: options.sortableColumn ? this.sort : null
          }, grid);
          this.checkColumn.options.renderer = this;
          grid.columns.columns.reverse();
          grid.columns.columns.push(this.checkColumn);
          grid.columns.columns.reverse();

          if (options.checkAsHeader) {
              this.oldHeaderColumn = grid.row.options.headerColumn;
              grid.row.options.useHeaders = true;
              grid.row.options.headerColumn = 'selection';

              if (options.multiple) {
                  grid.addEvent('doneCreateGrid', this.afterGridRender);
              }
          }
          //attach event to header
          if (options.multiple) {
              document.id(this.checkColumn).getElement('input').addEvents({
                  'change': this.checkAll
              });
          }
        } else {
          grid.addEvent('gridCellClick', this.onCellClick);
        }
    },

    /**
     * Method: render
     * required for the renderer interface
     */
    render: function() {
      this.domObj = new Element('span', {
        'class': 'jxGridCellContent'
      });
      new Element('input', {
        'class': 'jxGridSelector',
        type: 'checkbox',
        events: {
          change: this.checkSelection
        }
      }).inject(this.domObj);
    },

    /**
     * Method: toElement
     * required for the Renderer interface
     */
    toElement: function() {
      return this.domObj;
    },

    /**
     * Method: updateCheckColumn
     * check to see if a row needs to have its checkbox updated after its been drawn
     *
     * Parameters:
     * index - {Integer} the row that was just rendered
     * record - {<Jx.Record>} the record that was rendered into that row
     */
    updateCheckColumn: function(index, record) {
      var state = this.selected.get('rows').contains(index),
          r = this.grid.gridTableBody.rows,
          tr = document.id((index >= 0 && index < r.length) ? r[index] : null);

      if (tr) {
        tr.store('jxRowData', {row: index});
        if (state) {
          tr.addClass('jxGridRowSelected');
        } else {
          tr.removeClass('jxGridRowSelected');
        }
        this.setCheckField(index, state);
      }
    },

    /**
     * Method: afterGridRender
     */
    afterGridRender: function () {
        if (this.options.checkAsHeader) {
            var chkCol = document.id(this.checkColumn).clone();
            chkCol.getElement('input').addEvent('change',this.checkAll);
            this.grid.rowColContainer.adopt(chkCol);
        }
        this.grid.removeEvent('doneCreateGrid',this.afterGridRender);
    },
    /**
     * APIMethod: detach
     */
    detach: function() {
        var grid = this.grid,
            options = this.options,
            col;
        if (grid) {
            grid.gridTableBody.removeEvents({
              click: this.onCellClick
            });
            if (this.checkColumn) {
                grid.columns.columns.erase(this.checkColumn);
                this.checkColumn.destroy();
                this.checkColumn = null;
            }
            if (options.useCheckColumn) {
                grid.removeEvent('gridDrawRow', this.updateCheckColumn);
                if (options.checkAsHeader) {
                    grid.row.options.headerColumn = this.oldHeaderColumn;
                }
            }
        }
        this.grid.removeEvent('gridSortFinished', this.updateSelectedRows);

        this.grid = null;
    },
    /**
     * APIMethod: activate
     * Allows programatic access to turning selections on.
     *
     * Parameters:
     * opt - the option to turn on. One of 'cell', 'column', or 'row'
     */
    activate: function (opt) {
        this.options[opt] = true;
    },
    /**
     * APIMethod: deactivate
     * Allows programatic access to turning selections off.
     *
     * Parameters:
     * opt - the option to turn off. One of 'cell', 'column', or 'row'
     */
    deactivate: function (opt) {
        var gridTableRows = this.grid.gridTableBody.rows,
            selected = this.selected,
            i;
        this.options[opt] = false;
        if (opt === 'cell') {
            selected.get('cells').each(function(cell) {
              cell.removeClass('jxGridCellSelected');
            });
            selected.set('cells',[]);
        } else if (opt === 'row') {
          this.getSelectedRows().each(function(row){
            idx = row.retrieve('jxRowData').row;
            row.removeClass('jxGridRowSelected');
            this.setCheckField(idx,false);
          }, this);
          selected.set('rows',[]);
          selected.get('rowHeads').each(function(rowHead){
            rowHead.removeClass('jxGridRowHeaderSelected');
          });
          selected.set('rowHeads',[]);
        } else {
            selected.get('columns').each(function(column){
                for (i = 0; i < gridTableRows.length; i++) {
                    gridTableRows[i].cells[column].removeClass('jxGridColumnSelected');
                }
            });
            selected.set('columns',[]);

            selected.get('columnHeads').each(function(rowHead){
            rowHead.removeClass('jxGridColumnHeaderSelected');
          },this);
          selected.set('columnHeads',[]);
        }
    },

    /**
     * Method: onCellClick
     * dispatch clicking on a table cell
     */
    onCellClick: function(cell) {
        if (cell) {
            this.select(cell);
        }
    },

    /**
     * Method: select
     * dispatches the grid click to the various selection methods
     */
    select : function (cell) {
        var data = cell.retrieve('jxCellData'),
            options = this.options,
            col;

        if (options.cell && $defined(data.row) && $defined(data.index)) {
          this.selectCell(cell);
        }

        if (options.row && $defined(data.row)) {
            this.selectRow(data.row);
        }

        if (options.column && $defined(data.index)) {
            if (this.grid.row.useHeaders()) {
                this.selectColumn(data.index - 1);
            } else {
                this.selectColumn(data.index);
            }
        }
    },

    /**
     * Method: selectCell
     * select a cell
     *
     * Parameters:
     * cell - {DOMElement} the cell element to select
     */
    selectCell: function(cell) {
        if (!this.options.cell) { return; }
        var cells = this.selected.get('cells');
        if (cell.hasClass('jxGridCellSelected')) {
          cell.removeClass('jxGridCellSelected');
          cells.erase(cell);
          this.fireEvent('unselectCell', cell);
        } else {
          cell.addClass('jxGridCellSelected');
          cells.push(cell);
          this.fireEvent('selectCell', cell);
        }
    },

    updateSelectedRows: function() {
      if (!this.options.row) { return; }
      var options = this.options,
          r = this.grid.gridTableBody.rows,
          rows = [];

      for (var i=0; i<r.length; i++) {
        if (r[i].hasClass('jxGridRowSelected')) {
          rows.push(i);
        }
      }
      this.selected.set('rows', rows);
    },

    /**
     * Method: selectRow
     * Select a row and apply the jxGridRowSelected style to it.
     *
     * Parameters:
     * row - {Integer} the row to select
     */
    selectRow: function (row, silently) {
        if (!this.options.row) { return; }
        var options = this.options,
            r = this.grid.gridTableBody.rows,
            tr = document.id((row >= 0 && row < r.length) ? r[row] : null),
            rows = this.selected.get('rows'),
            silently = $defined(silently) ? silently : false;
        if (tr) {
            if (tr.hasClass('jxGridRowSelected')) {
                tr.removeClass('jxGridRowSelected');
                this.setCheckField(row, false);
                if (options.multiple && options.useCheckColumn) {
                    if (options.checkAsHeader) {
                        document.id(this.grid.rowColContainer).getElement('input').removeProperty('checked');
                    } else {
                        document.id(this.checkColumn).getElement('input').removeProperty('checked');
                    }
                }
                //search array and remove this item
                rows.erase(row);
                if (!silently) {
                  this.fireEvent('unselectRow', row);
                }
            } else {
                tr.store('jxRowData', {row: row});
                rows.push(row);
                tr.addClass('jxGridRowSelected');
                this.setCheckField(row, true);
                if (!silently) {
                  this.fireEvent('selectRow', row);
                }
            }

            if (!this.options.multiple) {
                var unselected = [];
                this.getSelectedRows().each(function(row) {
                  var idx;
                  if (row !== tr) {
                    idx = row.retrieve('jxRowData').row;
                    row.removeClass('jxGridRowSelected');
                    this.setCheckField(idx,false);
                    rows.erase(row);
                    unselected.push(idx);
                    if (!silently) {
                      this.fireEvent('unselectRow', row);
                    }
                  }

                }, this);
                if (unselected.length && !silently) {
                  this.fireEvent('unselectRows', [unselected]);
                }
            }
        }
        this.selectRowHeader(row);
    },

    /**
     * Method: setCheckField
     */
    setCheckField: function (row, checked) {
        var grid = this.grid,
            options = this.options,
            check,
            col,
            cell;
        if (options.useCheckColumn) {
            if (options.checkAsHeader) {
              cell = document.id(grid.rowTableBody.rows[row].cells[0]);
            } else {
              col = grid.columns.getIndexFromGrid(this.checkColumn.name);
              cell = document.id(grid.gridTableBody.rows[row].cells[col]);
            }
            check = cell.getElement('.jxGridSelector')
            check.set('checked', checked);
        }
    },
    /**
     * Method: selectRowHeader
     * Apply the jxGridRowHeaderSelected style to the row header cell of a
     * selected row.
     *
     * Parameters:
     * row - {Integer} the row header to select
     */
    selectRowHeader: function (row) {
        if (!this.grid.row.useHeaders()) {
            return;
        }
        var rows = this.grid.rowTableBody.rows,
            cell = document.id((row >= 0 && row < rows.length) ?
                              rows[row].cells[0] : null),
            cells;

        if (!cell) {
            return;
        }
        cells = this.selected.get('rowHeads');
        if (cells.contains(cell)) {
            cell.removeClass('jxGridRowHeaderSelected');
            cells.erase(cell);
        } else {
          cell.addClass('jxGridRowHeaderSelected');
          cells.push(cell);
        }

        if (!this.options.multiple) {
          cells.each(function(c){
            if (c !== cell) {
              c.removeClass('jxGridRowHeaderSelected');
              cells.erase(c);
            }
          },this);
        }

    },
    /**
     * Method: selectColumn
     * Select a column.
     * This deselects a previously selected column.
     *
     * Parameters:
     * col - {Integer} the column to select
     */
    selectColumn: function (col) {
        var gridTable = this.grid.gridTableBody,
            cols = this.selected.get('columns'),
            m = '',
            i;
        if (col >= 0 && col < gridTable.rows[0].cells.length) {
            if (cols.contains(col)) {
                //deselect
                m = 'removeClass';
                cols.erase(col);
                this.fireEvent('unselectColumn', col);
            } else {
                //select
                m = 'addClass';
                cols.push(col);
                this.fireEvent('selectColumn', col);
            }
            for (i = 0; i < gridTable.rows.length; i++) {
                gridTable.rows[i].cells[col][m]('jxGridColumnSelected');
            }

            if (!this.options.multiple) {
                cols.each(function(c){
                  if (c !== col) {
                      for (i = 0; i < gridTable.rows.length; i++) {
                          gridTable.rows[i].cells[c].removeClass('jxGridColumnSelected');
                      }
                      cols.erase(c);
                      this.fireEvent('unselectColumn', c);
                  }
                }, this);
            }
            this.selectColumnHeader(col);
        }
    },
    /**
     * method: selectColumnHeader
     * Apply the jxGridColumnHeaderSelected style to the column header cell of a
     * selected column.
     *
     * Parameters:
     * col - {Integer} the column header to select
     */
    selectColumnHeader: function (col) {
        var rows = this.grid.colTableBody;
        if (rows.length === 0 || !this.grid.row.useHeaders()) {
            return;
        }

        var cell = (col >= 0 && col < rows[0].cells.length) ?
            rows[0].cells[col] : null;

        if (cell === null) {
            return;
        }

        cell = document.id(cell);
        cells = this.selected.get('columnHeads');

        if (cells.contains(cell)) {
            cell.removeClass('jxGridColumnHeaderSelected');
            cells.erase(cell);
        } else {
          cell.addClass('jxGridColumnHeaderSelected');
          cells.push(cell);
        }

        if (!this.options.multiple) {
          cells.each(function(c){
            if (c !== cell) {
              c.removeClass('jxGridColumnHeaderSelected');
              cells.erase(c);
            }
          });
        }
    },
    /**
     * Method: checkSelection
     * Checks whether a row's check box is/isn't checked and modifies the
     * selection appropriately.
     *
     * Parameters:
     * column - <Jx.Column> that created the checkbox
     * field - <Jx.Field.Checkbox> instance that was checked/unchecked
     * created the checkbox
     */
    checkSelection: function (event) {
      var cell =  event.target.getParent('tr'),
          row;
      if (cell) {
        row = cell.getParent().getChildren().indexOf(cell);
        this.selectRow(row);
      }
    },
    /**
     * Method: checkAll
     * Checks all checkboxes in the column the selector inserted.
     */
    checkAll: function () {
        var grid = this.grid,
            col,
            rows,
            selection = [],
            checked = this.options.checkAsHeader ?
                          grid.rowColContainer.getElement('input').get('checked') :
                          this.checkColumn.domObj.getElement('input').get('checked'),
            event = checked ? 'selectRows' : 'unselectRows';

        if (this.options.checkAsHeader) {
            col = 0;
            rows = grid.rowTableBody.rows;
        } else {
            col = grid.columns.getIndexFromGrid(this.checkColumn.name);
            rows = grid.gridTableBody.rows;
        }

        $A(rows).each(function(row, idx) {
            var check = row.cells[col].getElement('input');
            if ($defined(check)) {
                var rowChecked = check.get('checked');
                if (rowChecked !== checked) {
                    this.selectRow(idx, true);
                    selection.push(idx);
                }
            }
        }, this);

        this.fireEvent(event, [selection]);
    },

    sort: function(dir) {
      var grid = this.grid,
          store = grid.store,
          data = store.data,
          gridTableBody= grid.gridTableBody,
          gridParent = gridTableBody.getParent(),
          useHeaders = grid.row.useHeaders(),
          rowTableBody = grid.rowTableBody,
          rowParent = rowTableBody.getParent(),
          selected = this.getSelectedRows();

      // sorting only works for rows and when more than zero are selected
      // in fact it is probably only useful if multiple selections are also enabled
      // but that is not a hard rule for this method
      if (!this.options.row || selected.length == 0) {
        console.log('not sorting by selection, nothing to sort');
        return;
      }

      store.each(function(record, index) {
        record.dom = {
          cell: gridTableBody.childNodes[index],
          row: useHeaders ? rowTableBody.childNodes[index] : null
        };
      });

      gridTableBody.dispose();
      if (useHeaders) {
        rowTableBody.dispose();
      }
      selected.sort(function(a,b) {
        return a.retrieve('jxRowData').row - b.retrieve('jxRowData').row;
      }).each(function(row) {
        console.log('moving row ' + row.retrieve('jxRowData').row + ' to beginning of array');
        data.unshift(data.splice(row.retrieve('jxRowData').row,1)[0]);
      });

      if (dir == 'desc') {
        data.reverse();
      }

      store.each(function(record, index) {
        record.dom.cell.inject(gridTableBody);
        record.dom.cell.store('jxRowData', {row: index});
        if (useHeaders) {
          record.dom.row.inject(rowTableBody);
        }
      });

      if (gridParent) {
        gridParent.adopt(gridTableBody);
      }
      if (useHeaders && rowParent) {
        rowParent.adopt(rowTableBody);
      }
    },

    getSelectedRows: function() {
      var rows = [],
          selected = this.selected.get('rows'),
          r = this.grid.gridTableBody.rows;
      selected.each(function(row) {
        var tr = document.id((row >= 0 && row < r.length) ? r[row] : null);
        if (tr) {
          rows.push(tr);
        }
      });
      return rows;
    }
});
/*
---

name: Jx.Plugin.Grid.Prelighter

description: Highlights rows, columns, cells, and headers in grids

license: MIT-style license.

requires:
 - Jx.Plugin.Grid

provides: [Jx.Plugin.Grid.Prelighter]

...
 */
// $Id: grid.prelighter.js 981 2010-09-13 12:18:35Z pagameba $
/**
 * Class: Jx.Plugin.Grid.Prelighter
 *
 * Extends: <Jx.Plugin>
 *
 * Grid plugin to prelight rows, columns, and cells
 *
 * Inspired by the original code in Jx.Grid
 *
 * License:
 * Original Copyright (c) 2008, DM Solutions Group Inc.
 * This version Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Grid.Prelighter = new Class({

    Extends : Jx.Plugin,

    name: 'Prelighter',

    options : {
        /**
         * Option: cell
         * defaults to false.  If set to true, the cell under the mouse is
         * highlighted as the mouse moves.
         */
        cell : false,
        /**
         * Option: row
         * defaults to false.  If set to true, the row under the mouse is
         * highlighted as the mouse moves.
         */
        row : false,
        /**
         * Option: column
         * defaults to false.  If set to true, the column under the mouse is
         * highlighted as the mouse moves.
         */
        column : false,
        /**
         * Option: rowHeader
         * defaults to false.  If set to true, the row header of the row under
         * the mouse is highlighted as the mouse moves.
         */
        rowHeader : false,
        /**
         * Option: columnHeader
         * defaults to false.  If set to true, the column header of the column
         * under the mouse is highlighted as the mouse moves.
         */
        columnHeader : false
    },
    /**
     * APIMethod: init
     * construct a new instance of the plugin.  The plugin must be attached
     * to a Jx.Grid instance to be useful though.
     */
    init: function() {
        this.parent();
        this.bound.lighton = this.lighton.bind(this);
        this.bound.lightoff = this.lightoff.bind(this);
        this.bound.mouseleave = this.mouseleave.bind(this);
    },
    /**
     * APIMethod: attach
     * Sets up the plugin and connects it to the grid
     */
    attach: function (grid) {
        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
            return;
        }
        this.parent(grid);
        this.grid = grid;
        // this.grid.wantEvent('gridCellEnter');
        // this.grid.wantEvent('gridCellLeave');
        // this.grid.wantEvent('gridRowEnter');
        // this.grid.wantEvent('gridRowLeave');
        // this.grid.wantEvent('gridColumnEnter');
        // this.grid.wantEvent('gridColumnLeave');
        // this.grid.wantEvent('gridMouseLeave');

        this.grid.addEvent('gridCellEnter', this.bound.lighton);
        this.grid.addEvent('gridCellLeave', this.bound.lightoff);
        this.grid.addEvent('gridRowEnter', this.bound.lighton);
        this.grid.addEvent('gridRowLeave', this.bound.lightoff);
        this.grid.addEvent('gridColumnEnter', this.bound.lighton);
        this.grid.addEvent('gridColumnLeave', this.bound.lightoff);
        this.grid.addEvent('gridMouseLeave', this.bound.mouseleave);
    },
    /**
     * APIMethod: detach
     */
    detach: function() {
        if (this.grid) {
            this.grid.removeEvent('gridCellEnter', this.bound.lighton);
            this.grid.removeEvent('gridCellLeave', this.bound.lightoff);
            this.grid.removeEvent('gridRowEnter', this.bound.lighton);
            this.grid.removeEvent('gridRowLeave', this.bound.lightoff);
            this.grid.removeEvent('gridColumnEnter', this.bound.lighton);
            this.grid.removeEvent('gridColumnLeave', this.bound.lightoff);
            this.grid.removeEvent('gridMouseLeave', this.bound.mouseleave);
        }
        this.grid = null;
    },
    /**
     * APIMethod: activate
     * Allows programatic access to turning prelighting on.
     *
     * Parameters:
     * opt - the option to turn on. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
     */
    activate: function (opt) {
        this.options[opt] = true;
    },
    /**
     * APIMethod: deactivate
     * Allows programatic access to turning prelighting off.
     *
     * Parameters:
     * opt - the option to turn off. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
     */
    deactivate: function (opt) {
        this.options[opt] = false;
    },
    /**
     * Method: lighton
     */
    lighton : function (cell) {
        this.light(cell, true);

    },
    /**
     * Method: lightoff
     */
    lightoff : function (cell) {
        this.light(cell, false);

    },
    /**
     * Method: light
     * dispatches the event to the various prelight methods.
     */
    light: function (cell, on) {
        var parent = cell.getParent(),
            rowIndex = parent.getParent().getChildren().indexOf(parent),
            colIndex = cell.getParent().getChildren().indexOf(cell);

        if (this.options.cell) {
            this.prelightCell(cell, on);
        }
        if (this.options.row) {
            this.prelightRow(rowIndex, on);
        }
        if (this.options.column) {
            this.prelightColumn(colIndex, on);
        }
        if (this.options.rowHeader) {
            this.prelightRowHeader(rowIndex, on);
        }
        if (this.options.columnHeader) {
            this.prelightColumnHeader(colIndex, on);
        }
    },

    /**
     * Method: prelightRowHeader
     * apply the jxGridRowHeaderPrelight style to the header cell of a row.
     * This removes the style from the previously pre-lit row header.
     *
     * Parameters:
     * row - {Integer} the row to pre-light the header cell of
     */
    prelightRowHeader : function (row, on) {
        if ($defined(this.prelitRowHeader) && !on) {
            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
        } else if (on) {
            this.prelitRowHeader = (row >= 0 && row < this.grid.rowTableBody.rows.length) ? this.grid.rowTableBody.rows[row].cells[0] : null;
            if (this.prelitRowHeader) {
                this.prelitRowHeader.addClass('jxGridRowHeaderPrelight');
            }
        }
    },
    /**
     * Method: prelightColumnHeader
     * apply the jxGridColumnHeaderPrelight style to the header cell of a column.
     * This removes the style from the previously pre-lit column header.
     *
     * Parameters:
     * col - {Integer} the column to pre-light the header cell of
     * on - flag to tell if we're lighting on or off
     */
    prelightColumnHeader : function (col, on) {
        if (this.grid.colTableBody.rows.length === 0) {
            return;
        }

        if ($defined(this.prelitColumnHeader) && !on) {
            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
        } else if (on) {
            this.prelitColumnHeader = (col >= 0 && col < this.grid.colTableBody.rows[0].cells.length) ? this.grid.colTableBody.rows[0].cells[col] : null;
            if (this.prelitColumnHeader) {
                this.prelitColumnHeader.addClass('jxGridColumnHeaderPrelight');
            }
        }

    },
    /**
     * Method: prelightRow
     * apply the jxGridRowPrelight style to row.
     * This removes the style from the previously pre-lit row.
     *
     * Parameters:
     * row - {Integer} the row to pre-light
     * on - flag to tell if we're lighting on or off
     */
    prelightRow : function (row, on) {
       if ($defined(this.prelitRow) && !on) {
            this.prelitRow.removeClass('jxGridRowPrelight');
        } else if (on) {
            this.prelitRow = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row] : null;
            if (this.prelitRow) {
                this.prelitRow.addClass('jxGridRowPrelight');
            }
        }
        this.prelightRowHeader(row, on);
    },
    /**
     * Method: prelightColumn
     * apply the jxGridColumnPrelight style to a column.
     * This removes the style from the previously pre-lit column.
     *
     * Parameters:
     * col - {Integer} the column to pre-light
     * on - flag to tell if we're lighting on or off
     */
    prelightColumn : function (col, on) {
        if (col >= 0 && col < this.grid.gridTableBody.rows[0].cells.length) {
            if ($defined(this.prelitColumn) && !on) {
                for (var i = 0; i < this.grid.gridTableBody.rows.length; i++) {
                    this.grid.gridTableBody.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
                }
            } else if (on) {
                this.prelitColumn = col;
                for (i = 0; i < this.grid.gridTableBody.rows.length; i++) {
                    this.grid.gridTableBody.rows[i].cells[col].addClass('jxGridColumnPrelight');
                }
            }
            this.prelightColumnHeader(col, on);
        }
    },
    /**
     * Method: prelightCell
     * apply the jxGridCellPrelight style to a cell.
     * This removes the style from the previously pre-lit cell.
     *
     * Parameters:
     * cell - the cell to lighton/off
     * on - flag to tell if we're lighting on or off
     */
    prelightCell : function (cell, on) {
        if ($defined(this.prelitCell) && !on) {
            this.prelitCell.removeClass('jxGridCellPrelight');
        } else if (on) {
            this.prelitCell = cell;
            if (this.prelitCell) {
                this.prelitCell.addClass('jxGridCellPrelight');
            }
        }
    },

    mouseleave: function() {
        //turn off all prelights when the mouse leaves the grid
        if ($defined(this.prelitCell)) {
            this.prelitCell.removeClass('jxGridCellPrelight');
        }
        if ($defined(this.prelitColumn)) {
            for (var i = 0; i < this.grid.gridTableBody.rows.length; i++) {
                this.grid.gridTableBody.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
            }
        }
        if ($defined(this.prelitRow)) {
            this.prelitRow.removeClass('jxGridRowPrelight');
        }
        if ($defined(this.prelitColumnHeader)) {
            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
        }
        if ($defined(this.prelitRowHeader)) {
            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
        }
    }
});
/*
---

name: Jx.Plugin.Grid.Sorter

description: Enables column sorting in grids

license: MIT-style license.

requires:
 - Jx.Plugin.Grid

provides: [Jx.Plugin.Grid.Sorter]

images:
 - emblems.png
...
 */
// $Id: grid.sorter.js 1002 2010-12-17 20:57:31Z pagameba $
/**
 * Class: Jx.Plugin.Grid.Sorter
 *
 * Extends: <Jx.Plugin>
 *
 * Grid plugin to sort the grid by a single column.
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Grid.Sorter = new Class({
  Family: 'Jx.Plugin.Grid.Sorter',
  Extends: Jx.Plugin,
  name: 'Sorter',

  Binds: ['sort', 'modifyHeaders'],

  /**
   * Property: current
   * refernce to the currently sorted column
   */
  current: null,

  /**
   * Property: direction
   * tell us what direction the sort is in (either 'asc' or 'desc')
   */
  direction: null,

  options: {
    sortableClass: 'jxColSortable',
    ascendingClass: 'jxGridColumnSortedAsc',
    descendingClass: 'jxGridColumnSortedDesc'
  },

  /**
   * APIMethod: attach
   * Sets up the plugin and attaches the plugin to the grid events it
   * will be monitoring
   */
  attach: function(grid) {
    if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
        return;
    }
    this.parent(grid);

    this.grid = grid;

    // this.grid.wantEvent('gridColumnClick');
    this.grid.addEvent('gridColumnClick', this.sort);
    this.grid.addEvent('doneCreateGrid', this.modifyHeaders);
  },

  /**
   * APIMethod: detach
   */
  detach: function() {
    if (this.grid) {
        this.grid.removeEvent('gridColumnClick', this.sort);
    }
    this.grid = null;
  },

  /**
   * Method: modifyHeaders
   */
  modifyHeaders: function() {
    var grid = this.grid,
        columnTable = grid.colObj,
        store = grid.store,
        c = this.options.sortableClass;
    if (grid.columns.useHeaders()) {
      grid.columns.columns.each(function(col, index) {
        if (!col.isHidden() && col.isSortable()) {
          var th = columnTable.getElement('.jxGridCol'+index);
          th.addClass(c);
        }
      });
    }
  },

  /**
   * Method: sort
   * called when a grid header is clicked.
   *
   * Parameters:
   * cell - The cell clicked
   */
  sort: function(el) {
    var current = this.current,
        grid = this.grid,
        gridTableBody = grid.gridTableBody,
        gridParent = gridTableBody.getParent(),
        rowTableBody = grid.rowTableBody,
        rowParent = rowTableBody.getParent(),
        useHeaders = grid.row.useHeaders(),
        store = grid.store,
        sorter = store.getStrategy('sort'),
        data = el.retrieve('jxCellData'),
        dir = 'asc',
        opt = this.options;

    if ($defined(data.column) && data.column.isSortable()){
      if (el.hasClass(opt.ascendingClass)) {
        el.removeClass(opt.ascendingClass).addClass(opt.descendingClass);
        dir = 'desc';
      } else if (el.hasClass(opt.descendingClass)) {
        el.removeClass(opt.descendingClass).addClass(opt.ascendingClass);
      } else {
        el.addClass(opt.ascendingClass);
      }
      if (current && el != current) {
        current.removeClass(opt.ascendingClass).removeClass(opt.descendingClass);
      }
      this.current = el;

      this.grid.fireEvent('gridSortStarting');

      if ($defined(data.column.options.sort) && Jx.type(data.column.options.sort) == 'function') {
        data.column.options.sort(dir);
      } else {
        if (sorter) {
          gridTableBody.dispose();
          if (useHeaders) {
            rowTableBody.dispose();
          }
          store.each(function(record, index) {
            record.dom = {
              cell: gridTableBody.childNodes[index],
              row: useHeaders ? rowTableBody.childNodes[index] : null
            };
          });

          sorter.sort(data.column.name, null, dir);

          store.each(function(record, index) {
            record.dom.cell.inject(gridTableBody);
            if (useHeaders) {
              record.dom.row.inject(rowTableBody);
            }
          });

          if (gridParent) {
            gridParent.adopt(gridTableBody);
          }
          if (useHeaders && rowParent) {
            rowParent.adopt(rowTableBody);
          }
        }
      }
      this.grid.fireEvent('gridSortFinished');
    }
  }
});/*
---

name: Jx.Plugin.Grid.Resize

description: Enables column resizing in grids

license: MIT-style license.

requires:
 - Jx.Plugin.Grid

provides: [Jx.Plugin.Grid.Resize]

...
 */
// $Id: grid.resize.js 992 2010-10-07 19:28:37Z pagameba $
/**
 * Class: Jx.Plugin.Grid.Resize
 *
 * Extends: <Jx.Plugin>
 *
 * Grid plugin to enable dynamic resizing of column width and row height
 *
 *
 * License:
 * Copyright (c) 2009, DM Solutions Group.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Grid.Resize = new Class({

    Extends : Jx.Plugin,

    name: 'Resize',

    Binds: ['createHandles','removeHandles'],
    options: {
        /**
         * Option: column
         * set to true to make column widths resizeable
         */
        column: false,
        /**
         * Option: row
         * set to true to make row heights resizeable
         */
        row: false,
        /**
         * Option: tooltip
         * the tooltip to display for the draggable portion of the
         * cell header, localized with MooTools.lang.get('Jx','plugin.resize').tooltip for default
         */
        tooltip: ''
    },
    /**
     * Property: els
     * the DOM elements by which the rows/columns are resized.
     */
    els: {
      column: [],
      row: []
    },

    /**
     * Property: drags
     * the Drag instances
     */
    drags: {
      column: [],
      row: []
    },

    /**
     * APIMethod: attach
     * Sets up the plugin and connects it to the grid
     */
    attach: function (grid) {
      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
          return;
      }
      this.parent(grid);
      this.grid = grid;
      if (grid.columns.useHeaders()) {
        this.grid.addEvent('doneCreateGrid', this.createHandles);
        this.grid.addEvent('beginCreateGrid', this.removeHandles);
        this.createHandles();
      }
    },
    /**
     * APIMethod: detach
     */
    detach: function() {
      this.parent();
      if (this.grid) {
          this.grid.removeEvent('doneCreateGrid', this.createHandles);
          this.grid.removeEvent('beginCreateGrid', this.removeHandles);
      }
      this.grid = null;
    },

    /**
     * APIMethod: activate
     */
    activate: function(option) {
        if ($defined(this.options[option])) {
          this.options[option] = true;
        }
        if (this.grid.columns.useHeaders()) {
          this.createHandles();
        }
    },

    /**
     * APIMethod: deactivate
     */
    deactivate: function(option) {
        if ($defined(this.options[option])) {
          this.options[option] = false;
        }
        this.createHandles();
    },
    /**
     * Method: removeHandles
     * clean up any handles we created
     */
    removeHandles: function() {
        ['column','row'].each(function(option) {
          this.els[option].each(function(el) { el.dispose(); } );
          this.els[option] = [];
          this.drags[option].each(function(drag){ drag.detach(); });
          this.drags[option] = [];
        }, this);
    },
    /**
     * Method: createHandles
     * create handles that let the user drag to resize columns and rows
     */
    createHandles: function() {
      var grid = this.grid,
          store = grid.store;
      this.removeHandles();
      if (this.options.column && grid.columns.useHeaders()) {
        grid.columns.columns.each(function(col, idx) {
          if (col.isResizable() && !col.isHidden()) {
            var colEl = grid.colObj.getElement('.jxGridCol'+idx+ ' .jxGridCellContent');
            var el = new Element('div', {
              'class':'jxGridColumnResize',
              title: this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip),
              events: {
                dblclick: function() {
                  // size to fit?
                }
              }
            }).inject(colEl);
            this.els.column.push(el);
            this.drags.column.push(new Drag(el, {
                limit: {y:[0,0]},
                snap: 2,
                onBeforeStart: function(el) {
                  var l = el.getPosition(el.parentNode).x.toInt();
                  el.setStyles({
                    left: l,
                    right: null
                  });

                },
                onStart: function(el) {
                  var l = el.getPosition(el.parentNode).x.toInt();
                  el.setStyles({
                    left: l,
                    right: null
                  });
                },
                onDrag: function(el) {
                    var w = el.getPosition(el.parentNode).x.toInt();
                    col.setWidth(w);
                },
                onComplete: function(el) {
                  el.setStyle('left', null);
                }
            }));
          }
        }, this);
      }
      //if (this.options.row && this.grid.row.useHeaders()) {}
    },
    /**
     * Method: createText
     * respond to a language change by updating the tooltip
     */
    changeText: function (lang) {
      this.parent();
      var txt = this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip);
      ['column','row'].each(function(option) {
        this.els[option].each(function(el) { el.set('title',txt); } );
      }, this);
    }
});/*
---

name: Jx.Plugin.Grid.Editor

description: Enables inline editing in grids

license: MIT-style license.

requires:
 - Jx.Plugin.Grid
 - More/Keyboard

provides: [Jx.Plugin.Grid.Editor]

images:
 - icons.png
...
 */
// $Id: grid.editor.js 981 2010-09-13 12:18:35Z pagameba $
/**
 * Class: Jx.Plugin.Grid.Editor
 *
 * Extends: <Jx.Plugin>
 *
 * Grid plugin to enable inline editing within a cell
 *
 * Original selection code from Jx.Grid's original class
 *
 * License:
 * Original Copyright (c) 2008, DM Solutions Group Inc.
 * This version Copyright (c) 2009, Conrad Barthelmes.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Grid.Editor = new Class({

    Extends : Jx.Plugin,

    name: 'Editor',

    Binds: ['activate','deactivate','changeText','onCellClick'],

    options : {
      /**
       * Option: enabled
       * Determines if inline editing is avaiable
       */
      enabled : true,
      /**
       * Option: blurDelay
       * Set the time in miliseconds when the inputfield/popup shall hide. When
       * the user refocuses the input/popup within this time, the timeout will be cleared
       *
       * set to 'false' if no hiding on blur is wanted
       */
      blurDelay : 500,
      /**
       * Option: popup
       *
       * Definitions for a PopUp to use.
       * - use        - determines whether to use a PopUp or simply the input
       * - useLabel   - determines whether to use labels on top of the input.
       *                Text will be the column header
       * - useButtons - determines whether to use Submit and Cancel Buttons
       * - buttonLabel.submit - Text for Submit Button, uses MooTools.lang.get('Jx', 'plugin.editor').submitButton for default
       * - buttonLabel.cancel - Text for Cancel Button, uses MooTools.lang.get('Jx', 'plugin.editor').cancelButton for default
       */
      popup : {
        use           : true,
        useLabels     : false,
        useButtons    : true,
        button        : {
          submit : {
            label : '',
            image : 'images/accept.png'
          },
          cancel : {
            label : '',
            image : 'images/cancel.png'
          }
        },
        template: '<div class="jxGridEditorPopup"><div class="jxGridEditorPopupInnerWrapper"></div></div>'
      },
      /**
       * Option {boolean} validate
       * - set to true to have all editable input fields as mandatory field
       *   if they don't have 'mandatory:true' in their colOptions
       */
      validate : true,
      /**
       * Option: {Array} fieldOptions with objects
       * Contains objects with options for the Jx.Field instances to show up.
       * Default options will be added automatically if custom options are entered.
       *
       * Preferences:
       *   field             - Default * for all types or the name of the column in the store (Jx.Store)
       *   type              - Input type to show (Text, Password, Textarea, Select, Checkbox)
       *   options           - All Jx.Field options for this column. More options depend on what type you are using.
       *                       See Jx.Form.[yourField] for details
       *   validatorOptions: - See Jx.Plugin.Field.Validator Options for details
       *                       will only be used if this.options.validate is set to true
       */
      fieldOptions : [
        {
          field   : '*',
          type    : 'Text',
          options : {},
          validatorOptions: {
            validators : [],
            validateOnBlur: true,
            validateOnChange : false
          }
        }
      ],
      /**
       * Option: {Boolean} fieldFormatted
       * Displays the cell value also inside the input field as formatted
       */
      fieldFormatted : true,
      /**
       * Option cellChangeFx
       * set use to false if no highlighting effect is wanted.
       *
       * this is just an idea how successfully changing could be highlighed for the user
       */
      cellChangeFx : {
        use     : true,
        success : '#090',
        error   : '#F00'
      },
      /**
       * Option cellOutline
       * shows an outline style to the currently active cell to make it easier to see
       * which cell is active
       */
      cellOutline : {
        use   : true,
        style : '2px solid #88c3e7'
      },
      /**
       * Option: useKeyboard
       * Set to false if no keyboard support is needed
       */
      useKeyboard : true,
      /**
       * Option: keys
       * Contains the event codes for several commands that can be used when
       * a field is active. Syntax is the same like for the Mootools Keyboard Class
       * http://mootools.net/docs/more/Interface/Keyboard
       */
      keys : {
        'ctrl+shift+enter' : 'saveNGoUp',
        'tab'              : 'saveNGoRight',
        'ctrl+enter'       : 'saveNGoDown',
        'shift+tab'        : 'saveNGoLeft',
        'enter'            : 'saveNClose',
        'ctrl+up'          : 'cancelNGoUp',
        'ctrl+right'       : 'cancelNGoRight',
        'ctrl+down'        : 'cancelNGoDown',
        'ctrl+left'        : 'cancelNGoLeft',
        'esc'              : 'cancelNClose',
        'up'               : 'valueIncrement',
        'down'             : 'valueDecrement'
      },
      /**
       * Option: keyboardMethods
       *
       * can be used to overwrite existing keyboard methods that are used inside
       * this.options.keys - also possible to add new ones.
       * Functions are bound to the editor plugin when using 'this'
       *
       * example:
       *  keys : {
       *    'ctrl+u' : 'cancelNGoRightNDown'
       *  },
       *  keyboardMethods: {
       *    'cancelNGoRightNDown' : function(ev){
       *      ev.preventDefault();
       *      this.getNextCellInRow(false);
       *      this.getNextCellInCol(false);
       *    }
       *  }
       */
      keyboardMethods : {},
      /**
       * Option: keypressLoop
       * loop through the grid when pressing TAB (or some other method that uses
       * this.getNextCellInRow() or this.getPrevCellInRow()). If set to false,
       * the input field/popup will not start at the opposite site of the grid
       * Defaults to true
       */
      keypressLoop : true,
      /**
       * Option: linkClickListener
       * disables all click events on links that are formatted with Jx.Formatter.Uri
       * - otherwise the link will open directly instead of open the input editor)
       * - hold [ctrl] to open the link in a new tab
       */
      linkClickListener : true
    },
    classes: ['jxGridEditorPopup', 'jxGridEditorPopupInnerWrapper'],
    /**
     * Property: activeCell
     *
     * Containing Objects:
     *   field        : Reference to the Jx.Field instance that will be created
     *   cell         : Reference to the cell inside the table
     *   span         : Reference to the Dom Element inside the selected cell of the grid
     *   oldValue     : Old value of the cell from the grid's store
     *   newValue     : Object with <data> and <error> for better validation possibilites
     *   timeoutId    : TimeoutId if the focus blurs the input.
     *   data         : Reference to the cell data
     *   fieldOptions : Reference to the field options of this column
     */
    activeCell : {
      field       : null,
      cell        : null,
      span        : null,
      oldValue    : null,
      newValue    : { data: null, error: false },
      timeoutId   : null,
      data        : {},
      fieldOptions: {}
    },
    /**
     * Property : popup
     *
     * References to all contents within a popup (only 1 popup for 1 grid initialization)
     *
     * COMMENT: I don't know how deep we need to go into that.. innerWrapper and closeLink probably don't need
     * own references.. I just made them here in case they are needed at some time..
     *
     * Containing Objects:
     *   domObj         : Reference to the Dom Element of the popup (absolutely positioned)
     *   innerWrapper   : Reference to the inner Wrapper inside the popup to provide relative positioning
     *   closeIcon      : Reference to the Dom Element of a little [x] in the upper right to close it (not saving)
     *   buttons        : References to all Jx.Buttons used inside the popup
     *   buttons.submit : Reference to the Submit Button
     *   buttons.cancel : Reference to the Cancel Button
     */
    popup : {
      domObj       : null,
      innerWarpper : null,
      closeIcon    : null,
      button       : {
        submit : null,
        cancel : null
      }
    },
    /**
     * Property: keyboard
     * Instance of a Mootols Keyboard Class
     */
    keyboard : null,
    /**
     * Property keyboardMethods
     * Editing and grid functions for keyboard functionality.
     * Methods are defined and implemented inside this.attach() because of referencing troubles
     */
    keyboardMethods : {},
    /**
     * APIMethod: init
     * construct a new instance of the plugin.  The plugin must be attached
     * to a Jx.Grid instance to be useful though.
     */
    init: function() {
      this.parent();
    },
    /**
     * APIMethod: attach
     * Sets up the plugin and attaches the plugin to the grid events it
     * will be monitoring
     *
     * @var {Object} grid - Instance of Class Jx.Grid
     */
    attach: function (grid) {
      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
        return;
      }
      this.parent(grid);
      this.grid = grid;

      //this.grid.gridTableBody.addEvent('click', this.onCellClick);
      // this.grid.wantEvent('gridCellClick');
      this.grid.addEvent('gridCellClick', this.onCellClick);

      /*
       * add default field options to the options in case some new options were entered
       * to be still able to use them for the rest of the fields
       */
      if(this.getFieldOptionsByColName('*').field != '*') {
        this.options.fieldOptions.unshift({
          field   : '*',
          type    : 'Text',
          options : {},
          validatorOptions: {
            validators : [],
            validateOnBlur: true,
            validateOnChange : false
          }
        });
      }

      /**
       * set the keyboard methods here to have a correct reference to the instance of
       * the editor plugin
       *
       * @todo other names maybe? or even completely different way of handling the keyboard events?
       * @todo more documentation than method name
       */
      var self = this;
      this.keyboardMethods = {
        saveNClose     : function(ev) {
          if(self.activeCell.fieldOptions.type != 'Textarea' || (self.activeCell.fieldOptions.type == 'Textarea' && ev.key != 'enter')) {
            self.deactivate();
          }
        },
        saveNGoUp      : function(ev) {ev.preventDefault();self.getPrevCellInCol();},
        saveNGoRight   : function(ev) {ev.preventDefault();self.getNextCellInRow();},
        saveNGoDown    : function(ev) {ev.preventDefault();self.getNextCellInCol();},
        saveNGoLeft    : function(ev) {ev.preventDefault();self.getPrevCellInRow();},
        cancelNClose   : function(ev) {ev.preventDefault();self.deactivate(false);},
        cancelNGoUp    : function(ev) {ev.preventDefault();self.getPrevCellInCol(false);},
        cancelNGoRight : function(ev) {ev.preventDefault();self.getNextCellInRow(false);},
        cancelNGoDown  : function(ev) {ev.preventDefault();self.getNextCellInCol(false);},
        cancelNGoLeft  : function(ev) {ev.preventDefault();self.getPrevCellInRow(false);},
        valueIncrement : function(ev) {ev.preventDefault();self.cellValueIncrement(true);},
        valueDecrement : function(ev) {ev.preventDefault();self.cellValueIncrement(false);}
      };

      var keyboardEvents = {};
      for(var i in this.options.keys) {
        if($defined(this.keyboardMethods[this.options.keys[i]])) {
          keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
        }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
          keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
        }else if(Jx.type(this.options.keys[i]) == 'function') {
          keyboardEvents[i] = this.options.keys[i].bind(self);
        }else{
          $defined(console) ? console.warn("keyboard method %o not defined", this.options.keys[i]) : false;
        }
      }

      // initalize keyboard support but do NOT activate it (this is done inside this.activate()).
      this.keyboard = new Keyboard({
        events: keyboardEvents
      });

      this.addFormatterUriClickListener();
    },
    /**
     * APIMethod: detach
     * detaches from the grid
     *
     * @return void
     */
    detach: function() {
      if (this.grid) {
        this.grid.removeEvent('gridCellClick', this.onCellClick);
      }
      this.grid = null;
      this.keyboard = null;
    },
    /**
     * APIMethod: enable
     * enables the grid 'externally'
     *
     * @return void
     */
    enable : function () {
      this.options.enabled = true;
    },
    /**
     * APIMethod: disable
     * disables the grid 'externally'
     *
     * @var Boolean close - default true: also closes the currently open input/popup
     * @var Boolean save - default false: also changes the currently open input/popup
     * @return void
     */
    disable : function(close, save) {
      close = $defined(close) ? close : true;
      save = $defined(save) ? save : false;
      if(close && this.activeCell.cell != null) {
        this.deactivate(save);
      }
      this.options.enabled = false;
    },

    /**
     * Method: onCellClick
     * dispatch clicking on a table cell
     */
    onCellClick: function(cell) {
      this.activate(cell);
    },
    /**
     * Method: activate
     * activates the input field or breaks up if conditions are not fulfilled
     *
     * @todo Field validation
     *
     * Parameters:
     * @var {Object} cell Table Element
     * @return void
     */
    activate: function(cell) {
      // if not enabled or the cell is null, do nothing at all
      if(!this.options.enabled || !cell)
        return;

      // activate can be called by clicking on the same cell or a
      // different one
      if (this.activeCell.cell) {
        if (this.activeCell.cell != cell) {
          if (!this.deactivate()) {
            return;
          }
        } else {
          // they are the same, ignore?
          return;
        }
      }

      var data  = this.grid.getCellData(cell); //.retrieve('jxCellData');

      if (!data || !$defined(data.row) || !$defined(data.column)) {
        if($defined(console)) {
          console.warn('out of grid %o',cell);
          console.warn('data was %o', data);
        }
        return;
      }

      // column marked as not editable
      if (!data.column.options.isEditable) {
        return;
      }

      if (this.activeCell.timeoutId) {
        clearTimeout(activeCell.timeoutId);
      }

      // set active record index to selected row
      this.grid.store.moveTo(data.row);

      // set up the data objects we need
      var options = this.options,
          grid = this.grid,
          store = grid.getStore(),
          index = grid.columns.getIndexFromGrid(data.column.name),
          colOptions = data.column.options,
          activeCell = {
            oldValue      : store.get(data.column.name),
            newValue      : {data: null, error: false},
            fieldOptions  : this.getFieldOptionsByColName(data.column.name),
            data          : data,
            cell          : cell,
            span          : cell.getElement('span.jxGridCellContent'),
            validator     : null,
            field         : null,
            timeoutId     : null
          },
          jxFieldOptions = activeCell.fieldOptions.options,
          oldValue,
          groups,
          k,
          n;

      // check if this column has special validation settings -
      // otherwise use default from this.options.validate
      if(!$defined(data.column.options.validate) || typeof(data.column.options.validate) != 'boolean') {
        data.column.options.validate = options.validate;
        cell.store('jxCellData', data);
      }

      // check for different input field types
      switch(activeCell.fieldOptions.type) {
        case 'Text':
        case 'Color':
        case 'Password':
        case 'File':
          jxFieldOptions.value = activeCell.oldValue;
          break;
        case 'Textarea':
          jxFieldOptions.value = activeCell.oldValue.replace(/<br \/>/gi, '\n');
          break;
        case 'Select':
          // find out which visible value fits to the value inside
          // <option>{value}</option> and set it to selected
          jxFieldOptions.value = oldValue  = activeCell.oldValue.toString();
          function setCombos(opts, oldValue) {
            for(var i = 0, j = opts.length; i < j; i++) {
              if(opts[i].value == oldValue) {
                opts[i].selected = true;
              }else{
                opts[i].selected = false;
              }
            }
            return opts;
          }

          if(jxFieldOptions.comboOpts) {
            jxFieldOptions.comboOpts = setCombos(jxFieldOptions.comboOpts, oldValue);
          }else if(jxFieldOptions.optGroups) {
            groups = jxFieldOptions.optGroups;
            for(k = 0, n = groups.length; k < n; k++) {
              groups[k].options = setCombos(groups[k].options, oldValue);
            }
            jxFieldOptions.optGroups = groups;
          }
          break;
        case 'Radio':
        case 'Checkbox':
        default:
          $defined(console) ? console.warn("Fieldtype %o is not supported yet. If you have set a validator for a column, you maybe have forgotton to enter a field type.", activeCell.fieldOptions.type) : false;
          return;
          break;
      }

      // update the 'oldValue' to the formatted style, to compare the new value with the formatted one instead with the non-formatted-one
      if(options.fieldFormatted && colOptions.renderer.options.formatter != null) {
        if(!$defined(colOptions.fieldFormatted) || colOptions.fieldFormatted == true ) {
          jxFieldOptions.value = colOptions.renderer.options.formatter.format(jxFieldOptions.value);
          activeCell.oldValue = jxFieldOptions.value;
        }
      }

      // create jx.field
      activeCell.field = new Jx.Field[activeCell.fieldOptions.type.capitalize()](jxFieldOptions);
      // create validator
      if(options.validate && colOptions.validate) {
        activeCell.validator = new Jx.Plugin.Field.Validator(activeCell.fieldOptions.validatorOptions);
        activeCell.validator.attach(activeCell.field);
      }

      // store properties of the active cell
      this.activeCell = activeCell;
      this.setStyles(cell);

      if(options.useKeyboard) {
        this.keyboard.activate();
      }

      // convert a string to an integer if somebody entered a numeric value in quotes, if it failes: make false
      if(typeof(options.blurDelay) == 'string') {
        options.blurDelay = options.blurDelay.toInt() ? options.blurDelay.toInt() : false;
      }

      // add a onblur() and onfocus() event to the input field if enabled.
      if(options.blurDelay !== false && typeof(options.blurDelay) == 'number') {
        activeCell.field.field.addEvents({
          // activate the timeout to close the input/poup
          'blur' : function() {
            // @todo For some reason, webkit does not clear the timeout correctly when navigating through the grid with keyboard
            clearTimeout(activeCell.timeoutId);
            activeCell.timeoutId = this.deactivate.delay(this.options.blurDelay);
          }.bind(this),
          // clear the timeout when the user focusses again
          'focus' : function() {
            clearTimeout(activeCell.timeoutId);
          },
          // clear the timeout when the user puts the mouse over the input
          'mouseover' : function() {
            clearTimeout(activeCell.timeoutId);
          }
        });
        if(this.popup.domObj != null) {
          this.popup.domObj.addEvent('mouseenter', function() {
            clearTimeout(activeCell.timeoutId);
          });
        }
      }

      activeCell.field.field.focus();
    },
    /**
     * APIMethod: deactivate
     * hides the currently active field and stores the new entered data if the
     * value has changed
     *
     * Parameters:
     * @var {Boolean} save (Optional, default: true) - force aborting
     * @return true if no data error occured, false if error (popup/input stays visible)
     */
    deactivate: function(save) {
      var newValue = {data : null, error : false},
          index,
          activeCell = this.activeCell,
          grid = this.grid,
          store = grid.store,
          options = this.options,
          highlighter,
          cellBg;

      clearTimeout(activeCell.timeoutId);

      if(activeCell.field !== null) {
        save = $defined(save) ? save : true;


        // update the value in the column
        if(save && activeCell.field.getValue().toString() != activeCell.oldValue.toString()) {
          store.moveTo(activeCell.data.row);
          /*
           * @todo webkit shrinks the rows when the value is updated... but refreshing the grid
           *       immidiately returns in a wrong calculating of the cell position (getCoordinates)
           */
          switch (activeCell.fieldOptions.type) {
            case 'Select':
              index = activeCell.field.field.selectedIndex;
              newValue.data = document.id(activeCell.field.field.options[index]).get('value');
              break;
            case 'Textarea':
              newValue.data = activeCell.field.getValue().replace(/\n/gi, '<br />');
              break;
            default:
              newValue.data = activeCell.field.getValue();
              break;
          }
          if (save) {
            activeCell.newValue.data = newValue.data;
          }
          // validation only if it should be saved!
          if (activeCell.validator != null && !activeCell.validator.isValid()) {
            newValue.error = true;
            activeCell.field.field.focus.delay(50, activeCell.field.field);
          }
        } else {
          activeCell.span.show();
        }

        // var data = activeCell.cell.retrieve('jxCellData');
        if (save && newValue.data != null && newValue.error == false) {
          store.set(activeCell.data.column.name, newValue.data);
          this.addFormatterUriClickListener();
        // else show error message and cell
        } else if (newValue.error == true) {
          activeCell.span.show();
        }

        // update reference to activeCell
        if ($defined(activeCell.data.row) && $defined(activeCell.data.index)) {
          var colIndex = grid.row.useHeaders() ? activeCell.data.index-1 : activeCell.data.index;
          this.activeCell.cell = grid.gridTableBody.rows[this.activeCell.data.row].cells[colIndex];
        }

        if (options.useKeyboard) {
          activeCell.field.removeEvent('keypress', this.setKeyboard);
        }

        /**
         * COMMENT: this is just an idea how changing a value could be visualized
         * we could also pass an Fx.Tween element?
         * the row could probably be highlighted as well?
         */
        if(options.cellChangeFx.use) {
          highlighter = new Fx.Tween(this.activeCell.cell, {
            duration: 250,
            onComplete: function(ev) {
              this.element.removeProperty('style');
            }
          });
          cellBg = activeCell.cell.getStyle('background-color');
          cellBg = cellBg == 'transparent' ? '#fff' : cellBg;
          if (newValue.data != null && newValue.error == false) {
            highlighter.start('background-color',options.cellChangeFx.success, cellBg);
          } else if (newValue.error){
            highlighter.start('background-color',options.cellChangeFx.error, cellBg);
          }
        }

        // check for error and keep input field alive
        if (newValue.error) {
          if(options.cellChangeFx.use) {
            activeCell.field.field.highlight(options.cellChangeFx.error);
          }
          activeCell.field.field.setStyle('border','1px solid '+options.cellChangeFx.error);
          activeCell.field.field.focus();
          return false;
        // otherwise hide it
        }else{
          this.keyboard.deactivate();
          this.unsetActiveField();
          return true;
        }
      }
    },
    /**
     * Method: setStyles
     *
     * sets some styles for the Jx.Field elements...
     *
     * Parameters:
     * @var cell - table cell of the grid
     * @return void
     */
    setStyles : function(cell) {
      var styles,
          size,
          options = this.options,
          activeCell = this.activeCell;
      // popup
      if (options.popup.use) {
        if (options.popup.useLabels) {
          activeCell.field.options.label = activeCell.data.column.options.header;
          activeCell.field.render();
        }
        styles = {
          field : {
            'width'  : activeCell.field.type == 'Select' ?
                         cell.getContentBoxSize().width + 5 + "px" :
                         cell.getContentBoxSize().width - 14 + "px",
            'margin' : 'auto 0'
          }
        };
        activeCell.field.field.setStyles(styles.field);
        this.showPopUp(cell);
      // No popup
      } else {
        size   = cell.getContentBoxSize();
        styles = {
          domObj : {
            position: 'absolute'
          },
          field : {
            width : size.width + "px",
            'margin-left' : 0
          }
        };

        activeCell.field.domObj.setStyles(styles.domObj);
        activeCell.field.field.setStyles(styles.field);

        activeCell.field.domObj.inject(document.body);
        Jx.Widget.prototype.position(activeCell.field.domObj, cell, {
            horizontal: ['left left'],
            vertical: ['top top']
        });

        activeCell.span.hide();
      }

      // COMMENT: an outline of the cell helps identifying the currently active cell
      if(options.cellOutline.use) {
        cell.setStyle('outline', options.cellOutline.style);
      }
    },
    /**
     * Method: showPopUp
     *
     * Shows the PopUp of of the editor if it already exists, otherwise calls Method
     * this.createPopUp
     *
     * Parameters:
     * @var cell - table cell of the grid
     */
    showPopUp : function(cell) {
      if(this.popup.domObj != null) {
        Jx.Widget.prototype.position(this.popup.domObj, cell, {
            horizontal: ['left left'],
            vertical: ['top top']
        });
        this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
        this.popup.domObj.show();
        this.setPopUpButtons();
        this.setPopUpStylesAfterRendering();
      }else{
        this.createPopUp(cell);
      }
    },
    /**
     * Method: createPopUp
     *
     * creates the popup for the requested cell.
     *
     * COMMENT: this could also be an jx.dialog..? if we use jx.dialog, maybe without a title element?
     *          Maybe a jx.dialog is too much for this little thing?
     *
     * Parameters:
     * @var cell - table cell of the grid
     */
    createPopUp : function(cell) {
      var coords = cell.getCoordinates(),
          self      = this, popup  = null, innerWrapper = null,
          closeIcon = null, submit = null, cancel       = null,
          template  = Jx.Widget.prototype.processTemplate(this.options.popup.template, this.classes);

      popup = template.jxGridEditorPopup;

      innerWrapper = template.jxGridEditorPopupInnerWrapper;
      /**
       * COMMENT: first positioning is always in the top left of the grid..
       * don't know why
       * manual positioning is needed..?
       */
      popup.setStyles({
        'left' : coords.left+'px',
        'top'  : coords.top +'px'
      });
      /*
      Jx.Widget.prototype.position(popup, cell, {
            horizontal: ['left left'],
            vertical: ['top top']
      });
      */

      this.popup.domObj         = popup;
      this.popup.innerWrapper   = innerWrapper;
      this.popup.closeIcon      = closeIcon;
      this.setPopUpButtons();

      this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
      this.popup.domObj.inject(document.body);

      this.setPopUpStylesAfterRendering();
    },
    /**
     * Method: setPopUpStylesAfterRendering
     *
     * - measures the widths of the buttons to set a new min-width for the popup
     *   because custom labels could break the min-width and force a line-break
     * - resets the size of the field to make it fit inside the popup (looks nicer)
     *
     * @return void
     */
    setPopUpStylesAfterRendering: function() {
      if(this.options.popup.useButtons && this.popup.button.submit != null && this.popup.button.cancel != null) {
        this.popup.domObj.setStyle('min-width', this.popup.button.submit.domObj.getSize().x + this.popup.button.cancel.domObj.getSize().x + "px");
      }else{
        if(this.popup.button.submit != null)
          this.popup.button.submit.domObj.hide();
        if(this.popup.button.cancel != null)
          this.popup.button.cancel.domObj.hide();
      }
      this.activeCell.field.field.setStyle('width',
        this.activeCell.field.type == 'Select' ?
          this.popup.domObj.getSize().x - 7 + "px" :
          this.popup.domObj.getSize().x - 17 + "px");
    },
    /**
     * Method: setPopUpButtons
     * creates the PopUp Buttons if enabled in options or deletes them if set to false
     *
     * @return void
     */
    setPopUpButtons : function() {
      var self = this,
          button = {
            submit : null,
            cancel : null
          };
      // check if buttons are needed, innerWrapper exists and no buttons already exist
      if(this.options.popup.useButtons && this.popup.innerWrapper != null && this.popup.button.submit == null) {
        button.submit = new Jx.Button({
          label : this.options.popup.button.submit.label.length == 0 ?
                    this.getText({set:'Jx',key:'plugin.editor',value:'submitButton'}) :
                    this.getText(this.options.popup.button.submit.label),
          image : this.options.popup.button.submit.image,
          onClick: function() {
            self.deactivate(true);
          }
        }).addTo(this.popup.innerWrapper);
        button.cancel = new Jx.Button({
          label : this.options.popup.button.cancel.label.length == 0 ?
                    this.getText({set:'Jx',key:'plugin.editor',value:'cancelButton'}) :
                    this.getText(this.options.popup.button.cancel.label),
          image : this.options.popup.button.cancel.image,
          onClick: function() {
            self.deactivate(false);
          }
        }).addTo(this.popup.innerWrapper);
      }else if(this.options.popup.useButtons && this.popup.button.submit != null) {
        button = {
          submit : this.popup.button.submit,
          cancel : this.popup.button.cancel
        };
      // check if buttons are not needed and buttons already exist to remove them
      }else if(this.options.popup.useButtons == false && this.popup.button.submit != null) {
        this.popup.button.submit.cleanup();
        this.popup.button.cancel.cleanup();
      }

      this.popup.button = button;
    },
    /**
     * Method: unsetActiveField
     * resets the activeField and hides the popup
     *
     * @return void
     */
    unsetActiveField: function() {
      this.activeCell.field.destroy();
      if(this.popup.domObj != null) {
        this.popup.domObj.removeEvent('mouseenter');
        this.popup.domObj.hide();
      }

      this.activeCell.cell.setStyle('outline', '0px');

      this.activeCell = {
        field         : null,
        oldValue      : null,
        newValue      : { data: null, error: false},
        cell          : null,
        span          : null,
        timeoutId     : null,
        //popup         : null,   // do not destroy the popup, it might be used again
        data           : {},
        fieldOptions  : {},
        validator     : null
      };
    },
    /**
     * Method: unsetPopUp
     * resets the popup manually to be able to use it with different settings
     */
    unsetPopUp : function() {
      if(this.popup.domObj != null) {
        this.popup.domObj.destroy();
        this.popup.innerWrapper   = null;
        this.popup.closeIcon      = null;
        this.popup.button.submit = null;
        this.popup.button.cancel = null;
      }
    },
    /**
     * APIMethod: getNextCellInRow
     * activates the next cell in a row if it is editable
     * otherwise the focus jumps to the next editable cell in the next row
     * or starts at the beginning
     *
     * @var  {Boolean} save (Optional, default: true)
     * @return void
     */
    getNextCellInRow: function(save) {
      save = $defined(save) ? save : true;
      var nextCell = true,
          nextRow = true,
          sumCols = this.grid.columns.columns.length,
          jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)',
          i = 0,
          data,
          cell = this.activeCell.cell,
          options = this.options;
      if (this.activeCell.cell != null) {
        do {
          nextCell = i > 0 ? nextCell.getNext(jxCellClass) : cell.getNext(jxCellClass);
          // check if cell is still in row, otherwise returns null
          if (nextCell == null) {
            nextRow  = cell.getParent('tr').getNext();
            // check if this was the last row in the table
            if (nextRow == null && options.keypressLoop) {
              nextRow = cell.getParent('tbody').getFirst();
            } else if(nextRow == null && !options.keypressLoop){
              return;
            }
            nextCell = nextRow.getFirst(jxCellClass);
          }
          data = this.grid.getCellData(nextCell);
          i++;
          // if all columns are set to uneditable during runtime, jump out of the loop after
          // running through 2 times to prevent an endless-loop and browser crash :)
          if (i == sumCols*2) {
            this.deactivate(save);
            return;
          }
        } while(data && !data.column.options.isEditable);

        if (save === false) {
          this.deactivate(save);
        }
        this.activate(nextCell);
      }
    },
    /**
     * APIMethod: getPrevCellInRow
     * activates the previous cell in a row if it is editable
     * otherwise the focus jumps to the previous editable cell in the previous row
     * or starts at the last cell in the last row at the end
     *
     * @var  {Boolean} save (Optional, default: true)
     * @return void
     */
    getPrevCellInRow: function(save) {
      save = $defined(save) ? save : true;
      var prevCell,
          prevRow,
          i = 0,
          data,
          row,
          index,
          cell = this.activeCell.cell,
          sumCols = this.grid.columns.columns.length,
          jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)',
          options = this.options;
      if(cell != null) {
        do {
          prevCell = i > 0 ? prevCell.getPrevious(jxCellClass) : cell.getPrevious(jxCellClass);
          // check if cell is still in row, otherwise returns null
          if(prevCell == null) {
            prevRow  = cell.getParent('tr').getPrevious();
            // check if this was the last row in the table
            if(prevRow == null && options.keypressLoop) {
              prevRow = cell.getParent('tbody').getLast();
            }else if(prevRow == null && !options.keypressLoop) {
              return;
            }
            prevCell = prevRow.getLast(jxCellClass);
          }
          data  = this.grid.getCellData(prevCell);
          row   = data.row;
          index = data.index;
          i++;
          // if all columns are set to uneditable during runtime, jump out of the loop after
          // running through 2 times to prevent an endless-loop and browser crash :)
          if(i == sumCols*2) {
            this.deactivate(save);
            return;
          }
        }while(data && !data.column.options.isEditable);

        if(save === false) {
          this.deactivate(save);
        }
        this.activate(prevCell);
      }
    },
    /**
     * APIMethod: getNextCellInCol
     * activates the next cell in a column under the currently active one
     * if the active cell is in the last row, the first one will be used
     *
     * @var  {Boolean} save (Optional, default: true)
     * @return void
     */
    getNextCellInCol : function(save) {
      var nextRow,
          nextCell,
          activeCell = this.activeCell;
      save = $defined(save) ? save : true;
      if (activeCell.cell != null) {
        nextRow = activeCell.cell.getParent().getNext();
        if (nextRow == null) {
          nextRow = activeCell.cell.getParent('tbody').getFirst();
        }
        nextCell = nextRow.getElement('td.jxGridCol'+activeCell.data.index);
        if (save === false) {
          this.deactivate(save);
        }
        this.activate(nextCell);
      }
    },
    /**
     * APIMethod: getPrevCellInCol
     * activates the previous cell in a column above the currently active one
     * if the active cell is in the first row, the last one will be used
     *
     * @var  {Boolean} save (Optional, default: true)
     * @return void
     */
    getPrevCellInCol : function(save) {
      var prevRow,
          prevCell,
          activeCell = this.activeCell;
      save = $defined(save) ? save : true;
      if (activeCell.cell != null) {
        prevRow = activeCell.cell.getParent().getPrevious();
        if (prevRow == null) {
          prevRow = activeCell.cell.getParent('tbody').getLast();
        }
        prevCell = prevRow.getElement('td.jxGridCol'+activeCell.data.index);
        if (save === false) {
          this.deactivate(save);
        }
        this.activate(prevCell);
      }
    },
    /**
     * Method: cellValueIncrement
     * Whether increments or decrements the value of the active cell if the dataType is numeric
     *
     * Parameters
     * @var {Boolean} bool
     * @return void
     */
    cellValueIncrement : function(bool) {
      var activeCell = this.activeCell,
          dataType = activeCell.data.column.options.dataType,
          valueNew = null,
          formatter;
      switch (dataType) {
        case 'numeric':
        case 'currency':
          valueNew = activeCell.field.getValue().toInt();
          if (typeof(valueNew) == 'number') {
            if (bool) {
              valueNew++;
            } else {
              valueNew--;
            }
          }
          break;
        case 'date':
          valueNew = Date.parse(activeCell.field.getValue());
          if (valueNew instanceof Date) {
            if (bool) {
              valueNew.increment();
            } else {
              valueNew.decrement();
            }
            formatter = new Jx.Formatter.Date();
            valueNew = formatter.format(valueNew);
          }
          break;
      }
      if (valueNew != null) {
        activeCell.field.setValue(valueNew);
      }
    },
    /**
     * Method: cellIsInGrid
     * determins if the given coordinates are within the grid
     *
     * Parameters:
     * @var {Integer} row
     * @var {Integer} index
     * @return {Boolean}
     */
    cellIsInGrid: function(row, index) {
      if($defined(row) && $defined(index)) {
        //console.log("Row %i - max Rows: %i, Col %i - max Cols %i", row, this.grid.gridTableBody.rows.length, index, this.grid.gridTableBody.rows[row].cells.length);
        if( row >= 0 && index >= 0 &&
            row <= this.grid.gridTableBody.rows.length &&
            index <= this.grid.gridTableBody.rows[row].cells.length
        ) {
          return true;
        }else{
          return false;
        }
      }else{
        return false;
      }
    },
    /**
     * APIMethod: getFieldOptionsByColName
     * checks for the name of a column inside the fieldOptions and returns
     * the object if found, otherwise the default options for the field
     *
     * Parameters:
     * @var {String} colName
     * @return {Object} default field options
     */
    getFieldOptionsByColName : function(colName) {
      var fo = this.options.fieldOptions,
          r  = this.options.fieldOptions[0];
      for(var i = 0, j = fo.length; i < j; i++) {
        if(fo[i].field == colName) {
          r = fo[i];
          break;
        }
      }
      return r;
    },
    /**
     * Method: addFormatterUriClickListener
     *
     * looks up for Jx.Formatter.Uri columns to disable the link and open the
     * inline editor instead when CTRL is NOT pressed.
     * set option linkClickListener to false to disable this
     *
     */
    addFormatterUriClickListener : function() {
      if(this.options.linkClickListener) {
        // prevent a link from beeing opened if the editor should appear and the uri formatter is activated
        var uriCols = [], tableCols, anchor;
        // find out which columns are using a Jx.Formatter.Uri
        this.grid.columns.columns.each(function(col,i) {
          if(col.options.renderer.options.formatter != null && col.options.renderer.options.formatter instanceof Jx.Formatter.Uri) {
            uriCols.push(i);
          }
        });
        // add an event to all anchors inside these columns
        this.grid.gridObj.getElements('tr').each(function(tr,i) {
          tableCols = tr.getElements('td.jxGridCell');
          for(var j = 0, k = uriCols.length; j < k; j++) {
            anchor = tableCols[uriCols[j]-1].getElement('a');
            if(anchor) {
              anchor.removeEvent('click');
              anchor.addEvent('click', function(ev) {
                // open link if ctrl was clicked
                if(!ev.control) {
                  ev.preventDefault();
                }
              });
            }
          }
        });
      }
    },
    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     * 		translations changed.
     */
    changeText: function (lang) {
    	this.parent();
    	if (this.options.popup.use && this.options.popup.useButtons) {
        if(this.popup.button.submit != null) {
          this.popup.button.submit.cleanup();
          this.popup.button.cancel.cleanup();
          this.popup.button.submit = null;
          this.popup.button.cancel = null;
          this.setPopUpButtons();
        }
    	}
    }
});
/*
---

name: Jx.Plugin.DataView

description: Namespace for DataView plugins

license: MIT-style license.

requires:
 - Jx.Plugin

provides: [Jx.Plugin.DataView]
...
 */
/**
 * Namespace: Jx.Plugin.DataView
 * The namespace for all dataview plugins
 */
Jx.Plugin.DataView = {};/*
---

name: Jx.Slide

description: A class that shows and hides elements using a slide effect. Does not use a wrapper element or require a fixed width or height.

license: MIT-style license.

requires:
 - Jx.Object
 - Core/Fx.Tween

provides: [Jx.Slide]

...
 */
// $Id: slide.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Slide
 * Hides and shows an element without depending on a fixed width or height
 *
 * Copyright 2009 by Jonathan Bomgardner
 * License: MIT-style
 */
Jx.Slide = new Class({
    Family: 'Jx.Slide',
    Implements: Jx.Object,
    Binds: ['handleClick'],
    options: {
        /**
         * Option: target
         * The element to slide
         */
        target: null,
        /**
         * Option: trigger
         * The element that will have a click event added to start the slide
         */
        trigger: null,
        /**
         * Option: type
         * The type of slide. Can be either "width" or "height". defaults to "height"
         */
        type: 'height',
        /**
         * Option: setOpenTo
         * Allows the caller to determine what the open target is set to. Defaults to 'auto'.
         */
        setOpenTo: 'auto',
        /**
         * Option: onSlideOut
         * function called when the target is revealed.
         */
        onSlideOut: $empty,
        /**
         * Option: onSlideIn
         * function called when a panel is hidden.
         */
        onSlideIn: $empty
    },
    /**
     * Method: init
     * sets up the slide
     */
    init: function () {

        this.target = document.id(this.options.target);

        this.target.set('tween', {onComplete: this.setDisplay.bind(this)});

        if ($defined(this.options.trigger)) {
            this.trigger = document.id(this.options.trigger);
            this.trigger.addEvent('click', this.handleClick);
        }

        this.target.store('slider', this);

    },
    /**
     * Method: handleClick
     * event handler for clicks on the trigger. Starts the slide process
     */
    handleClick: function () {
        var sizes = this.target.getMarginBoxSize();
        if (sizes.height === 0) {
            this.slide('in');
        } else {
            this.slide('out');
        }
    },
    /**
     * Method: setDisplay
     * called at the end of the animation to set the target's width or
     * height as well as other css values to the appropriate values
     */
    setDisplay: function () {
        var h = this.target.getStyle(this.options.type).toInt();
        if (h === 0) {
            this.target.setStyle('display', 'none');
            this.fireEvent('slideOut', this.target);
        } else {
            //this.target.setStyle('overflow', 'auto');
            if (this.target.getStyle('position') !== 'absolute') {
                this.target.setStyle(this.options.type, this.options.setOpenTo);
            }
            this.fireEvent('slideIn', this.target);
        }
    },
    /**
     * APIMethod: slide
     * Actually determines how to slide and initiates the animation.
     *
     * Parameters:
     * dir - the direction to slide (either "in" or "out")
     */
    slide: function (dir) {
        var h;
        if (dir === 'in') {
            h = this.target.retrieve(this.options.type);
            this.target.setStyles({
                overflow: 'hidden',
                display: 'block'
            });
            this.target.setStyles(this.options.type, 0);
            this.target.tween(this.options.type, h);
        } else {
            if (this.options.type === 'height') {
                h = this.target.getMarginBoxSize().height;
            } else {
                h = this.target.getMarginBoxSize().width;
            }
            this.target.store(this.options.type, h);
            this.target.setStyle('overflow', 'hidden');
            this.target.setStyle(this.options.type, h);
            this.target.tween(this.options.type, 0);
        }
    }
});/*
---

name: Jx.Plugin.DataView.GroupFolder

description: Enables closing and opening groups in a group dataview

license: MIT-style license.

requires:
 - Jx.Plugin.DataView
 - Jx.Slide

provides: [Jx.Plugin.DataView.GroupFolder]

...
 */
/**
 * Class: Jx.Plugin.DataView.GroupFolder
 *
 * Extends: <Jx.Plugin>
 *
 * Plugin for DataView - allows folding/unfolding of the groups in the
 * grouped dataview
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.DataView.GroupFolder = new Class({

    Extends: Jx.Plugin,

    options: {
        /**
         * Option: headerClass
         * The base for styling the header. Gets '-open' or '-closed' added
         * to it.
         */
        headerClass: null
    },
    /**
     * Property: headerState
     * Hash that holds the open/closed state of each header
     */
    headerState: null,
    init: function() {
      this.headerState = new Hash();
    },
    /**
     * APIMethod: attach
     * Attaches this plugin to a dataview
     */
    attach: function (dataView) {
        if (!$defined(dataView) && !(dataview instanceof Jx.Panel.DataView)) {
            return;
        }

        this.dv = dataView;
        this.dv.addEvent('renderDone', this.setHeaders.bind(this));
    },
    /**
     * Method: setHeaders
     * Called after the dataview is rendered. Sets up the Jx.Slide instance
     * for each header. It also sets the initial state of each header so that
     * if the dataview is redrawn for some reason the open/closed state is
     * preserved.
     */
    setHeaders: function () {
        var headers = this.dv.domA.getElements('.' + this.dv.options.groupHeaderClass);

        headers.each(function (header) {
            var id = header.get('id');
            var s = new Jx.Slide({
                target: header.getNext(),
                trigger: id,
                onSlideOut: this.onSlideOut.bind(this, header),
                onSlideIn: this.onSlideIn.bind(this, header)
            });

            if (this.headerState.has(id)) {
                var state = this.headerState.get(id);
                if (state === 'open') {
                    s.slide('in');
                } else {
                    s.slide('out');
                }
            } else {
                s.slide('in');
            }
        }, this);
    },

    /**
     * Method: onSlideIn
     * Called when a group opens.
     *
     * Parameters:
     * header - the header that was clicked.
     */
    onSlideIn: function (header) {
        this.headerState.set(header.get('id'), 'open');
        if (header.hasClass(this.options.headerClass + '-closed')) {
            header.removeClass(this.options.headerClass + '-closed');
        }
        header.addClass(this.options.headerClass + '-open');
    },
    /**
     * Method: onSlideOut
     * Called when a group closes.
     *
     * Parameters:
     * header - the header that was clicked.
     */
    onSlideOut: function (header) {
        this.headerState.set(header.get('id'), 'closed');
        if (header.hasClass(this.options.headerClass + '-open')) {
            header.removeClass(this.options.headerClass + '-open');
        }
        header.addClass(this.options.headerClass + '-closed');
    }
});
/*
---

name: Jx.Plugin.Field

description: Namespace for Jx.Field plugins

license: MIT-style license.

requires:
 - Jx.Plugin

provides: [Jx.Plugin.Field]

...
 */
// $Id: plugin.field.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Plugin.Field
 * Field plugin namespace
 *
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Field = {};/*
---

name: Jx.Plugin.Field.Validator

description: Provides validation services for Jx.Field subclasses

license: MIT-style license.

requires:
 - Jx.Plugin.Field
 - More/Form.Validator
 - More/Form.Validator.Extras

provides: [Jx.Plugin.Field.Validator]

...
 */
// $Id: field.validator.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Plugin.Field.Validator
 *
 * Extends: <Jx.Plugin>
 *
 * Field plugin for enforcing validation when a field is not used in a form.
 *
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner.
 * Parts inspired by mootools-more's Form.Validator class
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Field.Validator = new Class({

    Extends : Jx.Plugin,
    name: 'Field.Validator',

    options: {
        /**
         * Option: validators
         * An array that contains either a string that names the predefined
         * validator to use with its needed options or an object that defines
         * the options of an InputValidator (also with needed options) defined
         * like so:
         *
         * (code)
         * {
         *     validatorClass: 'name:with options',    //gets applied to the field
         *     validator: {                         //used to create the InputValidator
         *         name: 'validatorName',
         *         options: {
         *             errorMsg: 'error message',
         *             test: function(field,props){}
         *         }
         *     }
         * }
         * (end)
         */
        validators: [],
        /**
         * Option: validateOnBlur
         * Determines whether the plugin will validate the field on blur.
         * Defaults to true.
         */
        validateOnBlur: true,
        /**
         * Option: validateOnChange
         * Determines whether the plugin will validate the field on change.
         * Defaults to true.
         */
        validateOnChange: true
    },
    /**
     * Property: valid
     * tells whether this field passed validation or not.
     */
    valid: null,
    /**
     * Property: errors
     * array of errors found on this field
     */
    errors: null,
    validators : null,
    /**
     * APIMethod: init
     * construct a new instance of the plugin.  The plugin must be attached
     * to a Jx.Grid instance to be useful though.
     */
    init: function () {
        this.parent();
        this.errors = [];
        this.validators = new Hash();
        this.bound.validate = this.validate.bind(this);
        this.bound.reset = this.reset.bind(this);
    },
    /**
     * APIMethod: attach
     * Sets up the plugin and connects it to the field
     */
    attach: function (field) {
        if (!$defined(field) && !(field instanceof Jx.Field)) {
            return;
        }
        this.field = field;
        if (this.field.options.required && !this.options.validators.contains('required')) {
            //would have used unshift() but reading tells me it may not work in IE.
            this.options.validators.reverse().push('required');
            this.options.validators.reverse();
        }
        //add validation classes
        this.options.validators.each(function (v) {
            var t = Jx.type(v);
            if (t === 'string') {
                this.field.field.addClass(v);
            } else if (t === 'object') {
                this.validators.set(v.validator.name, new InputValidator(v.validator.name, v.validator.options));
                this.field.field.addClass(v.validatorClass);
            }
        }, this);
        if (this.options.validateOnBlur) {
            this.field.field.addEvent('blur', this.bound.validate);
        }
        if (this.options.validateOnChange) {
            this.field.field.addEvent('change', this.bound.validate);
        }
        this.field.addEvent('reset', this.bound.reset);
    },
    /**
     * APIMethod: detach
     */
    detach: function () {
        if (this.field) {
            this.field.field.removeEvent('blur', this.bound.validate);
            this.field.field.removeEvent('change', this.bound.validate);
        }
        this.field.removeEvent('reset', this.bound.reset);
        this.field = null;
        this.validators = null;
    },

    validate: function () {
        $clear(this.timer);
        this.timer = this.validateField.delay(50, this);
    },

    validateField: function () {
        //loop through the validators
        this.valid = true;
        this.errors = [];
        this.options.validators.each(function (v) {
            var val = (Jx.type(v) === 'string') ? Form.Validator.getValidator(v) : this.validators.get(v.validator.name);
            if (val) {
                if (!val.test(this.field.field)) {
                    this.valid = false;
                    this.errors.push(val.getError(this.field.field));
                }
            }
        }, this);
        if (!this.valid) {
            this.field.domObj.removeClass('jxFieldSuccess').addClass('jxFieldError');
            this.fireEvent('fieldValidationFailed', [this.field, this]);
        } else {
            this.field.domObj.removeClass('jxFieldError').addClass('jxFieldSuccess');
            this.fireEvent('fieldValidationPassed', [this.field, this]);
        }
        return this.valid;
    },

    isValid: function () {
        return this.validateField();
    },

    reset: function () {
        this.valid = null;
        this.errors = [];
        this.field.field.removeClass('jxFieldError').removeClass('jxFieldSuccess');
    },
    /**
     * APIMethod: getErrors
     * USe this method to retrieve all of the errors noted for this field.
     */
    getErrors: function () {
        return this.errors;
    }


});
/*
---

name: Jx.Plugin.Form

description: Namespace for Jx.Form plugins

license: MIT-style license.

requires:
 - Jx.Plugin

provides: [Jx.Plugin.Form]

...
 */
// $Id: plugin.form.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Plugin.Form
 * Form plugin namespace
 *
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Form = {};/*
---

name: Jx.Plugin.Form.Validator

description: Provides validation services for Jx.Form

license: MIT-style license.

requires:
 - Jx.Plugin.Form
 - Jx.Plugin.Field.Validator

provides: [Jx.Plugin.Form.Validator]

...
 */
// $Id: form.validator.js 970 2010-08-23 12:20:57Z pagameba $
/**
 * Class: Jx.Plugin.Form.Validator
 *
 * Extends: <Jx.Plugin>
 *
 * Form plugin for enforcing validation on the fields in a form.
 *
 * License:
 * Copyright (c) 2009, Jonathan Bomgardner.
 * Parts inspired by mootools-more's Form.Validator class
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.Form.Validator = new Class({

    Extends : Jx.Plugin,
    name: 'Form.Validator',

    options: {
        /**
         * Option: fields
         * This will be key/value pairs for each of the fields as shown here:
         * (code)
         * {
         *     fieldID: {
         *          ... options for Field.Validator plugin ...
         *     },
         *     fieldID: {...
         *     }
         * }
         * (end)
         */
        fields: null,
        /**
         * Option: fieldDefaults
         * {Object} contains named defaults for field validators to be
         * triggered on blur or change.  Default is:
         * (code)
         * {
         *    validateOnBlur: true
         *    validateOnChange: false
         * }
         * (end)
         */
        fieldDefaults: {
            validateOnBlur: true,
            validateOnChange: true
        },
        /**
         * Option: validateOnSubmit
         * {Boolean} default true.  Trigger validation on submission of
         * form if true.
         */
        validateOnSubmit: true,
        /**
         * Option: suspendSubmit
         * {Boolean} default false.  Stop form submission when validator is
         * attached.
         */
        suspendSubmit: false
    },
    /**
     * Property: errorMessagess
     * element holding
     */
    errorMessage: null,
    /**
     * APIMethod: init
     * construct a new instance of the plugin.  The plugin must be attached
     * to a Jx.Grid instance to be useful though.
     */
    init: function() {
        this.parent();
        this.bound.validate = this.validate.bind(this);
        this.bound.failed = this.fieldFailed.bind(this);
        this.bound.passed = this.fieldPassed.bind(this);
    },
    /**
     * APIMethod: attach
     * Sets up the plugin and connects it to the form
     */
    attach: function (form) {
        if (!$defined(form) && !(form instanceof Jx.Form)) {
            return;
        }
        this.form = form;
        var plugin = this,
            options = this.options;
        //override the isValid function in the form
        form.isValid = function () {
            return plugin.isValid();
        };

        if (options.validateOnSubmit && !options.suspendSubmit) {
            document.id(this.form).addEvent('submit', this.bound.validate);
        } else if (options.suspendSubmit) {
            document.id(this.form).addEvent('submit', function (ev) {
                ev.stop();
            });
        }

        this.plugins = $H();

        //setup the fields
        $H(options.fields).each(function (val, key) {
            var opts = $merge(this.options.fieldDefaults, val),
                fields = this.form.getFieldsByName(key).
                p;
            if (fields && fields.length) {
                p = new Jx.Plugin.Field.Validator(opts);
                this.plugins.set(key, p);
                p.attach(fields[0]);
                p.addEvent('fieldValidationFailed', this.bound.failed);
                p.addEvent('fieldValidationPassed', this.bound.passed);
            }
        }, this);

    },
    /**
     * APIMethod: detach
     */
    detach: function() {
        if (this.form) {
            document.id(this.form).removeEvent('submit');
        }
        this.form = null;
        this.plugins.each(function(plugin){
            plugin.detach();
            plugin = null;
        },this);
        this.plugins = null;
    },
    /**
     * APIMethod: isValid
     * Call this to determine whether the form validates.
     */
    isValid: function () {
        return this.validate();
    },
    /**
     * Method: validate
     * Method that actually does the work of validating the fields in the form.
     */
    validate: function () {
        var valid = true;
        this.errors = $H();
        this.plugins.each(function(plugin){
            if (!plugin.isValid()) {
                valid = false;
                this.errors.set(plugin.field.id,plugin.getErrors());
            }
        }, this);
        if (valid) {
            this.fireEvent('formValidationPassed', [this.form, this]);
        } else {
            this.fireEvent('formValidationFailed', [this.form, this]);
        }
        return valid;
    },
    /**
     * Method: fieldFailed
     * Refires the fieldValidationFailed event from the field validators it contains
     */
    fieldFailed: function (field, validator) {
        this.fireEvent('fieldValidationFailed', [field, validator]);
    },
    /**
     * Method: fieldPassed
     * Refires the fieldValidationPassed event from the field validators it contains
     */
    fieldPassed: function (field, validator) {
        this.fireEvent('fieldValidationPassed', [field, validator]);
    },
    /**
     * APIMethod: getErrors
     * Use this method to get all of the errors from all of the fields.
     */
    getErrors: function () {
        if (!$defined(this.errors)) {
           this.validate();
        }
        return this.errors;
    }


});
/*
---

name: Jx.Plugin.ToolbarContainer

description: Namespace for Jx.Toolbar.Container

license: MIT-style license.

requires:
 - Jx.Plugin

provides: [Jx.Plugin.ToolbarContainer]

...
 */
/**
 * Class: Jx.Plugin.Toolbar
 * Toolbar plugin namespace
 *
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.ToolbarContainer = {};/*
---

name: Jx.Plugin.ToolbarContainer.TabMenu

description: Adds a menu of tabs to the toolbar container for easy access to all tabs.

license: MIT-style license.

requires:
 - Jx.Plugin.ToolbarContainer

provides: [Jx.Plugin.ToolbarContainer.TabMenu]

...
 */
/**
 * Class: Jx.Plugin.ToolbarContainer.TabMenu
 *
 * Extends: <Jx.Plugin>
 *
 * This plugin provides a menu of tabs in a toolbar (similar to the button in firefox at the end of the row of tabs).
 * It is designed to be used only when the toolbar contains tabs and only when the container is allowed to scroll. Also,
 * this plugin must be added directly to the Toolbar container. You can get a reference to the container for a
 * <Jx.TabBox> by doing
 *
 * (code)
 * var tabbox = new Jx.TabBox();
 * var toolbarContainer = document.id(tabBox.tabBar).getParent('.jxBarContainer').retrieve('jxBarContainer');
 * (end)
 *
 * You can then use the attach method to connect the plugin. Otherwise, you can add it via any normal means to a
 * directly instantiated Container.
 *
 * License:
 * Copyright (c) 2010, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Plugin.ToolbarContainer.TabMenu = new Class({

    Family: 'Jx.Plugin.ToolbarContainer.TabMenu',
    Extends: Jx.Plugin,

    Binds: ['addButton'],

    options: {
    },
    /**
     * Property: tabs
     * holds all of the tabs that we're tracking
     */
    tabs: [],

    init: function () {
        this.parent();
    },

    attach: function (toolbarContainer) {
        this.parent(toolbarContainer);

        this.container = toolbarContainer;

        //we will only be used if the container is allowed to scroll
        if (!this.container.options.scroll) {
            return;
        }

        this.menu = new Jx.Menu({},{
            buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><span class="jxButtonContent"><span class="jxButtonLabel"></span></span></a></span>'
        }).addTo(this.container.controls,'bottom');
        document.id(this.menu).addClass('jxTabMenuRevealer');
        this.container.update();

        //go through all of the existing tabs and add them to the menu
        //grab the toolbar...
        var tb = document.id(this.container).getElement('ul').retrieve('jxToolbar');
        tb.list.each(function(item){
            this.addButton(item);
        },this);

        //connect to the add event of the toolbar list to monitor the addition of buttons
        tb.list.addEvent('add',this.addButton);
    },

    detach: function () {
        this.parent();
    },

    addButton: function (item) {
        var tab;
        tab = (item instanceof Jx.Tab) ? item : document.id(item).getFirst().retrieve('jxTab');


        var l = tab.getLabel();
        if (!$defined(l)) {
            l = '';
        }
        var mi = new Jx.Menu.Item({
            label: l,
            image: tab.options.image,
            onClick: function() {
                if (tab.isActive()) {
                    this.container.scrollIntoView(tab);
                } else {
                    tab.setActive(true);
                }
            }.bind(this)
        });

        document.id(tab).store('menuItem', mi);

        tab.addEvent('close', function() {
            this.menu.remove(mi);
        }.bind(this));

        this.menu.add([mi]);
    }
});/*
---

name: Jx.Adaptor

description: Base class for all Adaptors.

license: MIT-style license.

requires:
 - Jx.Plugin

provides: [Jx.Adaptor]

...
 */
/**
 * Class: Jx.Adaptor
 * Base class for all adaptor implementations. Provides a place to locate all
 * common code and the Jx.Adaptor namespace.  Since it extends <Jx.Plugin> all
 * adaptors will be able to be used as plugins for their respective classes.
 * Also as such, they must have the attach() and detach() methods.
 *
 * Adaptors are specifically used to conform a <Jx.Store> to any one of
 * the different widgets (i.e. Jx.Tree, Jx.ListView, etc...) that could
 * benefit from integration with the store. This approach was taken to minimize
 * data access code in the widgets themselves. Widgets should have no idea where
 * the data/items come from so that they will be usable in the broadest number
 * of situations.
 *
 * Copyright 2010 by Jonathan Bomgardner
 * License: mit-style
 */
Jx.Adaptor = new Class({


  Extends: Jx.Plugin,
  Family: 'Jx.Adaptor',

  name: 'Jx.Adaptor',

  options: {
        /**
         * Option: template
         * The text template to use in creating the items for this adaptor
         */
      template: '',
        /**
         * Option: useTemplate
         * Whether or not to use the text template above. Defaults to true.
         */
      useTemplate: true,
        /**
         * Option: store
         * The store to use with the adaptor.
         */
      store: null
  },
    /**
     * Property: columnsNeeded
     * Will hold an array of the column names needed for processing the
     * template
     */
  columnsNeeded: null,

  init: function () {
      var options = this.options;
      this.parent();

      this.store = options.store;

      if (options.useTemplate && $defined(this.store.getColumns())) {
          this.columnsNeeded = this.store.parseTemplate(options.template);
      }
  },

  attach: function (widget) {
    this.parent(widget);
    this.widget = widget;
  },

  detach: function () {
    this.parent();
  }

});/*
---

name: Jx.Adaptor.Tree

description: Base class for all adaptors that fill Jx.Tree widgets. Also acts as the namespace for other Jx.Tree adaptors.

license: MIT-style license.

requires:
 - Jx.Adaptor

provides: [Jx.Adaptor.Tree]

...
 */
/**
 * Class: Jx.Adaptor.Tree
 * This base class is used to change a store (a flat list of records) into the
 * data structure needed for a Jx.Tree. It will have 2 subclasses:
 * <Jx.Adapter.Tree.Mptt> and <Jx.Adapter.Tree.Parent>.
 *
 * Copyright 2010 by Jonathan Bomgardner
 * License: mit-style
 */
Jx.Adaptor.Tree = new Class({


    Extends: Jx.Adaptor,
    Family: 'Jx.Adaptor.Tree',

    Binds: ['fill','checkFolder'],

    options: {
        /**
         * Option: monitorFolders
         * Determines if this adapter should use monitor the TreeFolder items in
         * order to request any items they should contain if they are empty.
         */
        monitorFolders: false,
        /**
         * Option: startingNodeKey
         * The store primary key to use as the node that we're requesting.
         * Initially set to -1 to indicate that we're request the first set of
         * data
         */
        startingNodeKey: -1,
        /**
         * Option: folderOptions
         * A Hash containing the options for <Jx.TreeFolder>. Defaults to null.
         */
        folderOptions: null,
        /**
         * Option: itemOptions
         * A Hash containing the options for <Jx.TreeItem>. Defaults to null.
         */
        itemOptions: null
    },
    /**
     * Property: folders
     * A Hash containing all of the <Jx.TreeFolders> in this tree.
     */
    folders: null,
    /**
     * Property: currentRecord
     * An integer indicating the last position we were at in the store. Used to
     * allow the adaptor to pick up rendering items after we request additional
     * data.
     */
    currentRecord: -1,
    init: function() {
      this.folders = new Hash();
      this.parent();
    },
    /**
     * APIMethod: attach
     * Attaches this adaptor to a specific tree instance.
     *
     * Parameters:
     * tree - an instance of <Jx.Tree>
     */
    attach: function (tree) {
        this.parent(tree);

        this.tree = tree;

        if (this.options.monitorFolders) {
            this.strategy = this.store.getStrategy('progressive');

            if (!$defined(this.strategy)) {
                this.strategy = new Jx.Store.Strategy.Progressive({
                    dropRecords: false,
                    getPaginationParams: function () { return {}; }
                });
                this.store.addStrategy(this.strategy);
            } else {
                this.strategy.options.dropRecords = false;
                this.strategy.options.getPaginationParams = function () { return {}; };
            }

        }

        this.store.addEvent('storeDataLoaded', this.fill);


    },
    /**
     * APIMethod: detach
     * removes this adaptor from the current tree.
     */
    detach: function () {
      this.parent();
      this.store.removeEvent('storeDataLoaded', this.fill);
    },
    /**
     * APIMethod: firstLoad
     * Method used to start the first store load.
     */
    firstLoad: function () {
      //initial store load
      this.busy = 'tree';
      this.tree.setBusy(true);
        this.store.load({
            node: this.options.startingNodeKey
        });
    },

    /**
     * APIMethod: fill
     * This function will start at this.currentRecord and add the remaining
     * items to the tree.
     */
    fill: function () {
      var i,
          template,
          item,
          p,
          folder,
          options = this.option;

      if (this.busy == 'tree') {
        this.tree.setBusy(false);
        this.busy = 'none';
      } else if (this.busy == 'folder') {
        this.busyFolder.setBusy(false);
        this.busy = 'none';
      }
        var l = this.store.count() - 1;
        for (i = this.currentRecord + 1; i <= l; i++) {
            template = this.store.fillTemplate(i,options.template,this.columnsNeeded);

            if (this.hasChildren(i)) {
                //add as folder
                item = new Jx.TreeFolder($merge(options.folderOptions, {
                    label: template
                }));

                if (options.monitorFolders) {
                  item.addEvent('disclosed', this.checkFolder);
                }

                this.folders.set(i,item);
            } else {
                //add as item
                item = new Jx.TreeItem($merge(options.itemOptions, {
                    label: template
                }));
            }
            document.id(item).store('index', i).store('jxAdaptor', this);
            //check for a parent
            if (this.hasParent(i)) {
                //add as child of parent
                var p = this.getParentIndex(i);
                var folder = this.folders.get(p);
                folder.add(item);
            } else {
                //otherwise add to the tree itself
                this.tree.add(item);
            }
        }
        this.currentRecord = l;
    },
    /**
     * Method: checkFolder
     * Called by the disclose event of the tree to determine if we need to
     * request additional items for a branch of the tree.
     */
    checkFolder: function (folder) {
        var items = folder.items(),
            index,
            node;
        if (!$defined(items) || items.length === 0) {
            //get items via the store
          index = document.id(folder).retrieve('index');
          node = this.store.get('primaryKey', index);
          this.busyFolder = folder;
          this.busyFolder.setBusy(true);
          this.busy = 'folder';
            this.store.load({
                node: node
            });
        }
    },
    /**
     * Method: hasChildren
     * Virtual method to be overridden by sublcasses. Determines if a specific
     * node has any children.
     */
    hasChildren: $empty,
    /**
     * Method: hasParent
     * Virtual method to be overridden by sublcasses. Determines if a specific
     * node has a parent node.
     */
    hasParent: $empty,
    /**
     * Method: getParentIndex
     * Virtual method to be overridden by sublcasses. Determines the store index
     * of the parent node.
     */
    getParentIndex: $empty
});/*
---

name: Jx.Adaptor.Tree.Mptt

description: Fills a Jx.Tree instance from a remote table that represents an MPTT (Modified Preorder Table Traversal) data source.

license: MIT-style license.

requires:
 - Jx.Adaptor.Tree

provides: [Jx.Adaptor.Tree.Mptt]

...
 */
/**
 * Class: Jx.Adaptor.Tree.Mptt
 * This class adapts a table adhering to the classic Parent-style "tree table".
 *
 * This class requires an MPTT (Modified Preorder Tree Traversal) table. The MPTT
 * has a 'left' and a 'right' column that indicates the order of nesting. For
 * more details see the sitepoint.com article at
 * http://articles.sitepoint.com/article/hierarchical-data-database
 *
 * if useAjax option is set to true then this adapter will send an Ajax request
 * to the server, through the store's strategy (should be Jx.Store.Strategy.Progressive)
 * to request additional nodes.
 *
 * Copyright 2010 by Jonathan Bomgardner
 * License: mit-style
 */
Jx.Adaptor.Tree.Mptt = new Class({


    Family: 'Jx.Adaptor.Tree.Mptt',
    Extends: Jx.Adaptor.Tree,

    name: 'tree.mptt',

    options: {
        left: 'left',
        right: 'right'
    },

    /**
     * APIMethod: hasChildren
     *
     * Parameters:
     * index - {integer} the array index of the row in the store (not the
     *          primary key).
     */
    hasChildren: function (index) {
        var l = this.store.get(this.options.left, index).toInt(),
            r = this.store.get(this.options.right, index).toInt();
        return (l + 1 !== r);
    },

    /**
     * APIMethod: hasParent
     *
     * Parameters:
     * index - {integer} the array index of the row in the store (not the
     *          primary key).
     */
    hasParent: function (index) {
        var i = this.getParentIndex(index),
            result = false;
        if ($defined(i)) {
            result = true;
        }
        return result;
    },

    /**
     * APIMethod: getParentIndex
     *
     * Parameters:
     * index - {integer} the array index of the row in the store (not the
     *          primary key).
     */
    getParentIndex: function (index) {
        var store = this.store,
            options = this.options,
            l,
            r,
            i,
            pl,
            pr;
        l = store.get(options.left, index).toInt();
        r = store.get(options.right, index).toInt();
        for (i = index-1; i >= 0; i--) {
            pl = store.get(options.left, i).toInt();
            pr = store.get(options.right, i).toInt();
            if (pl < l && pr > r) {
                return i;
            }
        }
        return null;
    }
});/*
---

name: Jx.Adaptor.Tree.Parent

description: Fills a Jx.Tree instance from a standard parent/child/folder style data table.

license: MIT-style license.

requires:
 - Jx.Adaptor.Tree

provides: [Jx.Adaptor.Tree.Parent]


...
 */
/**
 * Class: Jx.Adapter.Tree.Parent
 * This class adapts a table adhering to the classic Parent-style "tree table".
 *
 * Basically, the store needs to have a column that will indicate the
 * parent of each row. The root(s) of the tree should be indicated by a "-1"
 * in this column. The name of the "parent" column is configurable in the
 * options.
 *
 * if the monitorFolders option is set to true then this adapter will send
 * an Ajax request to the server, through the store's strategy (should be
 * Jx.Store.Strategy.Progressive) to request additional nodes. Also, a column
 * indicating whether this is a folder needs to be set as there is no way to
 * tell if a node has children without it.
 *
 * Copyright 2010 by Jonathan Bomgardner
 * License: mit-style
 */
Jx.Adaptor.Tree.Parent = new Class({


    Extends: Jx.Adaptor.Tree,
    Family: 'Jx.Adaptor.Tree.Parent',

    options: {
        parentColumn: 'parent',
        folderColumn: 'folder'
    },

    /**
     * APIMethod: hasChildren
     *
     * Parameters:
     * index - {integer} the array index of the row in the store (not the
     *          primary key).
     */
    hasChildren: function (index) {
    	return this.store.get(this.options.folderColumn, index);
    },

    /**
     * APIMethod: hasParent
     *
     * Parameters:
     * index - {integer} the array index of the row in the store (not the
     *          primary key).
     */
    hasParent: function (index) {
        if (this.store.get(this.options.parentColumn, index).toInt() !== -1) {
            return true;
        }
        return false;
    },

    /**
     * APIMethod: getParentIndex
     *
     * Parameters:
     * index - {integer} the array index of the row in the store (not the
     *          primary key).
     */
    getParentIndex: function (index) {
        //get the parent based on the index
        var pk = this.store.get(this.options.parentColumn, index);
        return this.store.findByColumn('primaryKey', pk);
    }
});/*
---

name: Jx.Adaptor.Combo

description: Namespace for all Jx.Combo adaptors.

license: MIT-style license.

requires:
 - Jx.Adaptor

provides: [Jx.Adaptor.Combo]

...
*/
/**
 * Class: Jx.Adaptor.Combo
 * The namespace for all combo adaptors
 */
Jx.Adaptor.Combo = {};/*
---

name: Jx.Adaptor.Combo.Fill

description: Loads data into a Jx.Combo instance from designated column(s) of a data source.

license: MIT-style license.

requires:
 - Jx.Adaptor.Combo

provides: [Jx.Adaptor.Combo.Fill]

...
 */
Jx.Adaptor.Combo.Fill = new Class({

    Family: 'Jx.Adaptor.Combo.Fill',
    Extends: Jx.Adaptor,
    name: 'combo.fill',
    Binds: ['fill'],

    /**
     * Note: option.template is used for constructing the text for the label
     */
    options: {
        /**
         * Option: imagePathColumn
         * points to a store column that holds the image information
         * for the combo items.
         */
        imagePathColumn: null,
        /**
         * Option: imageClassColumn
         * Points to a store column that holds the image class
         * information for the combo items
         */
        imageClassColumn: null,
        /**
         * Option: selectedFn
         * This should be a function that could be run to determine if
         * an item should be selected. It will get passed the current store
         * record as the only parameter. It should return either true or false.
         */
        selectedFn: null,
        /**
         * Option: noRepeats
         * This option allows you to use any store even if it has duplicate
         * values in it. With this option set to true the adaptor will keep
         * track of all of teh labels it adds and will not add anything that's
         * a duplicate.
         */
        noRepeats: false
    },

    labels: null,

    init: function () {
        this.parent();

        if (this.options.noRepeat) {
            this.labels = [];
        }
    },

    attach: function (combo) {
        this.parent(combo);

        this.store.addEvent('storeDataLoaded', this.fill);
        if (this.store.loaded) {
            this.fill();
        }
    },

    detach: function () {
        this.parent();

        this.store.removeEvent('storeDataLoaded', this.fill);
    },

    fill: function () {
        var template,
            items=[],
            selected,
            obj,
            options = this.options,
            noRepeat = this.options.noRepeat;
        //empty the combo
        this.widget.empty();
        //reset the store and cycle through creating the objects
        //to pass to combo.add()
        this.store.first();
        items = [];
        this.store.each(function(record){
            template = this.store.fillTemplate(record,options.template,this.columnsNeeded);
            if (!noRepeat || (noRepeat && !this.labels.contains(template))) {
                selected = false;
                if ($type(options.selectedFn) == 'function') {
                    selected = options.selectedFn.run(record);
                }
                obj = {
                    label: template,
                    image: record.get(options.imagePathColumn),
                    imageClass: record.get(options.imageClassColumn),
                    selected: selected
                };
                items.push(obj);

                if (noRepeat) {
                    this.labels.push(template);
                }
            }

        },this);
        //pass all of the objects at once
        this.widget.add(items);
    }
});/*
---

name: Jx.Menu.Context

description: A Jx.Menu that has no button but can be opened at a specific browser location to implement context menus (for instance).

license: MIT-style license.

requires:
 - Jx.Menu

provides: [Jx.Menu.Context]

css:
 - menu

...
 */
// $Id: context.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Menu.Context
 *
 * Extends: Jx.Menu
 *
 * A <Jx.Menu> that has no button but can be opened at a specific
 * browser location to implement context menus (for instance).
 *
 * Example:
 * (code)
 * (end)
 *
 * Events:
 * TODO - add open/close events?
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Menu.Context = new Class({
    Family: 'Jx.Menu.Context',
    Extends: Jx.Menu,

    parameters: ['id'],

    /**
     * APIMethod: render
     * create a new context menu
     */
    render: function() {
        this.id = document.id(this.options.id);
        if (this.id) {
            this.id.addEvent('contextmenu', this.show.bindWithEvent(this));
        }
        this.parent();
    },
    /**
     * Method: show
     * Show the context menu at the location of the mouse click
     *
     * Parameters:
     * e - {Event} the mouse event
     */
    show : function(e) {
        if (this.list.count() ==0) {
            return;
        }

        this.target = e.target;

        this.contentContainer.setStyle('visibility','hidden');
        this.contentContainer.setStyle('display','block');
        document.id(document.body).adopt(this.contentContainer);
        /* we have to size the container for IE to render the chrome correctly
         * but just in the menu/sub menu case - there is some horrible peekaboo
         * bug in IE related to ULs that we just couldn't figure out
         */
         this.contentContainer.setStyles({
           width: null,
           height: null
         });
        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());

        this.position(this.contentContainer, document.body, {
            horizontal: [e.page.x + ' left'],
            vertical: [e.page.y + ' top', e.page.y + ' bottom'],
            offsets: this.chromeOffsets
        });

        this.contentContainer.setStyle('visibility','');
        this.showChrome(this.contentContainer);

        document.addEvent('mousedown', this.bound.hide);
        document.addEvent('keyup', this.bound.keypress);

        e.stop();
    }
});/*
---

name: Jx.Menu.Separator

description: Convenience class to create a visual separator in a menu.

license: MIT-style license.

requires:
 - Jx.Menu

provides: [Jx.Menu.Separator]

images:
 - toolbar_separator_v.png

...
 */
// $Id: menu.separator.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Menu.Separator
 *
 * Extends: <Jx.Object>
 *
 * A convenience class to create a visual separator in a menu.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Menu.Separator = new Class({
    Family: 'Jx.Menu.Separator',
    Extends: Jx.Widget,
    /**
     * Property: domObj
     * {HTMLElement} the HTML element that the separator is contained
     * within
     */
    domObj: null,
    /**
     * Property: owner
     * {<Jx.Menu>, <Jx.Menu.SubMenu>} the menu that the separator is in.
     */
    owner: null,
    options: {
        template: "<li class='jxMenuItemContainer jxMenuItem'><span class='jxMenuSeparator'>&nbsp;</span></li>"
    },
    classes: new Hash({
        domObj: 'jxMenuItem'
    }),
    /**
     * APIMethod: render
     * Create a new instance of a menu separator
     */
    render: function() {
        this.parent();
        this.domObj.store('jxMenuItem', this);
    },
    cleanup: function() {
      this.domObj.eliminate('jxMenuItem');
      this.owner = null;
      this.parent();
    },
    /**
     * Method: setOwner
     * Set the ownder of this menu item
     *
     * Parameters:
     * obj - {Object} the new owner
     */
    setOwner: function(obj) {
        this.owner = obj;
    },
    /**
     * Method: hide
     * Hide the menu item.
     */
    hide: $empty,
    /**
     * Method: show
     * Show the menu item
     */
    show: $empty
});/*
---

name: Jx.Menu.SubMenu

description: A sub menu contains menu items within a main menu or another sub menu.

license: MIT-style license.

requires:
 - Jx.Menu.Item
 - Jx.Menu

provides: [Jx.Menu.SubMenu]

...
 */
// $Id: submenu.js 1012 2011-03-03 20:37:26Z pagameba $
/**
 * Class: Jx.Menu.SubMenu
 *
 * Extends: <Jx.Menu.Item>
 *
 * Implements: <Jx.AutoPosition>, <Jx.Chrome>
 *
 * A sub menu contains menu items within a main menu or another
 * sub menu.
 *
 * The structure of a SubMenu is the same as a <Jx.Menu.Item> with
 * an additional unordered list element appended to the container.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Menu.SubMenu = new Class({
    Family: 'Jx.Menu.SubMenu',
    Extends: Jx.Menu.Item,
    /**
     * Property: subDomObj
     * {HTMLElement} the HTML container for the sub menu.
     */
    subDomObj: null,
    /**
     * Property: owner
     * {<Jx.Menu> or <Jx.SubMenu>} the menu or sub menu that this sub menu
     * belongs
     */
    owner: null,
    /**
     * Property: visibleItem
     * {<Jx.MenuItem>} the visible item within the menu
     */
    visibleItem: null,
    /**
     * Property: list
     * {<Jx.List>} a list to manage menu items
     */
    list: null,
    options: {
        template: '<li class="jxMenuItemContainer"><a class="jxMenuItem jxButtonSubMenu"><span class="jxMenuItemContent"><img class="jxMenuItemIcon" src="'+Jx.aPixel.src+'"><span class="jxMenuItemLabel"></span></span></a></li>',
        position: {
            horizontal: ['right left', 'left right'],
            vertical: ['top top']
        }
    },

    /**
     * APIMethod: render
     * Create a new instance of Jx.SubMenu
     */
    render: function() {
        this.parent();
        this.open = false;

        this.menu = new Jx.Menu(null, {
            position: this.options.position
        });
        this.menu.domObj = this.domObj;
    },
    cleanup: function() {
      this.menu.domObj = null;
      this.menu.destroy();
      this.menu = null;
      this.parent();
    },
    /**
     * Method: setOwner
     * Set the owner of this sub menu
     *
     * Parameters:
     * obj - {Object} the owner
     */
    setOwner: function(obj) {
        this.owner = obj;
        this.menu.owner = obj;
    },
    /**
     * Method: show
     * Show the sub menu
     */
    show: function() {
        if (this.open || this.menu.list.count() == 0) {
            return;
        }
        this.menu.show();
        this.open = true;
        // this.setActive(true);
    },

    eventInMenu: function(e) {
        if (this.visibleItem &&
            this.visibleItem.eventInMenu &&
            this.visibleItem.eventInMenu(e)) {
            return true;
        }
        return document.id(e.target).descendantOf(this.domObj) ||
               this.menu.eventInMenu(e);
    },

    /**
     * Method: hide
     * Hide the sub menu
     */
    hide: function() {
        if (!this.open) {
            return;
        }
        this.open = false;
        this.menu.hide();
        this.visibleItem = null;
    },
    /**
     * Method: add
     * Add menu items to the sub menu.
     *
     * Parameters:
     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
     * can be added by passing multiple arguments to this function.
     */
    add: function(item, position) {
        this.menu.add(item, position, this);
        return this;
    },
    /**
     * Method: remove
     * Remove a menu item from the menu
     *
     * Parameters:
     * item - {<Jx.MenuItem>} the menu item to remove
     */
    remove: function(item) {
        this.menu.remove(item);
        return this;
    },
    /**
     * Method: replace
     * Replace a menu item with another menu item
     *
     * Parameters:
     * what - {<Jx.MenuItem>} the menu item to replace
     * withWhat - {<Jx.MenuItem>} the menu item to replace it with
     */
    replace: function(item, withItem) {
        this.menu.replace(item, withItem);
        return this;
    },
    /**
     * APIMethod: empty
     * remove all items from the sub menu
     */
    empty: function() {
      this.menu.empty();
    },
    /**
     * Method: deactivate
     * Deactivate the sub menu
     *
     * Parameters:
     * e - {Event} the event that triggered the menu being
     * deactivated.
     */
    deactivate: function(e) {
        if (this.owner) {
            this.owner.deactivate(e);
        }
    },
    /**
     * Method: isActive
     * Indicate if this sub menu is active
     *
     * Returns:
     * {Boolean} true if the <Jx.Menu> that ultimately contains
     * this sub menu is active, false otherwise.
     */
    isActive: function() {
        if (this.owner) {
            return this.owner.isActive();
        } else {
            return false;
        }
    },
    /**
     * Method: setActive
     * Set the active state of the <Jx.Menu> that contains this sub menu
     *
     * Parameters:
     * isActive - {Boolean} the new active state
     */
    setActive: function(isActive) {
        if (this.owner && this.owner.setActive) {
            this.owner.setActive(isActive);
        }
    },
    /**
     * Method: setVisibleItem
     * Set a sub menu of this menu to be visible and hide the previously
     * visible one.
     *
     * Parameters:
     * obj - {<Jx.SubMenu>} the sub menu that should be visible
     */
    setVisibleItem: function(obj) {
        if (this.visibleItem != obj) {
            if (this.visibleItem && this.visibleItem.hide) {
                this.visibleItem.hide();
            }
            this.visibleItem = obj;
            this.visibleItem.show();
        }
    }
});/*
---

name: Jx.Splitter.Snap

description: A helper class to create an element that can snap a split panel open or closed.

license: MIT-style license.

requires:
 - Jx.Splitter

provides: [Jx.Splitter.Snap]

...
 */
// $Id: snap.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Splitter.Snap
 *
 * Extends: <Jx.Object>
 *
 * A helper class to create an element that can snap a split panel open or
 * closed.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Splitter.Snap = new Class({
    Family: 'Jx.Splitter.Snap',
    Extends: Jx.Object,
    /**
     * Property: snap
     * {HTMLElement} the DOM element of the snap (the thing that gets
     * clicked).
     */
    snap: null,
    /**
     * Property: element
     * {HTMLElement} An element of the <Jx.Splitter> that gets controlled
     * by this snap
     */
    element: null,
    /**
     * Property: splitter
     * {<Jx.Splitter>} the splitter that this snap is associated with.
     */
    splitter: null,
    /**
     * Property: layout
     * {String} track the layout of the splitter for convenience.
     */
    layout: 'vertical',
    /**
     * Parameters:
     * snap - {HTMLElement} the clickable thing that snaps the element
     *           open and closed
     * element - {HTMLElement} the element that gets controlled by the snap
     * splitter - {<Jx.Splitter>} the splitter that this all happens inside of.
     */
    parameters: ['snap','element','splitter','events'],

    /**
     * APIMethod: init
     * Create a new Jx.Splitter.Snap
     */
    init: function() {
        this.snap = this.options.snap;
        this.element = this.options.element;
        this.splitter = this.options.splitter;
        this.events = this.options.events;
        var jxl = this.element.retrieve('jxLayout');
        jxl.addEvent('sizeChange', this.sizeChange.bind(this));
        this.layout = this.splitter.options.layout;
        var jxo = jxl.options;
        var size = this.element.getContentBoxSize();
        if (this.layout == 'vertical') {
            this.originalSize = size.height;
            this.minimumSize = jxo.minHeight ? jxo.minHeight : 0;
        } else {
            this.originalSize = size.width;
            this.minimumSize = jxo.minWidth ? jxo.minWidth : 0;
        }
        this.events.each(function(eventName) {
            this.snap.addEvent(eventName, this.toggleElement.bind(this));
        }, this);
    },

    /**
     * Method: toggleElement
     * Snap the element open or closed.
     */
    toggleElement: function() {
        var size = this.element.getContentBoxSize();
        var newSize = {};
        if (this.layout == 'vertical') {
            if (size.height == 0) {
                newSize.height = this.originalSize;
            } else {
                this.originalSize = size.height;
                newSize.height = this.minimumSize;
            }
        } else {
            if (size.width == 0) {
                newSize.width = this.originalSize;
            } else {
                this.originalSize = size.width;
                newSize.width = this.minimumSize;
            }
        }
        this.element.resize(newSize);
        this.splitter.sizeChanged();
    },

    /**
     * Method: sizeChanged
     * Handle the size of the element changing to see if the
     * toggle state has changed.
     */
    sizeChange: function() {
        var size = this.element.getContentBoxSize();
        if (this.layout == 'vertical') {
            if (size.height == 0) {
                this.snap.addClass('jxSnapClosed');
                this.snap.removeClass('jxSnapOpened');
            } else {
                this.snap.addClass('jxSnapOpened');
                this.snap.removeClass('jxSnapClosed');
            }
        } else {
            if (size.width == 0) {
                this.snap.addClass('jxSnapClosed');
                this.snap.removeClass('jxSnapOpened');
            } else {
                this.snap.addClass('jxSnapOpened');
                this.snap.removeClass('jxSnapClosed');
            }
        }
    }
});/*
---

name: Jx.Tab

description: A single tab in a tab set.

license: MIT-style license.

requires:
 - Jx.Button
 - Jx.Layout

provides: [Jx.Tab]

css:
 - tab

images:
 - tab_top.png
 - tab_bottom.png
 - tab_left.png
 - tab_right.png
 - tab_close.png

...
 */
// $Id: tab.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Tab
 *
 * Extends: <Jx.Button>
 *
 * A single tab in a tab set.  A tab has a label (displayed in the tab) and a
 * content area that is displayed when the tab is active.  A tab has to be
 * added to both a <Jx.TabSet> (for the content) and <Jx.Toolbar> (for the
 * actual tab itself) in order to be useful.  Alternately, you can use
 * a <Jx.TabBox> which combines both into a single control at the cost of
 * some flexibility in layout options.
 *
 * A tab is a <Jx.ContentLoader> and you can specify the initial content of
 * the tab using any of the methods supported by
 * <Jx.ContentLoader::loadContent>.  You can acccess the actual DOM element
 * that contains the content (if you want to dynamically insert content
 * for instance) via the <Jx.Tab::content> property.
 *
 * A tab is a button of type *toggle* which means that it emits the *up*
 * and *down* events.
 *
 * Example:
 * (code)
 * var tab1 = new Jx.Tab({
 *     label: 'tab 1',
 *     content: 'content1',
 *     onDown: function(tab) {
 *         console.log('tab became active');
 *     },
 *     onUp: function(tab) {
 *         console.log('tab became inactive');
 *     }
 * });
 * (end)
 *
 *
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Tab = new Class({
    Family: 'Jx.Tab',
    Extends: Jx.Button,
    /**
     * Property: content
     * {HTMLElement} The content area that is displayed when the tab is
     * active.
     */
    content: null,

    options: {
        /* Option: toggleClass
         * the CSS class to use for the button, 'jxTabToggle' by default
         */
        toggleClass: 'jxTabToggle',
        /* Option: pressedClass
         * the CSS class to use when the tab is pressed, 'jxTabPressed' by
         * default
         */
        pressedClass: 'jxTabPressed',
        /* Option: activeClass
         * the CSS class to use when the tab is active, 'jxTabActive' by
         * default.
         */
        activeClass: 'jxTabActive',
        /* Option: activeTabClass
         * the CSS class to use on the content area of the active tab,
         * 'tabContentActive' by default.
         */
        activeTabClass: 'tabContentActive',
        /* Option: template
         * the HTML template for a tab
         */
        template: '<span class="jxTabContainer"><a class="jxTab"><span class="jxTabContent"><img class="jxTabIcon" src="'+Jx.aPixel.src+'"><span class="jxTabLabel"></span></span></a><a class="jxTabClose"></a></span>',
        /* Option: contentTemplate
         * the HTML template for a tab's content area
         */
        contentTemplate: '<div class="tabContent"></div>',
        /* Option: close
         * {Boolean} can the tab be closed by the user?  False by default.
         */
        close: false,
        /* Option: shouldClose
         * {Mixed} when a tab is closeable, the shouldClose option is checked
         * first to see if the tab should close.  You can provide a function
         * for this option that can be used to return a boolean value.  This
         * is useful if your tab contains something the user can edit and you
         * want to see if they want to discard the changes before closing.
         * The default value is true, meaning the tab will close immediately.
         * (code)
         * new Jx.Tab({
         *   label: 'test close',
         *   close: true,
         *   shouldClose: function() {
         *     return window.confirm('Are you sure?');
         *   }
         * });
         * (end)
         */
        shouldClose: true
    },
    /**
     * Property: classes
     * {<Hash>} a hash of object properties to CSS class names used to
     * automatically extract references to important DOM elements when
     * processing a widget template.  This allows developers to provide custom
     * HTML structures without affecting the functionality of widgets.
     */
    classes: new Hash({
        domObj: 'jxTabContainer',
        domA: 'jxTab',
        domImg: 'jxTabIcon',
        domLabel: 'jxTabLabel',
        domClose: 'jxTabClose',
        content: 'tabContent'
    }),

    /**
     * Method: render
     * Create a new instance of Jx.Tab.  Any layout options passed are used
     * to create a <Jx.Layout> for the tab content area.
     */
    render : function( ) {
        this.options = $merge(this.options, {toggle:true});
        this.parent();
        this.domObj.store('jxTab', this);
        this.processElements(this.options.contentTemplate, this.classes);
        new Jx.Layout(this.content, this.options);

        // load content onDemand if needed
        if(!this.options.loadOnDemand || this.options.active) {
          this.loadContent(this.content);
          // set active if needed
          if(this.options.active) {
            this.clicked();
          }
        }else{
          this.addEvent('contentLoaded', function(ev) {
            this.setActive(true);
          }.bind(this));
        }
        this.addEvent('down', function(){
            this.content.addClass(this.options.activeTabClass);
        }.bind(this));
        this.addEvent('up', function(){
            this.content.removeClass(this.options.activeTabClass);
        }.bind(this));

        //remove the close button if necessary
        if (this.domClose) {
            if (this.options.close) {
                this.domObj.addClass('jxTabClose');
                this.domClose.addEvent('click', (function(){
                  var shouldClose = true;
                  if ($defined(this.options.shouldClose)) {
                    if (typeof this.options.shouldClose == 'function') {
                      shouldClose = this.options.shouldClose();
                    } else {
                      shouldClose = this.options.shouldClose;
                    }
                  }
                  if (shouldClose) {
                    this.fireEvent('close');
                  }
                }).bind(this));
            } else {
                this.domClose.dispose();
            }
        }
    },
    /**
     * APIMethod: clicked
     * triggered when the user clicks the button, processes the
     * actionPerformed event
     */
    clicked : function(evt) {
      if(this.options.enabled) {
        // just set active when caching is enabled
        if(this.contentIsLoaded && this.options.cacheContent) {
          this.setActive(true);
        // load on demand or reload content if caching is disabled
        }else if(this.options.loadOnDemand || !this.options.cacheContent){
          this.loadContent(this.content);
        }else{
          this.setActive(true);
        }
      }
    }
});

/* keep the old location temporarily */
Jx.Button.Tab = new Class({
  Extends: Jx.Tab,
  init: function() {
    if (console.warn) {
      console.warn('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
    } else {
      console.log('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
    }
    this.parent();
  }
});/*
---

name: Jx.TabSet

description: A TabSet manages a set of Jx.Tab content areas by ensuring that only one of the content areas is visible (i.e. the active tab).

license: MIT-style license.

requires:
 - Jx.Tab

provides: [Jx.TabSet]

...
 */
// $Id: tabset.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.TabSet
 *
 * Extends: <Jx.Object>
 *
 * A TabSet manages a set of <Jx.Tab> content areas by ensuring that only one
 * of the content areas is visible (i.e. the active tab).  TabSet does not
 * manage the actual tabs.  The instances of <Jx.Tab> that are to be managed
 * as a set have to be added to both a TabSet and a <Jx.Toolbar>.  The content
 * areas of the <Jx.Tab>s are sized to fit the content area that the TabSet
 * is managing.
 *
 * Example:
 * (code)
 * var tabBar = new Jx.Toolbar('tabBar');
 * var tabSet = new Jx.TabSet('tabArea');
 *
 * var tab1 = new Jx.Tab('tab 1', {contentID: 'content1'});
 * var tab2 = new Jx.Tab('tab 2', {contentID: 'content2'});
 * var tab3 = new Jx.Tab('tab 3', {contentID: 'content3'});
 * var tab4 = new Jx.Tab('tab 4', {contentURL: 'test_content.html'});
 *
 * tabSet.add(t1, t2, t3, t4);
 * tabBar.add(t1, t2, t3, t4);
 * (end)
 *
 * Events:
 * tabChange - the current tab has changed
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.TabSet = new Class({
    Family: 'Jx.TabSet',
    Extends: Jx.Object,
    /**
     * Property: tabs
     * {Array} array of tabs that are managed by this tab set
     */
    tabs: null,
    /**
     * Property: domObj
     * {HTMLElement} The HTML element that represents this tab set in the DOM.
     * The content areas of each tab are sized to fill the domObj.
     */
    domObj : null,
    /**
     * Parameters:
     * domObj - {HTMLElement} an element or id of an element to put the
     * content of the tabs into.
     * options - an options object, only event handlers are supported
     * as options at this time.
     */
    parameters: ['domObj','options'],

    /**
     * APIMethod: init
     * Create a new instance of <Jx.TabSet> within a specific element of
     * the DOM.
     */
    init: function() {
        this.tabs = [];
        this.domObj = document.id(this.options.domObj);
        if (!this.domObj.hasClass('jxTabSetContainer')) {
            this.domObj.addClass('jxTabSetContainer');
        }
        this.setActiveTabFn = this.setActiveTab.bind(this);
    },
    /**
     * Method: resizeTabBox
     * Resize the tab set content area and propogate the changes to
     * each of the tabs managed by the tab set.
     */
    resizeTabBox: function() {
        if (this.activeTab && this.activeTab.content.resize) {
            this.activeTab.content.resize({forceResize: true});
        }
    },

    /**
     * Method: add
     * Add one or more <Jx.Tab>s to the TabSet.
     *
     * Parameters:
     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab set.  More
     * than one tab can be added by passing extra parameters to this method.
     */
    add: function() {
        $A(arguments).flatten().each(function(tab) {
            if (tab instanceof Jx.Tab) {
                tab.addEvent('down',this.setActiveTabFn);
                tab.tabSet = this;
                this.domObj.appendChild(tab.content);
                this.tabs.push(tab);
                if ((!this.activeTab || tab.options.active) && tab.options.enabled) {
                    tab.options.active = false;
                    tab.setActive(true);
                }
            }
        }, this);
        return this;
    },
    /**
     * Method: remove
     * Remove a tab from this TabSet.  Note that it is the caller's responsibility
     * to remove the tab from the <Jx.Toolbar>.
     *
     * Parameters:
     * tab - {<Jx.Tab>} the tab to remove.
     */
    remove: function(tab) {
        if (tab instanceof Jx.Tab && this.tabs.indexOf(tab) != -1) {
            this.tabs.erase(tab);
            if (this.activeTab == tab) {
                if (this.tabs.length) {
                    this.tabs[0].setActive(true);
                }
            }
            tab.removeEvent('down',this.setActiveTabFn);
            tab.content.dispose();
        }
    },
    /**
     * Method: setActiveTab
     * Set the active tab to the one passed to this method
     *
     * Parameters:
     * tab - {<Jx.Tab>} the tab to make active.
     */
    setActiveTab: function(tab) {
        if (this.activeTab && this.activeTab != tab) {
            this.activeTab.setActive(false);
        }
        this.activeTab = tab;
        if (this.activeTab.content.resize) {
          this.activeTab.content.resize({forceResize: true});
        }
        this.fireEvent('tabChange', [this, tab]);
    }
});



/*
---

name: Jx.TabBox

description: A convenience class to handle the common case of a single toolbar directly attached to the content area of the tabs.

license: MIT-style license.

requires:
 - Jx.Toolbar
 - Jx.Panel
 - Jx.TabSet

provides: [Jx.TabBox]

images:
 - tabbar.png
 - tabbar_bottom.png
 - tabbar_left.png
 - tabbar_right.png

...
 */
// $Id: tabbox.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.TabBox
 *
 * Extends: <Jx.Widget>
 *
 * A convenience class to handle the common case of a single toolbar
 * directly attached to the content area of the tabs.  It manages both a
 * <Jx.Toolbar> and a <Jx.TabSet> so that you don't have to.  If you are using
 * a TabBox, then tabs only have to be added to the TabBox rather than to
 * both a <Jx.TabSet> and a <Jx.Toolbar>.
 *
 * Example:
 * (code)
 * var tabBox = new Jx.TabBox('subTabArea', 'top');
 *
 * var tab1 = new Jx.Button.Tab('Tab 1', {contentID: 'content4'});
 * var tab2 = new Jx.Button.Tab('Tab 2', {contentID: 'content5'});
 *
 * tabBox.add(tab1, tab2);
 * (end)
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.TabBox = new Class({
    Family: 'Jx.TabBox',
    Extends: Jx.Widget,
    options: {
        /* Option: parent
         * a DOM element to add the tab box to
         */
        parent: null,
        /* Option: position
         * the position of the tab bar in the box, one of 'top', 'right',
         * 'bottom' or 'left'.  Top by default.
         */
        position: 'top',
        /* Option: height
         * a fixed height in pixels for the tab box.  If not set, it will fill
         * its container
         */
        height: null,
        /* Option: width
         * a fixed width in pixels for the tab box.  If not set, it will fill
         * its container
         */
        width: null,
        /* Option: scroll
         * should the tab bar scroll its tabs if there are too many to fit
         * in the toolbar, true by default
         */
        scroll:true
    },

    /**
     * Property: tabBar
     * {<Jx.Toolbar>} the toolbar for this tab box.
     */
    tabBar: null,
    /**
     * Property: tabSet
     * {<Jx.TabSet>} the tab set for this tab box.
     */
    tabSet: null,
    /**
     * APIMethod: render
     * Create a new instance of a TabBox.
     */
    render : function() {
        this.parent();
        this.tabBar = new Jx.Toolbar({
            position: this.options.position,
            scroll: this.options.scroll
        });
        this.panel = new Jx.Panel({
            toolbars: [this.tabBar],
            hideTitle: true,
            height: this.options.height,
            width: this.options.width
        });
        this.panel.domObj.addClass('jxTabBox');
        this.tabSet = new Jx.TabSet(this.panel.content);
        this.tabSet.addEvent('tabChange', function(tabSet, tab) {
            this.showItem(tab);
        }.bind(this.tabBar));
        this.domObj = this.panel.domObj;
        /* when the panel changes size, the tab set needs to update
         * the content areas.
         */
         this.panel.addEvent('sizeChange', (function() {
             this.tabSet.resizeTabBox();
             this.tabBar.domObj.getParent('.jxBarContainer').retrieve('jxBarContainer').update();
             this.tabBar.domObj.getParent('.jxBarContainer').addClass('jxTabBar'+this.options.position.capitalize());
         }).bind(this));
        /* when tabs are added or removed, we might need to layout
         * the panel if the toolbar is or becomes empty
         */
        this.tabBar.addEvents({
            add: (function() {
                this.domObj.resize({forceResize: true});
            }).bind(this),
            remove: (function() {
                this.domObj.resize({forceResize: true});
            }).bind(this)
        });
        /* trigger an initial resize when first added to the DOM */
        this.addEvent('addTo', function() {
            this.domObj.resize({forceResize: true});
        });
        if (this.options.parent) {
            this.addTo(this.options.parent);
        }
    },
    /**
     * Method: add
     * Add one or more <Jx.Tab>s to the TabBox.
     *
     * Parameters:
     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab box.  More
     * than one tab can be added by passing extra parameters to this method.
     * Unlike <Jx.TabSet>, tabs do not have to be added to a separate
     * <Jx.Toolbar>.
     */
    add : function() {
        this.tabBar.add.apply(this.tabBar, arguments);
        this.tabSet.add.apply(this.tabSet, arguments);
        $A(arguments).flatten().each(function(tab){
            tab.addEvents({
                close: (function(){
                    this.tabBar.remove(tab);
                    this.tabSet.remove(tab);
                }).bind(this)
            });
        }, this);
        return this;
    },
    /**
     * Method: remove
     * Remove a tab from the TabSet.
     *
     * Parameters:
     * tab - {<Jx.Tab>} the tab to remove.
     */
    remove : function(tab) {
        this.tabBar.remove(tab);
        this.tabSet.remove(tab);
    }
});
/*
---

name: Jx.Toolbar.Separator

description:  A helper class that represents a visual separator in a Jx.Toolbar.

license: MIT-style license.

requires:
 - Jx.Toolbar

provides: [Jx.Toolbar.Separator]

images:
 - toolbar_separator_h.png
 - toolbar_separator_v.png

...
 */
// $Id: toolbar.separator.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Toolbar.Separator
 *
 * Extends: <Jx.Object>
 *
 * A helper class that represents a visual separator in a <Jx.Toolbar>
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Toolbar.Separator = new Class({
    Family: 'Jx.Toolbar.Separator',
    Extends: Jx.Widget,
    /**
     * APIMethod: render
     * Create a new Jx.Toolbar.Separator
     */
    render: function() {
        this.domObj = new Element('li', {'class':'jxToolItem'});
        this.domSpan = new Element('span', {'class':'jxBarSeparator'});
        this.domObj.appendChild(this.domSpan);
    }
});
/*
---

name: Jx.Tree

description: Jx.Tree displays hierarchical data in a tree structure of folders and nodes.

license: MIT-style license.

requires:
 - Jx.Widget
 - Jx.List

provides: [Jx.Tree]

css:
 - tree

images:
 - tree.png
 - tree_vert_line.png
...
 */
// $Id: tree.js 1011 2011-01-24 18:18:42Z pagameba $
/**
 * Class: Jx.Tree
 *
 * Jx.Tree displays hierarchical data in a tree structure of folders and nodes.
 *
 * Example:
 * (code)
 * (end)
 *
 * Extends: <Jx.Widget>
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Tree = new Class({
    Family: 'Jx.Tree',
    Extends: Jx.Widget,
    parameters: ['options','container', 'selection'],
    pluginNamespace: 'Tree',
    /**
     * APIProperty: selection
     * {<Jx.Selection>} the selection object for this tree.
     */
    selection: null,
    /**
     * Property: ownsSelection
     * {Boolean} indicates if this object created the <Jx.Selection> object
     * or not.  If true then the selection object will be destroyed when the
     * tree is destroyed, otherwise the selection object will not be
     * destroyed.
     */
    ownsSelection: false,
    /**
     * Property: list
     * {<Jx.List>} the list object is used to manage the DOM elements of the
     * items added to the tree.
     */
    list: null,
    dirty: true,
    /**
     * APIProperty: domObj
     * {HTMLElement} the DOM element that contains the visual representation
     * of the tree.
     */
    domObj: null,
    options: {
        /**
         * Option: select
         * {Boolean} are items in the tree selectable?  See <Jx.Selection>
         * for other options relating to selections that can be set here.
         */
        select: true,
        /**
         * Option: template
         * the default HTML template for a tree can be overridden
         */
        template: '<ul class="jxTreeRoot"></ul>'
    },
    /**
     * APIProperty: classes
     * {Hash} a hash of property to CSS class names for extracting references
     * to DOM elements from the supplied templates.  Requires
     * domObj element, anything else is optional.
     */
    classes: new Hash({domObj: 'jxTreeRoot'}),

    frozen: false,

    /**
     * APIMethod: render
     * Render the Jx.Tree.
     */
    render: function() {
        this.parent();
        if ($defined(this.options.container) &&
            document.id(this.options.container)) {
            this.domObj = this.options.container;
        }

        if (this.options.selection) {
            this.selection = this.options.selection;
        } else if (this.options.select) {
            this.selection = new Jx.Selection(this.options);
            this.ownsSelection = true;
        }

        this.bound.select = function(item) {
            this.fireEvent('select', item.retrieve('jxTreeItem'));
        }.bind(this);
        this.bound.unselect = function(item) {
            this.fireEvent('unselect', item.retrieve('jxTreeItem'));
        }.bind(this);
        this.bound.onAdd = function(item) {this.update();}.bind(this);
        this.bound.onRemove = function(item) {this.update();}.bind(this);

        if (this.selection && this.ownsSelection) {
            this.selection.addEvents({
                select: this.bound.select,
                unselect: this.bound.unselect
            });
        }

        this.list = new Jx.List(this.domObj, {
                hover: true,
                press: true,
                select: true,
                onAdd: this.bound.onAdd,
                onRemove: this.bound.onRemove
            }, this.selection);
        if (this.options.parent) {
            this.addTo(this.options.parent);
        }
    },
    /**
     * APIMethod: freeze
     * stop the tree from processing updates every time something is added or
     * removed.  Used for bulk changes, call thaw() when done updating.  Note
     * the tree will still display the changes but it will delay potentially
     * expensive recursion across the entire tree on every change just to
     * update visual styles.
     */
    freeze: function() { this.frozen = true; },
    /**
     * APIMethod: thaw
     * unfreeze the tree and recursively update styles
     */
    thaw: function() { this.frozen = false; this.update(true); },

    setDirty: function(state) {
      this.dirty = state;
      if (state && this.owner && this.owner.setDirty) {
        this.owner.setDirty(state);
      }
    },

    /**
     * APIMethod: add
     * add one or more items to the tree at a particular position in the tree
     *
     * Parameters:
     * item - {<Jx.TreeItem>} or an array of items to be added
     * position - {mixed} optional location to add the items.  By default,
     * this is 'bottom' meaning the items are added at the end of the list.
     * See <Jx.List::add> for options
     *
     * Returns:
     * {<Jx.Tree>} a reference to this object for chaining calls
     */
    add: function(item, position) {
        if ($type(item) == 'array') {
            item.each(function(what){ this.add(what, position); }.bind(this) );
            return;
        }
        item.addEvents({
            add: function(what) { this.fireEvent('add', what).bind(this); },
            remove: function(what) { this.fireEvent('remove', what).bind(this); },
            disclose: function(what) { this.fireEvent('disclose', what).bind(this); }
        });
        item.setSelection(this.selection);
        item.owner = this;
        this.list.add(item, position);
        this.setDirty(true);
        this.update(true);
        return this;
    },
    /**
     * APIMethod: remove
     * remove an item from the tree
     *
     * Parameters:
     * item - {<Jx.TreeItem>} the tree item to remove
     *
     * Returns:
     * {<Jx.Tree>} a reference to this object for chaining calls
     */
    remove: function(item) {
        item.removeEvents('add');
        item.removeEvents('remove');
        item.removeEvents('disclose');
        item.owner = null;
        this.list.remove(item);
        item.setSelection(null);
        this.setDirty(true);
        this.update(true);
        return this;
    },
    /**
     * APIMethod: replace
     * replaces one item with another
     *
     * Parameters:
     * item - {<Jx.TreeItem>} the tree item to remove
     * withItem - {<Jx.TreeItem>} the tree item to insert
     *
     * Returns:
     * {<Jx.Tree>} a reference to this object for chaining calls
     */
    replace: function(item, withItem) {
        item.owner = null;
        withItem.owner = this;
        this.list.replace(item, withItem);
        withItem.setSelection(this.selection);
        item.setSelection(null);
        this.setDirty(true);
        this.update(true);
        return this;
    },

    /**
     * Method: cleanup
     * Clean up a Jx.Tree instance
     */
    cleanup: function() {
        // stop updates when removing existing items.
        this.freeze();
        if (this.selection && this.ownsSelection) {
            this.selection.removeEvents({
                select: this.bound.select,
                unselect: this.bound.unselect
            });
            this.selection.destroy();
            this.selection = null;
        }
        this.list.removeEvents({
          remove: this.bound.onRemove,
          add: this.bound.onAdd
        });
        this.list.destroy();
        this.list = null;
        this.bound.select = null;
        this.bound.unselect = null;
        this.bound.onRemove = null;
        this.bound.onAdd = null;
        this.parent();
    },

    /**
     * Method: update
     * Update the CSS of the Tree's DOM element in case it has changed
     * position
     *
     * Parameters:
     * shouldDescend - {Boolean} propagate changes to child nodes?
     */
    update: function(shouldDescend, isLast) {
        // since the memory leak cleanup, it seems that update gets called
        // from the bound onRemove event after the list has been cleaned
        // up.  I suspect that there is a delayed function call for IE in
        // event handling (or some such thing) PS
        if (!this.list) return;
        if (this.frozen) return;

        if ($defined(isLast)) {
            if (isLast) {
                this.domObj.removeClass('jxTreeNest');
            } else {
                this.domObj.addClass('jxTreeNest');
            }
        }
        var last = this.list.count() - 1;
        this.list.each(function(item, idx){
            var lastItem = idx == last;
            if (item.retrieve('jxTreeFolder')) {
                item.retrieve('jxTreeFolder').update(shouldDescend, lastItem);
            }
            if (item.retrieve('jxTreeItem')) {
                item.retrieve('jxTreeItem').update(lastItem);
            }
        });
        this.setDirty(false);
    },

    /**
     * APIMethod: items
     * return an array of tree item instances contained in this tree.
     * Does not descend into folders but does return a reference to the
     * folders
     */
    items: function() {
        return this.list.items().map(function(item) {
            return item.retrieve('jxTreeItem');
        });
    },
    /**
     * APIMethod: empty
     * recursively empty this tree and any folders in it
     */
    empty: function() {
        this.list.items().each(function(item){
          var f = item.retrieve('jxTreeItem');
          if (f && f instanceof Jx.TreeFolder) {
            f.empty();
          }
          if (f && f instanceof Jx.TreeItem) {
            this.remove(f);
            f.destroy();
          }
        }, this);
        this.setDirty(true);
    },

    /**
     * APIMethod: findChild
     * Get a reference to a child node by recursively searching the tree
     *
     * Parameters:
     * path - {Array} an array of labels of nodes to search for
     *
     * Returns:
     * {Object} the node or null if the path was not found
     */
    findChild : function(path) {
        //path is empty - we are asking for this node
        if (path.length == 0) {
            return false;
        }
        //path has more than one thing in it, find a folder and descend into it
        var name = path[0];
        var result = false;
        this.list.items().some(function(item) {
            var treeItem = item.retrieve('jxTreeItem');
            if (treeItem && treeItem.getLabel() == name) {
                if (path.length > 0) {
                    var folder = item.retrieve('jxTreeFolder');
                    if (folder && path.length > 1) {
                        result = folder.findChild(path.slice(1, path.length));
                    } else {
                      result = treeItem;
                    }
                } else {
                    result = treeItem;
                }
            }
            return result;
        });
        return result;
    },

    /**
     * APIMethod: setSelection
     * sets the <Jx.Selection> object to be used by this tree.  Used primarily
     * by <Jx.TreeFolder> to propogate a single selection object throughout a
     * tree.
     *
     * Parameters:
     * selection - {<Jx.Selection>} the new selection object to use
     *
     * Returns:
     * {<Jx.Tree>} a reference to this object for chaining
     */
    setSelection: function(selection) {
        if (this.selection && this.ownsSelection) {
            this.selection.removeEvents(this.bound);
            this.selection.destroy();
            this.ownsSelection = false;
        }
        this.selection = selection;
        this.list.setSelection(selection);
        this.list.each(function(item) {
            item.retrieve('jxTreeItem').setSelection(selection);
        });
        return this;
    }
});

/*
---

name: Jx.TreeItem

description: An item in a tree.

license: MIT-style license.

requires:
 - Jx.Widget

optional:
 - More/Drag

provides: [Jx.TreeItem]

images:
 - tree_hover.png

...
 */
// $Id: treeitem.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.TreeItem
 *
 * Extends: <Jx.Widget>
 *
 * An item in a tree.  An item is a leaf node that has no children.
 *
 * Jx.TreeItem supports selection via the click event.  The application
 * is responsible for changing the style of the selected item in the tree
 * and for tracking selection if that is important.
 *
 * Example:
 * (code)
 * (end)
 *
 * Events:
 * click - triggered when the tree item is clicked
 *
 * Implements:
 * Events - MooTools Class.Extras
 * Options - MooTools Class.Extras
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.TreeItem = new Class ({
    Family: 'Jx.TreeItem',
    Extends: Jx.Widget,
    selection: null,
    /**
     * Property: domObj
     * {HTMLElement} a reference to the HTML element that is the TreeItem
     * in the DOM
     */
    domObj : null,
    /**
     * Property: owner
     * {Object} the folder or tree that this item belongs to
     */
    owner: null,
    options: {
        /* Option: label
         * {String} the label to display for the TreeItem
         */
        label: '',
        /* Option: contextMenu
         * {<Jx.ContextMenu>} the context menu to trigger if there
         * is a right click on the node
         */
        contextMenu: null,
        /* Option: enabled
         * {Boolean} the initial state of the TreeItem.  If the
         * TreeItem is not enabled, it cannot be clicked.
         */
        enabled: true,
        selectable: true,
        /* Option: image
         * {String} URL to an image to use as the icon next to the
         * label of this TreeItem
         */
        image: null,
        /* Option: imageClass
         * {String} CSS class to apply to the image, useful for using CSS
         * sprites
         */
        imageClass: '',
        lastLeafClass: 'jxTreeLeafLast',
        template: '<li class="jxTreeContainer jxTreeLeaf"><img class="jxTreeImage" src="'+Jx.aPixel.src+'" alt="" title=""><a class="jxTreeItem" href="javascript:void(0);"><img class="jxTreeIcon" src="'+Jx.aPixel.src+'" alt="" title=""><span class="jxTreeLabel"></span></a></li>',
        busyMask: {
          message: null
        }
    },
    classes: new Hash({
        domObj: 'jxTreeContainer',
        domA: 'jxTreeItem',
        domImg: 'jxTreeImage',
        domIcon: 'jxTreeIcon',
        domLabel: 'jxTreeLabel'
    }),

    /**
     * APIMethod: render
     * Create a new instance of Jx.TreeItem with the associated options
     */
    render : function() {
        this.parent();

        this.domObj = this.elements.get('jxTreeContainer');
        this.domObj.store('jxTreeItem', this);
        this.domA.store('jxTreeItem', this);
        if (this.options.contextMenu) {
          this.domA.store('jxContextMenu', this.options.contextMenu);
        }
        /* the target for jxPressed, jxSelected, jxHover classes */
        this.domObj.store('jxListTarget', this.domA);

        if (!this.options.selectable) {
            this.domObj.addClass('jxUnselectable');
        }

        if (this.options.id) {
            this.domObj.id = this.options.id;
        }
        if (!this.options.enabled) {
            this.domObj.addClass('jxDisabled');
        }

        if (this.options.image && this.domIcon) {
            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
            if (this.options.imageClass) {
                this.domIcon.addClass(this.options.imageClass);
            }

        }

        if (this.options.label && this.domLabel) {
            this.setLabel(this.options.label);
        }

        if (this.domA) {
            this.domA.addEvents({
                click: this.click.bind(this),
                dblclick: this.dblclick.bind(this),
                drag: function(e) { e.stop(); }
            });
            if (typeof Drag != 'undefined') {
                new Drag(this.domA, {
                    onStart: function() {this.stop();}
                });
            }
        }

        if ($defined(this.options.enabled)) {
            this.enable(this.options.enabled, true);
        }
    },

    setDirty: function(state) {
      if (state && this.owner && this.owner.setDirty) {
        this.owner.setDirty(state);
      }
    },

    /**
     * Method: finalize
     * Clean up the TreeItem and remove all DOM references
     */
    finalize: function() { this.destroy(); },
    /**
     * Method: finalizeItem
     * Clean up the TreeItem and remove all DOM references
     */
    cleanup: function() {
      this.domObj.eliminate('jxTreeItem');
      this.domA.eliminate('jxTreeItem');
      this.domA.eliminate('jxContextMenu');
      this.domObj.eliminate('jxListTarget');
      this.domObj.eliminate('jxListTargetItem');
      this.domA.removeEvents();
      this.owner = null;
      this.selection = null;
      this.parent();
    },
    /**
     * Method: update
     * Update the CSS of the TreeItem's DOM element in case it has changed
     * position
     *
     * Parameters:
     * isLast - {Boolean} is the item the last one or not?
     */
    update : function(isLast) {
        if (isLast) {
            this.domObj.addClass(this.options.lastLeafClass);
        } else {
            this.domObj.removeClass(this.options.lastLeafClass);
        }
    },
    click: function() {
        if (this.options.enabled) {
            this.fireEvent('click', this);
        }
    },
    dblclick: function() {
        if (this.options.enabled) {
            this.fireEvent('dblclick', this);
        }
    },
    /**
     * Method: select
     * Select a tree node.
     */
    select: function() {
        if (this.selection && this.options.enabled) {
            this.selection.select(this.domA);
        }
    },

    /**
     * Method: getLabel
     * Get the label associated with a TreeItem
     *
     * Returns:
     * {String} the name
     */
    getLabel: function() {
        return this.options.label;
    },

    /**
     * Method: setLabel
     * set the label of a tree item
     */
    setLabel: function(label) {
        this.options.label = label;
        if (this.domLabel) {
            this.domLabel.set('html',this.getText(label));
            this.setDirty(true);
        }
    },

    setImage: function(url, imageClass) {
        if (this.domIcon && $defined(url)) {
            this.options.image = url;
            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
            this.setDirty(true);
        }
        if (this.domIcon && $defined(imageClass)) {
            this.domIcon.removeClass(this.options.imageClass);
            this.options.imageClass = imageClass;
            this.domIcon.addClass(imageClass);
            this.setDirty(true);
        }
    },
    enable: function(state, force) {
        if (this.options.enabled != state || force) {
            this.options.enabled = state;
            if (this.options.enabled) {
                this.domObj.removeClass('jxDisabled');
                this.fireEvent('enabled', this);
            } else {
                this.domObj.addClass('jxDisabled');
                this.fireEvent('disabled', this);
                if (this.selection) {
                    this.selection.unselect(document.id(this));
                }
            }
        }
    },

    /**
     * Method: propertyChanged
     * A property of an object has changed, synchronize the state of the
     * TreeItem with the state of the object
     *
     * Parameters:
     * obj - {Object} the object whose state has changed
     */
    propertyChanged : function(obj) {
        this.options.enabled = obj.isEnabled();
        if (this.options.enabled) {
            this.domObj.removeClass('jxDisabled');
        } else {
            this.domObj.addClass('jxDisabled');
        }
    },
    setSelection: function(selection){
        this.selection = selection;
    },

    /**
     * APIMethod: setBusy
     * set the busy state of the widget
     *
     * Parameters:
     * busy - {Boolean} true to set the widget as busy, false to set it as
     *    idle.
     */
    setBusy: function(state) {
      if (this.busy == state) {
        return;
      }
      this.busy = state;
      this.fireEvent('busy', this.busy);
      if (this.busy) {
        this.domImg.addClass(this.options.busyClass)
      } else {
        if (this.options.busyClass) {
          this.domImg.removeClass(this.options.busyClass);
        }
      }
    },
    changeText : function(lang) {
      this.parent();
      this.setLabel(this.options.label);
    }
});
/*
---

name: Jx.TreeFolder

description: A Jx.TreeFolder is an item in a tree that can contain other items. It is expandable and collapsible.

license: MIT-style license.

requires:
 - Jx.TreeItem
 - Jx.Tree

provides: [Jx.TreeFolder]

...
 */
// $Id: treefolder.js 1011 2011-01-24 18:18:42Z pagameba $
/**
 * Class: Jx.TreeFolder
 *
 * A Jx.TreeFolder is an item in a tree that can contain other items.  It is
 * expandable and collapsible.
 *
 * Example:
 * (code)
 * (end)
 *
 * Extends:
 * <Jx.TreeItem>
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.TreeFolder = new Class({
    Family: 'Jx.TreeFolder',
    Extends: Jx.TreeItem,
    /**
     * Property: tree
     * {<Jx.Tree>} a Jx.Tree instance for managing the folder contents
     */
    tree : null,

    options: {
        /* Option: open
         * is the folder open?  false by default.
         */
        open: false,
        /* folders will share a selection with the tree they are in */
        select: false,
        template: '<li class="jxTreeContainer jxTreeBranch"><img class="jxTreeImage" src="'+Jx.aPixel.src+'" alt="" title=""><a class="jxTreeItem" href="javascript:void(0);"><img class="jxTreeIcon" src="'+Jx.aPixel.src+'" alt="" title=""><span class="jxTreeLabel"></span></a><ul class="jxTree"></ul></li>'
    },
    classes: new Hash({
        domObj: 'jxTreeContainer',
        domA: 'jxTreeItem',
        domImg: 'jxTreeImage',
        domIcon: 'jxTreeIcon',
        domLabel: 'jxTreeLabel',
        domTree: 'jxTree'
    }),
    /**
     * APIMethod: render
     * Create a new instance of Jx.TreeFolder
     */
    render : function() {
        this.parent();
        this.domObj.store('jxTreeFolder', this);

        this.bound.toggle = this.toggle.bind(this);

        this.addEvents({
            click: this.bound.toggle,
            dblclick: this.bound.toggle
        });

        if (this.domImg) {
            this.domImg.addEvent('click', this.bound.toggle);
        }

        this.tree = new Jx.Tree({
            template: this.options.template,
            onAdd: function(item) {
                this.update();
                this.fireEvent('add', item);
            }.bind(this),
            onRemove: function(item) {
                this.update();
                this.fireEvent('remove', item);
            }.bind(this)
        }, this.domTree);
        if (this.options.open) {
            this.expand();
        } else {
            this.collapse();
        }

    },
    cleanup: function() {
      this.domObj.eliminate('jxTreeFolder');
      this.removeEvents({
        click: this.bound.toggle,
        dblclick: this.bound.toggle
      });
      if (this.domImg) {
        this.domImg.removeEvent('click', this.bound.toggle);
      }
      this.bound.toggle = null;
      this.tree.destroy();
      this.tree = null;
      this.parent();
    },
    /**
     * APIMethod: add
     * add one or more items to the folder at a particular position in the
     * folder
     *
     * Parameters:
     * item - {<Jx.TreeItem>} or an array of items to be added
     * position - {mixed} optional location to add the items.  By default,
     * this is 'bottom' meaning the items are added at the end of the list.
     * See <Jx.List::add> for options
     *
     * Returns:
     * {<Jx.TreeFolder>} a reference to this object for chaining calls
     */
    add: function(item, position) {
        this.tree.add(item, position);
        return this;
    },
    /**
     * APIMethod: remove
     * remove an item from the folder
     *
     * Parameters:
     * item - {<Jx.TreeItem>} the folder item to remove
     *
     * Returns:
     * {<Jx.Tree>} a reference to this object for chaining calls
     */
    remove: function(item) {
        this.tree.remove(item);
        return this;
    },
    /**
     * APIMethod: replace
     * replaces one item with another
     *
     * Parameters:
     * item - {<Jx.TreeItem>} the tree item to remove
     * withItem - {<Jx.TreeItem>} the tree item to insert
     *
     * Returns:
     * {<Jx.Tree>} a reference to this object for chaining calls
     */
    replace: function(item, withItem) {
        this.tree.replace(item, withItem);
        return this;
    },
    /**
     * APIMethod: items
     * return an array of tree item instances contained in this tree.
     * Does not descend into folders but does return a reference to the
     * folders
     */
    items: function() {
        return this.tree.items();
    },
    /**
     * APIMethod: empty
     * recursively empty this folder and any folders in it
     */
    empty: function() {
        this.tree.empty();
    },
    /**
     * Method: update
     * Update the CSS of the TreeFolder's DOM element in case it has changed
     * position.
     *
     * Parameters:
     * shouldDescend - {Boolean} propagate changes to child nodes?
     * isLast - {Boolean} is this the last item in the list?
     *
     * Returns:
     * {<Jx.TreeFolder>} a reference to this for chaining
     */
    update: function(shouldDescend,isLast) {
        /* avoid update if not attached to tree yet */
        if (!this.domObj.parentNode) return;

        if (this.tree.dirty || (this.owner && this.owner.dirty)) {
          if (!$defined(isLast)) {
              isLast = this.domObj.hasClass('jxTreeBranchLastOpen') ||
                       this.domObj.hasClass('jxTreeBranchLastClosed');
          }

          ['jxTreeBranchOpen','jxTreeBranchLastOpen','jxTreeBranchClosed',
          'jxTreeBranchLastClosed'].each(function(c){
              this.removeClass(c);
          }, this.domObj);

          var c = 'jxTreeBranch';
          c += isLast ? 'Last' : '';
          c += this.options.open ? 'Open' : 'Closed';
          this.domObj.addClass(c);
        }

        this.tree.update(shouldDescend, isLast);
    },
    /**
     * APIMethod: toggle
     * toggle the state of the folder between open and closed
     *
     * Returns:
     * {<Jx.TreeFolder>} a reference to this for chaining
     */
    toggle: function() {
        if (this.options.enabled) {
            if (this.options.open) {
                this.collapse();
            } else {
                this.expand();
            }
        }
        return this;
    },
    /**
     * APIMethod: expand
     * Expands the folder
     *
     * Returns:
     * {<Jx.TreeFolder>} a reference to this for chaining
     */
    expand : function() {
        this.options.open = true;
        document.id(this.tree).setStyle('display', 'block');
        this.setDirty(true);
        this.update(true);
        this.fireEvent('disclosed', this);
        return this;
    },
    /**
     * APIMethod: collapse
     * Collapses the folder
     *
     * Returns:
     * {<Jx.TreeFolder>} a reference to this for chaining
     */
    collapse : function() {
        this.options.open = false;
        document.id(this.tree).setStyle('display', 'none');
        this.setDirty(true);
        this.update(true);
        this.fireEvent('disclosed', this);
        return this;
    },
    /**
     * APIMethod: findChild
     * Get a reference to a child node by recursively searching the tree
     *
     * Parameters:
     * path - {Array} an array of labels of nodes to search for
     *
     * Returns:
     * {Object} the node or null if the path was not found
     */
    findChild : function(path) {
        //path is empty - we are asking for this node
        if (path.length == 0) {
            return this;
        } else {
            return this.tree.findChild(path);
        }
    },
    /**
     * Method: setSelection
     * sets the <Jx.Selection> object to be used by this folder.  Used
     * to propogate a single selection object throughout a tree.
     *
     * Parameters:
     * selection - {<Jx.Selection>} the new selection object to use
     *
     * Returns:
     * {<Jx.TreeFolder>} a reference to this for chaining
     */
    setSelection: function(selection) {
        this.tree.setSelection(selection);
        return this;
    },

    setDirty: function(state) {
      this.parent(state);
      if (this.tree) {
        this.tree.setDirty(true);
      }
    }

});/*
---

name: Jx.Slider

description: A wrapper for mootools' slider class to make it more Jx Friendly.

license: MIT-style license.

requires:
 - Jx.Widget
 - More/Slider

provides: [Jx.Slider]

css:
 - slider

...
 */
// $Id: slider.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Slider
 * This class wraps the mootools-more slider class to make it more Jx friendly
 *
 * Copyright 2009 by Jonathan Bomgardner
 * License: MIT-style
 */
Jx.Slider = new Class({
    Family: 'Jx.Slider',
    Extends: Jx.Widget,

    options: {
        /**
         * Option: template
         * The template used to render the slider
         */
        template: '<div class="jxSliderContainer"><div class="jxSliderKnob"></div></div>',
        /**
         * Option: max
         * The maximum value the slider should have
         */
        max: 100,
        /**
         * Option: min
         * The minimum value the slider should ever have
         */
        min: 0,
        /**
         * Option: step
         * The distance between adjacent steps. For example, the default (1)
         * with min of 0 and max of 100, provides 100 steps between the min
         * and max values
         */
        step: 1,
        /**
         * Option: mode
         * Whether this is a vertical or horizontal slider
         */
        mode: 'horizontal',
        /**
         * Option: wheel
         * Whether the slider reacts to the scroll wheel.
         */
        wheel: true,
        /**
         * Option: snap
         * whether to snap to each step
         */
        snap: true,
        /**
         * Option: startAt
         * The value, or step, to put the slider at initially
         */
        startAt: 0,
        /**
         * Option: offset
         *
         */
        offset: 0,
        onChange: $empty,
        onComplete: $empty
    },
    classes: new Hash({
        domObj: 'jxSliderContainer',
        knob: 'jxSliderKnob'
    }),
    slider: null,
    knob: null,
    sliderOpts: null,
    /**
     * APIMethod: render
     * Create the slider but does not start it up due to issues with it
     * having to be visible before it will work properly.
     */
    render: function () {
        this.parent();

        /**
         * Not sure why this is here...
         */
        /**
        if (this.domObj) {
            return;
        }
        **/

        this.sliderOpts = {
            range: [this.options.min, this.options.max],
            snap: this.options.snap,
            mode: this.options.mode,
            wheel: this.options.wheel,
            steps: (this.options.max - this.options.min) / this.options.step,
            offset: this.options.offset,
            onChange: this.change.bind(this),
            onComplete: this.complete.bind(this)
        };

    },
    /**
     * Method: change
     * Called when the slider moves
     */
    change: function (step) {
        this.fireEvent('change', [step, this]);
    },
    /**
     * Method: complete
     * Called when the slider stops moving and the mouse button is released.
     */
    complete: function (step) {
        this.fireEvent('complete', [step, this]);
    },
    /**
     * APIMethod: start
     * Call this method after the slider has been rendered in the DOM to start
     * it up and position the slider at the startAt poisition.
     */
    start: function () {
        if (!$defined(this.slider)) {
            this.slider = new Slider(this.domObj, this.knob, this.sliderOpts);
        }
        this.slider.set(this.options.startAt);
    },
    /**
     * APIMethod: set
     * set the value of the slider
     */
    set: function(value) {
      this.slider.set(value);
    }
});/*
---

name: Jx.Notice

description: Represents a single item used in a notifier.

license: MIT-style license.

requires:
 - Jx.ListItem

provides: [Jx.Notice]

images:
 - notice.png
 - notice_error.png
 - notice_warning.png
 - notice_success.png
 - icons.png


...
 */
// $Id: notice.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Notice
 *
 * Extends: <Jx.ListItem>
 *
 * Events:
 *
 * MooTools.lang Keys:
 * - notice.closeTip
 *
 * License:
 * Copyright (c) 2009, DM Solutions Group.
 *
 * This file is licensed under an MIT style license
 */
Jx.Notice = new Class({

    Family: 'Jx.Notice',
    Extends: Jx.ListItem,

    options: {
        /**
         * Option: fx
         * the effect to use on the notice when it is shown and hidden,
         * 'fade' by default
         */
        fx: 'fade',
        /**
         * Option: chrome
         * {Boolean} should the notice be displayed with chrome or not,
         * default is false
         */
        chrome: false,
        /**
         * Option: enabled
         * {Boolean} default is false
         */
        enabled: true,
        /**
         * Option: template
         * {String} the HTML template of a notice
         */
        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
        /**
         * Option: klass
         * {String} css class to add to the notice
         */
        klass: ''
    },

    classes: new Hash({
        domObj: 'jxNoticeItemContainer',
        domItem: 'jxNoticeItem',
        domContent: 'jxNotice',
        domClose: 'jxNoticeClose'
    }),

    /**
     * Method: render
     */
    render: function () {
        this.parent();

        if (this.options.klass) {
            this.domObj.addClass(this.options.klass);
        }
        if (this.domClose) {
            this.domClose.addEvent('click', this.close.bind(this));
        }
    },
    /**
     * APIMethod: close
     * close the notice
     */
    close: function() {
        this.fireEvent('close', this);
    },
    /**
     * APIMethod: show
     * show the notice
     */
    show: function(el, onComplete) {
        if (this.options.chrome) {
            this.showChrome();
        }
        if (this.options.fx) {
            document.id(el).adopt(this);
            if (onComplete) onComplete();
        } else {
            document.id(el).adopt(this);
            if (onComplete) onComplete();
        }
    },
    /**
     * APIMethod: hide
     * hide the notice
     */
    hide: function(onComplete) {
        if (this.options.chrome) {
            this.hideChrome();
        }
        if (this.options.fx) {
            document.id(this).dispose();
            if (onComplete) onComplete();
        } else {
            document.id(this).dispose();
            if (onComplete) onComplete();
        }
    },

    changeText : function(lang) {
        this.parent();
        //this.render();
        //this.processElements(this.options.template, this.classes);
    }
});
/**
 * Class: Jx.Notice.Information
 * A <Jx.Notice> subclass useful for displaying informational messages
 */
Jx.Notice.Information = new Class({
    Extends: Jx.Notice,
    options: {
        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
        klass: 'jxNoticeInformation'
    }
});
/**
 * Class: Jx.Notice.Success
 * A <Jx.Notice> subclass useful for displaying success messages
 */
Jx.Notice.Success = new Class({
    Extends: Jx.Notice,
    options: {
        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
        klass: 'jxNoticeSuccess'
    }
});
/**
 * Class: Jx.Notice.Success
 * A <Jx.Notice> subclass useful for displaying warning messages
 */
Jx.Notice.Warning = new Class({
    Extends: Jx.Notice,
    options: {
        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Warning"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
        klass: 'jxNoticeWarning'
    }
});
/**
 * Class: Jx.Notice.Error
 * A <Jx.Notice> subclass useful for displaying error messages
 */
Jx.Notice.Error = new Class({
    Extends: Jx.Notice,
    options: {
        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Error"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
        klass: 'jxNoticeError'
    }
});
/*
---

name: Jx.Notifier

description: Base class for notification areas that can hold temporary notices.

license: MIT-style license.

requires:
 - Jx.ListView
 - Jx.Notice
 - Core/Fx.Tween

provides: [Jx.Notifier]

css:
 - notification


...
 */
// $Id: notifier.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Notifier
 *
 * Extends: <Jx.ListView>
 *
 * Events:
 *
 * License:
 * Copyright (c) 2009, DM Solutions Group.
 *
 * This file is licensed under an MIT style license
 */
Jx.Notifier = new Class({

    Family: 'Jx.Notifier',
    Extends: Jx.ListView,

    options: {
        /**
         * Option: parent
         * The parent this notifier is to be placed in. If not specified, it
         * will be placed in the body of the document.
         */
        parent: null,
        /**
         * Option: template
         * This is the template for the notification container itself, not the
         * actual notice. The actual notice is below in the class property
         * noticeTemplate.
         */
        template: '<div class="jxNoticeListContainer"><ul class="jxNoticeList"></ul></div>',
        /**
         * Option: listOptions
         * An object holding custom options for the internal Jx.List instance.
         */
        listOptions: { }
    },

    classes: new Hash({
        domObj: 'jxNoticeListContainer',
        listObj: 'jxNoticeList'
    }),

    /**
     * Method: render
     * render the widget
     */
    render: function () {
        this.parent();

        if (!$defined(this.options.parent)) {
            this.options.parent = document.body;
        }
        document.id(this.options.parent).adopt(this.domObj);

        this.addEvent('postRender', function() {
            if (Jx.type(this.options.items) == 'array') {
                this.options.items.each(function(item){
                    this.add(item);
                },this);
            }
        }.bind(this));
    },

    /**
     * APIMethod: add
     * Add a new notice to the notifier
     *
     * Parameters:
     * notice - {<Jx.Notice>} the notice to add
     */
    add: function (notice) {
        if (!(notice instanceof Jx.Notice)) {
            notice = new Jx.Notice({content: notice});
        }
        notice.addEvent('close', this.remove.bind(this));
        notice.show(this.listObj);
    },

    /**
     * APIMethod: remove
     * Add a new notice to the notifier
     *
     * Parameters:
     * notice - {<Jx.Notice>} the notice to remove
     */
    remove: function (notice) {
        if (this.domObj.hasChild(notice)) {
            notice.removeEvents('close');
            notice.hide();
        }
    }
});/*
---

name: Jx.Notifier.Float

description: A notification area that floats in a container above other content.

license: MIT-style license.

requires:
 - Jx.Notifier

provides: [Jx.Notifier.Float]

...
 */
// $Id: notifier.float.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Notifier.Float
 * A floating notice area for displaying notices, notices get chrome if
 * the notifier has chrome
 *
 * Extends: <Jx.Notifier>
 *
 * Events:
 *
 * License:
 * Copyright (c) 2009, DM Solutions Group.
 *
 * This file is licensed under an MIT style license
 */
Jx.Notifier.Float = new Class({

    Family: 'Jx.Notifier.Float',
    Extends: Jx.Notifier,

    options: {
        /**
         * Option: chrome
         * {Boolean} should the notifier have chrome - default true
         */
        chrome: true,
        /**
         * Option: fx
         * {String} the effect to use when showing and hiding the notifier,
         * default is null
         */
        fx: null,
        /**
         * Option: width
         * {Integer} the width in pixels of the notifier, default is 250
         */
        width: 250,
        /**
         * Option: position
         * {Object} position options to use with <Jx.Widget::position>
         * for positioning the Notifier
         */
        position: {
            horizontal: 'center center',
            vertical: 'top top'
        }
    },

    /**
     * Method: render
     * render the widget
     */
    render: function () {
        this.parent();
        this.domObj.setStyle('position','absolute');
        if ($defined(this.options.width)) {
            this.domObj.setStyle('width',this.options.width);
        }
        this.position(this.domObj,
                      this.options.parent,
                      this.options.position);
    },

    /**
     * APIMethod: add
     * Add a new notice to the notifier
     *
     * Parameters:
     * notice - {<Jx.Notice>} the notice to add
     */
    add: function(notice) {
        if (!(notice instanceof Jx.Notice)) {
            notice = new Jx.Notice({content: notice});
        }
        notice.options.chrome = this.options.chrome;
        this.parent(notice);
    }
});/*
---

name: Jx.Scrollbar

description: An implementation of a custom CSS-styled scrollbar.

license: MIT-style license.

requires:
 - Jx.Slider

provides: [Jx.Scrollbar]

css:
 - scrollbar

...
 */
// $Id: scrollbar.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Scrollbar
 * Creates a custom scrollbar either vertically or horizontally (determined by
 * options). These scrollbars are designed to be styled entirely through CSS.
 *
 * Copyright 2009 by Jonathan Bomgardner
 * License: MIT-style
 *
 * Based in part on 'Mootools CSS Styled Scrollbar' on
 * http://solutoire.com/2008/03/10/mootools-css-styled-scrollbar/
 */
Jx.Scrollbar = new Class({

    Family: 'Jx.Scrollbar',

    Extends: Jx.Widget,

    Binds: ['scrollIt'],

    options: {
        /**
         * Option: direction
         * Determines which bars are visible. Valid options are 'horizontal'
         * or 'vertical'
         */
        direction: 'vertical',
        /**
         * Option: useMouseWheel
         * Whether to allow the mouse wheel to move the content. Defaults
         * to true.
         */
        useMouseWheel: true,
        /**
         * Option: useScrollers
         * Whether to show the scrollers. Defaults to true.
         */
        useScrollers: true,
        /**
         * Option: scrollerInterval
         * The amount to scroll the content when using the scrollers.
         * useScrollers option must be true. Default is 50 (px).
         */
        scrollerInterval: 50,
        /**
         * Option: template
         * the HTML template for a scrollbar
         */
        template: '<div class="jxScrollbarContainer"><div class="jxScrollLeft"></div><div class="jxSlider"></div><div class="jxScrollRight"></div></div>'
    },

    classes: new Hash({
        domObj: 'jxScrollbarContainer',
        scrollLeft: 'jxScrollLeft',
        scrollRight: 'jxScrollRight',
        sliderHolder: 'jxSlider'
    }),

    el: null,
    //element is the element we want to scroll.
    parameters: ['element', 'options'],

    /**
     * Method: render
     * render the widget
     */
    render: function () {
        this.parent();
        this.el = document.id(this.options.element);
        if (this.el) {
            this.el.addClass('jxHas'+this.options.direction.capitalize()+'Scrollbar');

            //wrap content to make scroll work correctly
            var children = this.el.getChildren();
            this.wrapper = new Element('div',{
                'class': 'jxScrollbarChildWrapper'
            });

            /**
             * the wrapper needs the same settings as the original container
             * specifically, the width and height
             */
            this.wrapper.setStyles({
                width: this.el.getStyle('width'),
                height: this.el.getStyle('height')
            });

            children.inject(this.wrapper);
            this.wrapper.inject(this.el);

            this.domObj.inject(this.el);

            var scrollSize = this.wrapper.getScrollSize();
            var size = this.wrapper.getContentBoxSize();
            this.steps = this.options.direction==='horizontal'?scrollSize.x-size.width:scrollSize.y-size.height;
            this.slider = new Jx.Slider({
                snap: false,
                min: 0,
                max: this.steps,
                step: 1,
                mode: this.options.direction,
                onChange: this.scrollIt

            });

            if (!this.options.useScrollers) {
                this.scrollLeft.dispose();
                this.scrollRight.dispose();
                //set size of the sliderHolder
                if (this.options.direction === 'horizontal') {
                    this.sliderHolder.setStyle('width','100%');
                } else {
                    this.sliderHolder.setStyle('height', '100%');
                }

            } else {
                this.scrollLeft.addEvents({
                    mousedown: function () {
                        this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
                        this.pid = function () {
                            this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
                        }.periodical(1000, this);
                    }.bind(this),
                    mouseup: function () {
                        $clear(this.pid);
                    }.bind(this)
                });
                this.scrollRight.addEvents({
                    mousedown: function () {
                        this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
                        this.pid = function () {
                            this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
                        }.periodical(1000, this);
                    }.bind(this),
                    mouseup: function () {
                        $clear(this.pid);
                    }.bind(this)
                });
                //set size of the sliderHolder
                var holderSize, scrollerRightSize, scrollerLeftSize;
                if (this.options.direction === 'horizontal') {
                    scrollerRightSize = this.scrollRight.getMarginBoxSize().width;
                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().width;
                    holderSize = size.width - scrollerRightSize - scrollerLeftSize;
                    this.sliderHolder.setStyle('width', holderSize + 'px');
                } else {
                    scrollerRightSize = this.scrollRight.getMarginBoxSize().height;
                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().height;
                    holderSize = size.height - scrollerRightSize - scrollerLeftSize;
                    this.sliderHolder.setStyle('height', holderSize + 'px');
                }
            }
            document.id(this.slider).inject(this.sliderHolder);

            //allows mouse wheel to function
            if (this.options.useMouseWheel) {
                $$(this.el, this.domObj).addEvent('mousewheel', function(e){
                    e = new Event(e).stop();
                    var step = this.slider.slider.step - e.wheel * 30;
                    this.slider.slider.set(step);
                }.bind(this));
            }

            //stop slider if we leave the window
            document.id(document.body).addEvent('mouseleave', function(){
                this.slider.slider.drag.stop();
            }.bind(this));

            this.slider.start();
        }
    },

    /**
     * Method: scrollIt
     * scroll the content in response to the slider being moved.
     */
    scrollIt: function (step) {
        var x = this.options.direction==='horizontal'?step:0;
        var y = this.options.direction==='horizontal'?0:step;
        this.wrapper.scrollTo(x,y);
    }
});/*
---

name: Jx.Formatter

description: Base formatter object

license: MIT-style license.

requires:
 - Jx.Object

provides: [Jx.Formatter]

...
 */
 // $Id: formatter.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Formatter
 *
 * Extends: <Jx.Object>
 *
 * Base class used for specific implementations to coerce data into specific formats
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter = new Class({
    Family: 'Jx.Formatter',
    Extends: Jx.Object,

    /**
     * APIMethod: format
     * Empty method that must be overridden by subclasses to provide
     * the needed formatting functionality.
     */
    format: $empty
});/*
---

name: Jx.Formatter.Number

description: Formats numbers including negative and floats

license: MIT-style license.

requires:
 - Jx.Formatter

provides: [Jx.Formatter.Number]

...
 */
// $Id: number.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Formatter.Number
 *
 * Extends: <Jx.Formatter>
 *
 * This class formats numbers. You can have it do the following
 *
 * o replace the decimal separator
 * o use/add a thousands separator
 * o change the precision (number of decimal places)
 * o format negative numbers with parenthesis
 *
 * Example:
 * (code)
 * (end)
 *
 * MooTools.lang Keys:
 * - 'formatter.number'.decimalSeparator
 * - 'formatter.number'.thousandsSeparator
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter.Number = new Class({

    Extends: Jx.Formatter,

    options: {
        /**
         * Option: precision
         * The number of decimal places to round to
         */
        precision: 2,
        /**
         * Option: useParens
         * Whether negative numbers should be done with parenthesis
         */
        useParens: true,
        /**
         * Option: useThousands
         * Whether to use the thousands separator
         */
        useThousands: true
    },
    /**
     * APIMethod: format
     * Formats the provided number
     *
     * Parameters:
     * value - the raw number to format
     */
    format : function (value) {
            //first set the decimal
        if (Jx.type(value) === 'string') {
                //remove commas from the string
            var p = value.split(',');
            value = p.join('');
            value = value.toFloat();
        }
        value = value.toFixed(this.options.precision);

        //split on the decimalSeparator
        var parts = value.split('.');
        var dec = true;
        if (parts.length === 1) {
            dec = false;
        }
        //check for negative
        var neg = false;
        var main;
        var ret = '';
        if (parts[0].contains('-')) {
            neg = true;
            main = parts[0].substring(1, parts[0].length);
        } else {
            main = parts[0];
        }

        if (this.options.useThousands) {
            var l = main.length;
            var left = l % 3;
            var j = 0;
            for (var i = 0; i < l; i++) {
                ret = ret + main.charAt(i);
                if (i === left - 1 && i !== l - 1) {
                    ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
                } else if (i >= left) {
                    j++;
                    if (j === 3 && i !== l - 1) {
                        ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
                        j = 0;
                    }
                }

            }
        } else {
            ret = parts[0];
        }

        if (dec) {
            ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'decimalSeparator'}) + parts[1];
        }
        if (neg && this.options.useParens) {
            ret = "(" + ret + ")";
        } else if (neg && !this.options.useParens) {
            ret = "-" + ret;
        }

        return ret;
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     * 		translations changed.
     */
    changeText: function (lang) {
    	this.parent();
    }
});/*
---

name: Jx.Formatter.Currency

description: Formats input as currency. Currently only US currency is supported

license: MIT-style license.

requires:
 - Jx.Formatter.Number

provides: [Jx.Formatter.Currency]

...
 */
// $Id: currency.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Formatter.Currency
 *
 * Extends: <Jx.Formatter.Number>
 *
 * This class formats numbers as US currency. It actually
 * runs the value through Jx.Formatter.Number first and then
 * updates the returned value as currency.
 *
 * Example:
 * (code)
 * (end)
 *
 * MooTools.lang Keys:
 * - 'formatter.currency'.sign
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter.Currency = new Class({

    Extends: Jx.Formatter.Number,

    options: {},
    /**
     * APIMethod: format
     * Takes a number and formats it as currency.
     *
     * Parameters:
     * value - the number to format
     */
    format: function (value) {

        this.options.precision = 2;

        value = this.parent(value);
        //check for negative
        var neg = false;
        if (value.contains('(') || value.contains('-')) {
            neg = true;
        }

        var ret;
        if (neg && !this.options.useParens) {
            ret = "-" + this.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value.substring(1, value.length);
        } else {
            ret = this.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value;
        }
        return ret;
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     * 		translations changed.
     */
    changeText: function (lang) {
    	this.parent();
    }
});/*
---

name: Jx.Formatter.Date

description: Formats dates using the mootools-more Date extensions

license: MIT-style license.

requires:
 - More/Date.Extras
 - Jx.Formatter

provides: [Jx.Formatter.Date]
...
 */
// $Id: date.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Formatter.Date
 *
 * Extends: <Jx.Formatter>
 *
 * This class formats dates using the mootools-more's
 * Date extensions. See the -more docs for details of
 * supported formats for parsing and formatting.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter.Date = new Class({

    Extends: Jx.Formatter,

    options: {
        /**
         * Option: format
         * The format to use. See the mootools-more Date
         * extension documentation for details on supported
         * formats
         */
        format: '%B %d, %Y'
    },
    /**
     * APIMethod: format
     * Does the work of formatting dates
     *
     * Parameters:
     * value - the text to format
     */
    format: function (value) {
        var d = Date.parse(value);
        return d.format(this.options.format);
    }
});/*
---

name: Jx.Formatter.URI

description: Formats uris using the mootools-more URI extensions

license: MIT-style license.

requires:
 - More/String.Extras
 - Jx.Formatter
 - More/URI

provides: [Jx.Formatter.URI]

...
 */
// $Id: uri.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Formatter.URI
 *
 * Extends: <Jx.Formatter>
 *
 * This class formats URIs using the mootools-more's
 * URI extensions. See the -more docs for details of
 * supported formats for parsing and formatting.
 *
 * @url http://mootools.net/docs/more/Native/URI
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter.Uri = new Class({

    Extends: Jx.Formatter,

    options: {
        /**
         * Option: format
         * The format to use. See the mootools-more URI options
         * to use within a {pattern}
         *   {string} will call the URI.toString() method
         */
        format: '<a href="{string}" target="_blank">{host}</a>'
    },
    /**
     * APIMethod: format
     * Does the work of formatting dates
     *
     * Parameters:
     * value - the text to format
     */
    format: function (value) {
      var uri        = new URI(value),
          uriContent = {},
          pattern    = new Array(),
          patternTmp = this.options.format.match(/\\?\{([^{}]+)\}/g);

      // remove bracktes
      patternTmp.each(function(e) {
        pattern.push(e.slice(1, e.length-1));
      });

      // build object that contains replacements
      for(var i = 0, j = pattern.length; i < j; i++) {
        switch(pattern[i]) {
          case 'string':
            uriContent[pattern[i]] = uri.toString();
            break;
          default:
            uriContent[pattern[i]] = uri.get(pattern[i]);
            break;
        }
      }
      return this.options.format.substitute(uriContent);
    }
});/*
---

name: Jx.Formatter.Boolean

description: Formats boolean input

license: MIT-style license.

requires:
 - Jx.Formatter

provides: [Jx.Formatter.Boolean]
...
 */
// $Id: boolean.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Formatter.Boolean
 *
 * Extends: <Jx.Formatter>
 *
 * This class formats boolean values. You supply the
 * text values for true and false in the options.
 *
 * Example:
 * (code)
 * (end)
 *
 * MooTools.lang Keys:
 * - 'formatter.boolean'.true
 * - 'formatter.boolean'.false
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter.Boolean = new Class({

    Extends: Jx.Formatter,

    options: {},
    /**
     * APIMethod: format
     * Takes a value, determines boolean equivalent and
     * displays the appropriate text value.
     *
     * Parameters:
     * value - the text to format
     */
    format : function (value) {
        var b = false;
        var t = Jx.type(value);
        switch (t) {
        case 'string':
            if (value === 'true') {
                b = true;
            }
            break;
        case 'number':
            if (value !== 0) {
                b = true;
            }
            break;
        case 'boolean':
            b = value;
            break;
        default:
            b = true;
        }
        return b ? this.getText({set:'Jx',key:'formatter.boolean',value:'true'}) : this.getText({set:'Jx',key:'formatter.boolean',value:'false'});
    },

    /**
     * APIMethod: changeText
     * This method should be overridden by subclasses. It should be used
     * to change any language specific default text that is used by the widget.
     *
     * Parameters:
     * lang - the language being changed to or that had it's data set of
     * 		translations changed.
     */
    changeText: function (lang) {
    	this.parent();
    }

});/*
---

name: Jx.Formatter.Phone

description: Formats phone numbers in US format including area code

license: MIT-style license.

requires:
 - Jx.Formatter


provides: [Jx.Formatter.Phone]

...
 */
// $Id: phone.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Formatter.Phone
 *
 * Extends: <Jx.Formatter>
 *
 * Formats data as phone numbers. Currently only US-style phone numbers
 * are supported.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter.Phone = new Class({

    Extends: Jx.Formatter,

    options: {
        /**
         * Option: useParens
         * Whether to use parenthesis () around the area code.
         * Defaults to true
         */
        useParens: true,
        /**
         * Option: separator
         * The character to use as a separator in the phone number.
         * Defaults to a dash '-'.
         */
        separator: "-"
    },
    /**
     * APIMethod: format
     * Format the input as a phone number. This will strip all non-numeric
     * characters and apply the current default formatting
     *
     * Parameters:
     * value - the text to format
     */
    format : function (value) {
        //first strip any non-numeric characters
        var sep = this.options.separator;
        var v = '' + value;
        v = v.replace(/[^0-9]/g, '');

        //now check the length. For right now, we only do US phone numbers
        var ret = '';
        if (v.length === 11) {
            //do everything including the leading 1
            ret = v.charAt(0);
            v = v.substring(1);
        }
        if (v.length === 10) {
            //do the area code
            if (this.options.useParens) {
                ret = ret + "(" + v.substring(0, 3) + ")";
            } else {
                ret = ret + sep + v.substring(0, 3) + sep;
            }
            v = v.substring(3);
        }
        //do the rest of the number
        ret = ret + v.substring(0, 3) + sep + v.substring(3);
        return ret;
    }
});/*
---

name: Jx.Formatter.Text

description: Formats strings by limiting to a max length

license: MIT-style license.

requires:
 - Jx.Formatter

provides: [Jx.Formatter.Text]

...
 */
// $Id: $
/**
 * Class: Jx.Formatter.Text
 *
 * Extends: <Jx.Formatter>
 *
 * This class formats strings by limiting them to a maximum length
 * and replacing the remainder with an ellipsis.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2010, Hughes Gauthier.
 *
 * This file is licensed under an MIT style license
 */
Jx.Formatter.Text = new Class({

  Extends: Jx.Formatter,

  options: {
    /**
     * Option: length
     * {Integer} default null, if set to an integer value greater than
     * 0 then the value will be truncated to length characters and
     * the remaining characters will be replaced by an ellipsis (...)
     */
    length: null,
    /**
     * Option: ellipsis
     * {String} the text to use as the ellipsis when truncating a string
     * default is three periods (...)
     */
    ellipsis: '...'
  },

  format : function (value) {
    var text = '' + value,
        max = this.options.length,
        ellipsis = this.options.ellipsis;

    if (max && text.length > max) {
      text = text.substr(0,max-ellipsis.length) + ellipsis;
    }

    return text;
  }
});/*
---

name: Jx.Field.Check

description: Represents a checkbox input

license: MIT-style license.

requires:
 - Jx.Field

provides: [Jx.Field.Checkbox]

...
 */
// $Id: checkbox.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Field.Check
 *
 * Extends: <Jx.Field>
 *
 * This class represents a radio input field.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 *
 */
Jx.Field.Checkbox = new Class({

    Extends : Jx.Field,

    options : {
        /**
         * Option: template
         * The template used for rendering this field
         */
        template : '<span class="jxInputContainer"><input class="jxInputCheck" type="checkbox" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
        /**
         * Option: checked
         * Whether this field is checked or not
         */
        checked : false,

        labelSeparator: ''
    },
    /**
     * Property: type
     * The type of this field
     */
    type : 'Check',

    /**
     * APIMethod: render
     * Creates a checkbox input field.
    */
    render : function () {
        this.parent();

        if ($defined(this.options.checked) && this.options.checked) {
            if (Browser.Engine.trident) {
                var parent = this.field.getParent();
                var sibling;
                if (parent) {
                    sibling = this.field.getPrevious();
                }
                this.field.setStyle('visibility','hidden');
                this.field.inject(document.id(document.body));
                this.field.checked = true;
                this.field.defaultChecked = true;
                this.field.dispose();
                this.field.setStyle('visibility','visible');
                if (sibling) {
                    this.field.inject(sibling, 'after');
                } else if (parent) {
                    this.field.inject(parent, 'top');
                }
            } else {
                this.field.set("checked", "checked");
                this.field.set("defaultChecked", "checked");
            }
        }

        // add click event to the label to toggle the checkbox
        if(this.label) {
          this.label.addEvent('click', function(ev) {
            this.setValue(this.getValue() != null ? false : true)
          }.bind(this));
        }
    },

    /**
     * APIMethod: setValue
     * Sets the value property of the field
     *
     * Parameters:
     * v - Whether the box shouldbe checked or not. "checked" or "true" if it should be checked.
     */
    setValue : function (v) {
        if (!this.options.readonly) {
            if (v === 'checked' || v === 'true' || v === true) {
                this.field.set('checked', "checked");
            } else {
                this.field.erase('checked');
            }
        }
    },

    /**
     * APIMethod: getValue
     * Returns the current value of the field. The field must be
     * "checked" in order to return a value. Otherwise it returns null.
     */
    getValue : function () {
        if (this.field.get("checked")) {
            return this.field.get("value");
        } else {
            return null;
        }
    },

    /**
     * APIMethod: reset
     * Sets the field back to the value passed in the original
     * options. no IE hack is implemented because the field should
     * already be in the DOM when this is called.
     */
    reset : function () {
        if (this.options.checked) {
            this.field.set('checked', "checked");
        } else {
            this.field.erase('checked');
        }
    },

    getChecked: function () {
        return this.field.get("checked");
    }

});
/*
---

name: Jx.Field.Radio

description: Represents a radio button input

license: MIT-style license.

requires:
 - Jx.Field

provides: [Jx.Field.Radio]

...
 */
// $Id: radio.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Field.Radio
 *
 * Extends: <Jx.Field>
 *
 * This class represents a radio input field.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Field.Radio = new Class({

    Extends: Jx.Field,

    options: {
        /**
         * Option: template
         * The template used to create this field
         */
        template: '<span class="jxInputContainer"><input class="jxInputRadio" type="radio" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
        /**
         * Option: checked
         * whether this radio button is checked or not
         */
        checked: false,

        labelSeparator: ''
    },
    /**
     * Property: type
     * What kind of field this is
     */
    type: 'Radio',

    /**
     * APIMethod: render
     * Creates a radiobutton input field.
     */
    render: function () {
        this.parent();

        if ($defined(this.options.checked) && this.options.checked) {
            if (Browser.Engine.trident) {
                var parent = this.field.getParent();
                var sibling;
                if (parent) {
                    sibling = this.field.getPrevious();
                }
                this.field.setStyle('visibility','hidden');
                this.field.inject(document.id(document.body));
                this.field.checked = true;
                this.field.defaultChecked = true;
                this.field.dispose();
                this.field.setStyle('visibility','visible');
                if (sibling) {
                    this.field.inject(sibling, 'after');
                } else if (parent) {
                    this.field.inject(parent, 'top');
                }
            } else {
                this.field.set("checked", "checked");
                this.field.set("defaultChecked", "checked");
            }
        }

        // add click event to toggle the radio buttons
        this.label.addEvent('click', function(ev) {
          this.field.checked ? this.setValue(false) : this.setValue(true);
        }.bind(this));

    },

    /**
     * APIMethod: setValue
     * Sets the value property of the field
     *
     * Parameters:
     * v - The value to set the field to, "checked" it should be checked.
     */
    setValue: function (v) {
        if (!this.options.readonly) {
            if (v === 'checked' || v === 'true' || v === true) {
                this.field.set('checked', "checked");
            } else {
                this.field.erase('checked');
            }
        }
    },

    /**
     * APIMethod: getValue
     * Returns the current value of the field. The field must be "checked"
     * in order to return a value. Otherwise it returns null.
     */
    getValue: function () {
        if (this.field.get("checked")) {
            return this.field.get("value");
        } else {
            return null;
        }
    },

    /**
     * Method: reset
     * Sets the field back to the value passed in the original
     * options
     */
    reset: function () {
        if (this.options.checked) {
            this.field.set('checked', "checked");
        } else {
            this.field.erase('checked');
        }
    }

});




/*
---

name: Jx.Field.Select

description: Represents a select, or drop down, input

license: MIT-style license.

requires:
 - Jx.Field

provides: [Jx.Field.Select]

...
 */
// $Id: select.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Field.Select
 *
 * Extends: <Jx.Field>
 *
 * This class represents a form select field.
 *
 * These fields are rendered as below.
 *
 * (code)
 * <div id='' class=''>
 *    <label for=''>A label for the field</label>
 *    <select id='' name=''>
 *      <option value='' selected=''>text</option>
 *    </select>
 * </div>
 * (end)
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 *
 */

Jx.Field.Select = new Class({

    Extends: Jx.Field,

    options: {
        /**
         * Option: multiple
         * {Boolean} optional, defaults to false.  If true, then the select
         * will support multi-select
         */
        mulitple: false,
        /**
         * Option: size
         * {Integer} optional, defaults to 1.  If set, then this specifies
         * the number of rows of the select that are visible
         */
        size: 1,
        /**
         * Option: comboOpts
         * Optional, defaults to null. if not null, this should be an array of
         * objects formated like [{value:'', selected: true|false,
         * text:''},...]
         */
        comboOpts: null,
        /**
         * Option: optGroups
         * Optional, defaults to null. if not null this should be an array of
         * objects defining option groups for this select. The comboOpts and
         * optGroups options are mutually exclusive. optGroups will always be
         * shown if defined.
         *
         * define them like [{name: '', options: [{value:'', selected: '',
         * text: ''}...]},...]
         */
        optGroups: null,
        /**
         * Option: template
         * The template for creating this select input
         */
        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><select class="jxInputSelect" name="{name}"></select><span class="jxInputTag"></span></span>'
    },
    /**
     * Property: type
     * Indictes this type of field.
     */
    type: 'Select',

    /**
     * APIMethod: render
     * Creates a select field.
     */
    render: function () {
        this.parent();
        this.field.addEvent('change', function() {this.fireEvent('change', this);}.bind(this));
        if ($defined(this.options.multiple)) {
          this.field.set('multiple', this.options.multiple);
        }
        if ($defined(this.options.size)) {
          this.field.set('size', this.options.size);
        }
        if ($defined(this.options.optGroups)) {
            this.options.optGroups.each(function(group){
                var gr = new Element('optGroup');
                gr.set('label',group.name);
                group.options.each(function(option){
                    var opt = new Element('option', {
                        'value': option.value,
                        'html': this.getText(option.text)
                    });
                    if ($defined(option.selected) && option.selected) {
                        opt.set("selected", "selected");
                    }
                    gr.grab(opt);
                },this);
                this.field.grab(gr);
            },this);
        } else if ($defined(this.options.comboOpts)) {
            this.options.comboOpts.each(function (item) {
                this.addOption(item);
            }, this);
        }
    },

    /**
     * Method: addOption
     * add an option to the select list
     *
     * Parameters:
     * item - The option to add.
     * position (optional) - an integer index or the string 'top'.
     *                     - default is to add at the bottom.
     */
    addOption: function (item, position) {
        var opt = new Element('option', {
            'value': item.value,
            'html': this.getText(item.text)
        });
        if ($defined(item.selected) && item.selected) {
            opt.set("selected", "selected");
        }
        var where = 'bottom';
        var field = this.field;
        if ($defined(position)) {
            if (Jx.type(position) == 'integer' &&
                (position >= 0  && position < field.options.length)) {
                field = this.field.options[position];
                where = 'before';
            } else if (position == 'top') {
                where = 'top';
            }

        }
        opt.inject(field, where);
    },

    /**
     * Method: removeOption
     * removes an option from the select list
     *
     * Parameters:
     *  item - The option to remove.
     */
    removeOption: function (item) {
        //TBD
    },
    /**
     * Method: setValue
     * Sets the value property of the field
     *
     * Parameters:
     * v - The value to set the field to.
     */
    setValue: function (v) {
        if (!this.options.readonly) {
            //loop through the options and set the one that matches v
            $$(this.field.options).each(function (opt) {
                if (opt.get('value') === v) {
                    document.id(opt).set("selected", true);
                }
            }, this);
        }
    },

    /**
     * Method: getValue
     * Returns the current value of the field.
     */
    getValue: function () {
        var index = this.field.selectedIndex;
        //check for a set "value" attribute. If not there return the text
        if (index > -1) {
            var ret = this.field.options[index].get("value");
            if (!$defined(ret)) {
                ret = this.field.options[index].get("text");
            }
            return ret;
        }
    },

    /**
     * APIMethod: empty
     * Empties all options from this select
     */
    empty: function () {
        if ($defined(this.field.options)) {
            $A(this.field.options).each(function (option) {
                this.field.remove(option);
            }, this);
        }
    }
});/*
---

name: Jx.Field.Textarea

description: Represents a textarea input

license: MIT-style license.

requires:
 - Jx.Field

provides: [Jx.Field.Textarea]

...
 */
// $Id: textarea.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Field.Textarea
 *
 * Extends: <Jx.Field>
 *
 * This class represents a textarea field.
 *
 * These fields are rendered as below.
 *
 * (code)
 * <div id='' class=''>
 *    <label for=''>A label for the field</label>
 *    <textarea id='' name='' rows='' cols=''>
 *      value/ext
 *    </textarea>
 * </div>
 * (end)
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 *
 */
Jx.Field.Textarea = new Class({

    Extends: Jx.Field,

    options: {
        /**
         * Option: rows
         * the number of rows to show
         */
        rows: null,
        /**
         * Option: columns
         * the number of columns to show
         */
        columns: null,
        /**
         * Option: template
         * the template used to render this field
         */
        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><textarea class="jxInputTextarea" name="{name}"></textarea><span class="jxInputTag"></span></span>'
    },
    /**
     * Property: type
     * The type of field this is.
     */
    type: 'Textarea',
    /**
     * Property: errorClass
     * The class applied to error elements
     */
    errorClass: 'jxFormErrorTextarea',

    /**
     * APIMethod: render
     * Creates the input.
    */
    render: function () {
        this.parent();

        if ($defined(this.options.rows)) {
            this.field.set('rows', this.options.rows);
        }
        if ($defined(this.options.columns)) {
            this.field.set('cols', this.options.columns);
        }

        //TODO: Do we need to use OverText here as well??

    }
});/*
---

name: Jx.Field.Button

description: Represents a button input

license: MIT-style license.

requires:
 - Jx.Field
 - Jx.Button

provides: [Jx.Field.Button]

...
 */
/**
 * Class: Jx.Field.Button
 *
 * Extends: <Jx.Field>
 *
 * This class represents a button.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, DM Solutions Group
 *
 * This file is licensed under an MIT style license
 */
Jx.Field.Button = new Class({

    Extends: Jx.Field,

    options: {
        /**
         * Option: buttonClass
         * choose the actual Jx.Button subclass to create for this form
         * field.  The default is to create a basic Jx.Button.  To create
         * a different kind of button, pass the class to this option, for
         * instance:
         * (code)
         * buttonClass: Jx.Button.Color
         * (end)
         */
        buttonClass: Jx.Button,

        /**
         * Option: buttonOptions
         */
        buttonOptions: {},
        /**
         * Option: template
         * The template used to render this field
         */
        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxInputButton"></div><span class="jxInputTag"></span></span>'
    },

    button: null,

    /**
     * Property: type
     * The type of this field
     */
    type: 'Button',

    processTemplate: function(template, classes, container) {
        var h = this.parent(template, classes, container);
        this.button = new this.options.buttonClass(this.options.buttonOptions);
        this.button.addEvent('click', function(){
          this.fireEvent('click');
        }.bind(this));
        var c = h.get('jxInputButton');
        if (c) {
            this.button.domObj.replaces(c);
        }
        this.button.setEnabled(!this.options.disabled);
        return h;
    },

    click: function() {
        this.button.clicked();
    },

    enable: function() {
      this.parent();
      this.button.setEnabled(true);
    },

    disable: function() {
      this.parent();
      this.button.setEnabled(false);
    }
});/*
---

name: Jx.Field.Combo

description: Represents an editable combo

license: MIT-style license.

requires:
 - Jx.Field
 - Jx.Button
 - Jx.Menu
 - Jx.Menu.Item
 - Jx.ButtonSet

provides: [Jx.Field.Combo]

...
 */
// $Id: jxcombo.js 993 2010-10-07 19:29:08Z pagameba $
/**
 * Class: Jx.Field.Combo
 *
 * Extends: <Jx.Field>
 *
 *
 * Example:
 * (code)
 * (end)
 *
 * Events:
 * change -
 *
 * License:
 * Copyright (c) 2008, DM Solutions Group Inc.
 *
 * This file is licensed under an MIT style license
 */
Jx.Field.Combo = new Class({
    Family: 'Jx.Field.Combo',
    Extends: Jx.Field,
    pluginNamespace: 'Combo',

    options: {
        buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
        /* Option: template
         */
         template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputCombo"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>'
     },

     type: 'Combo',

    /**
     * APIMethod: render
     * create a new instance of Jx.Field.Combo
     */
    render: function() {
        this.classes.combine({
          wrapper: 'jxInputWrapper',
          revealer: 'jxInputRevealer',
          icon: 'jxInputIcon'
        });
        this.parent();

        var button = new Jx.Button({
          template: this.options.buttonTemplate,
          imageClass: 'jxInputRevealerIcon'
        }).addTo(this.revealer);

        this.menu = new Jx.Menu();
        this.menu.button = button;
        this.buttonSet = new Jx.ButtonSet();

        this.buttonSet = new Jx.ButtonSet({
            onChange: (function(set) {
                var button = set.activeButton;
                var l = button.options.label;
                if (l == '&nbsp;') {
                    l = '';
                }
                this.setLabel(l);
                var img = button.options.image;
                if (img.indexOf('a_pixel') != -1) {
                    img = '';
                }
                this.setImage(img, button.options.imageClass);

                this.fireEvent('change', this);
            }).bind(this)
        });
        if (this.options.items) {
            this.add(this.options.items);
        }
        var that = this;
        button.addEvent('click', function(e) {
            if (this.list.count() === 0) {
                return;
            }
            if (!button.options.enabled) {
                return;
            }
            this.contentContainer.setStyle('visibility','hidden');
            this.contentContainer.setStyle('display','block');
            document.id(document.body).adopt(this.contentContainer);
            /* we have to size the container for IE to render the chrome correctly
             * but just in the menu/sub menu case - there is some horrible peekaboo
             * bug in IE related to ULs that we just couldn't figure out
             */
            this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());

            this.showChrome(this.contentContainer);

            this.position(this.contentContainer, that.field, {
                horizontal: ['left left', 'right right'],
                vertical: ['bottom top', 'top bottom'],
                offsets: this.chromeOffsets
            });

            this.contentContainer.setStyle('visibility','');

            document.addEvent('mousedown', this.bound.hide);
            document.addEvent('keyup', this.bound.keypress);

            this.fireEvent('show', this);
        }.bindWithEvent(this.menu));

        this.menu.addEvents({
            'show': (function() {
                //this.setActive(true);
            }).bind(this),
            'hide': (function() {
                //this.setActive(false);
            }).bind(this)
        });
    },

    setLabel: function(label) {
      if ($defined(this.field)) {
        this.field.value = this.getText(label);
      }
    },

    setImage: function(url, imageClass) {
      if ($defined(this.icon)) {
        this.icon.setStyle('background-image', 'url('+url+')');
        this.icon.setStyle('background-repeat', 'no-repeat');

        if (this.options.imageClass) {
            this.icon.removeClass(this.options.imageClass);
        }
        if (imageClass) {
            this.options.imageClass = imageClass;
            this.icon.addClass(imageClass);
            this.icon.setStyle('background-position','');
        } else {
            this.options.imageClass = null;
            this.icon.setStyle('background-position','center center');
        }
      }
      if (!url) {
        this.wrapper.addClass('jxInputIconHidden');
      } else {
        this.wrapper.removeClass('jxInputIconHidden');
      }
    },

    /**
     * Method: valueChanged
     * invoked when the current value is changed
     */
    valueChanged: function() {
        this.fireEvent('change', this);
    },

    setValue: function(value) {
        this.field.set('value', value);
        this.buttonSet.buttons.each(function(button){
          button.setActive(button.options.label === value);
        },this);
    },

    /**
     * Method: onKeyPress
     * Handle the user pressing a key by looking for an ENTER key to set the
     * value.
     *
     * Parameters:
     * e - {Event} the keypress event
     */
    onKeyPress: function(e) {
        if (e.key == 'enter') {
            this.valueChanged();
        }
    },

    /**
     * Method: add
     * add a new item to the pick list
     *
     * Parameters:
     * options - {Object} object with properties suitable to be passed to
     * a <Jx.Menu.Item.Options> object.  More than one options object can be
     * passed, comma separated or in an array.
     */
    add: function() {
        $A(arguments).flatten().each(function(opt) {
            var button = new Jx.Menu.Item($merge(opt,{
                toggle: true
            }));
            this.menu.add(button);
            this.buttonSet.add(button);
            if (opt.selected) {
              this.buttonSet.setActiveButton(button);
            }
        }, this);
    },

    /**
     * Method: remove
     * Remove the item at the given index.  Not implemented.
     *
     * Parameters:
     * idx - {Mixed} the item to remove by reference or by index.
     */
    remove: function(idx) {
      var item;
      if ($type(idx) == 'number' && idx < this.buttonSet.buttons.length) {
        item = this.buttonSet.buttons[idx];
      } else if ($type(idx) == 'string'){
        this.buttonSet.buttons.some(function(button){
            if (button.options.label === idx) {
                item = button;
                return true;
            }
            return false;
        },this);
      }
      if (item) {
        this.buttonSet.remove(item);
        this.menu.remove(item);
      }
    },
    /**
     * APIMethod: empty
     * remove all values from the combo
     */
    empty: function() {
      this.menu.empty();
      this.buttonSet.empty();
      this.setLabel('');
      this.setImage(Jx.aPixel.src);
    },

    enable: function() {
      this.parent();
      this.menu.setEnabled(true);
    },

    disable: function() {
      this.parent();
      this.menu.setEnabled(false);
    }

});/*
---

name: Jx.Field.Password

description: Represents a password input

license: MIT-style license.

requires:
 - Jx.Field.Text

provides: [Jx.Field.Password]

...
 */
// $Id: password.js 960 2010-06-06 22:23:16Z jonlb@comcast.net $
/**
 * Class: Jx.Field.Password
 *
 * Extends: <Jx.Field.Text>
 *
 * This class represents a password input field.
 *
 * Example:
 * (code)
 * (end)
 *
 * License:
 * Copyright (c) 2009, Jon Bomgardner.
 *
 * This file is licensed under an MIT style license
 */
Jx.Field.Password = new Class({

    Extends: Jx.Field,

    options: {
        template: '<span class="jxInputContainer"><label class="jxInputLabel" ></label><input class="jxInputPassword" type="password" name="{name}"/><span class="jxInputTag"></span></span>'
    },

    type: 'Password'
});/*
---

name: Jx.Field.Color

description: Represents an input field with a jx.button.color

license: MIT-style license.

requires:
 - Jx.Text
 - Jx.Button.Color
 - Jx.Form
 - Jx.Plugin.Field.Validator

provides: [Jx.Field.Color]

...
 */
/**
 * Class: Jx.Field.Color
 *
 * Extends: <Jx.Field>
 *
 * This class provides a Jx.Field.Text in combination with a Jx.Button.Color
 * to have a Colorpicker with an input field.
 *
 * License:
 * Copyright (c) 2010, Paul Spener, Fred Warnock, Conrad Barthelmes
 *
 * This file is licensed under an MIT style license
 */
  Jx.Field.Color = new Class({
    Extends: Jx.Field,
    Binds: ['changed','hide','keyup','changeText'],
    type: 'Color',
    options: {
      buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
      /**
       * Option: template
       * The template used to render this field
       */
      template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputColor"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>',
      /**
       * Option: showOnHover
       * {Boolean} show the color palette when hovering over the input, default
       * is false
       */
      showOnHover: false,
      /**
       *  Option: showDelay
       *  set time in milliseconds when to show the color field on mouseenter
       */
      showDelay: 250,
      /**
       * Option: errorMsg
       * error message for the validator.
       */
      errorMsg: 'Invalid Web-Color',
      /**
       * Option: color
       * a color to initialize the field with, defaults to #000000
       * (black) if not specified.
       */
      color: '#000000'

    },
    button: null,
    validator: null,
    render: function() {
        this.classes.combine({
          wrapper: 'jxInputWrapper',
          revealer: 'jxInputRevealer',
          icon: 'jxInputIcon'
        });
        this.parent();

      var self = this;
      if (!Jx.Field.Color.ColorPalette) {
          Jx.Field.Color.ColorPalette = new Jx.ColorPalette(this.options);
      }
      this.button = new Jx.Button.Flyout({
          template: this.options.buttonTemplate,
          imageClass: 'jxInputRevealerIcon',
          positionElement: this.field,
          onBeforeOpen: function() {
            if (Jx.Field.Color.ColorPalette.currentButton) {
                Jx.Field.Color.ColorPalette.currentButton.hide();
            }
            Jx.Field.Color.ColorPalette.currentButton = this;
            Jx.Field.Color.ColorPalette.addEvent('change', self.changed);
            Jx.Field.Color.ColorPalette.addEvent('click', self.hide);
            this.content.appendChild(Jx.Field.Color.ColorPalette.domObj);
            Jx.Field.Color.ColorPalette.domObj.setStyle('display', 'block');
          },
          onOpen: function() {
            /* setting these before causes an update problem when clicking on
             * a second color button when another one is open - the color
             * wasn't updating properly
             */
            Jx.Field.Color.ColorPalette.options.color = self.options.color;
            Jx.Field.Color.ColorPalette.updateSelected();
          }
        }).addTo(this.revealer);

      this.validator = new Jx.Plugin.Field.Validator({
        validators: [{
            validatorClass: 'colorHex',
            validator: {
              name: 'colorValidator',
              options: {
                validateOnChange: false,
                errorMsg: self.options.errorMsg,
                test: function(field,props) {
                  try {
                    var c = field.get('value').hexToRgb(true);
                    if(c == null) return false;
                    for(var i = 0; i < 3; i++) {
                      if(c[i].toString() == 'NaN') {
                        return false;
                      }
                    }
                  }catch(e) {
                    return false;
                  }
                  c = c.rgbToHex().toUpperCase();
                  self.setColor(c);
                  return true;
                }
              }
            }
        }],
        validateOnBlur: true,
        validateOnChange: true
      });
      this.validator.attach(this);
      this.field.addEvent('keyup', this.onKeyUp.bind(this));
      if (this.options.showOnHover) {
        this.field.addEvent('mouseenter', function(ev) {
          self.button.clicked.delay(self.options.showDelay, self.button);
        });
      }
      this.setValue(this.options.color);
      this.icon.setStyle('background-color', this.options.color);
      //this.addEvent('change', self.changed);
    },
    /*
     * Method: onKeyUp
     *
     * listens to the keyup event and validates the input for a hex color
     *
     */
    onKeyUp : function(ev) {
      var color = this.getValue();
      if (color.substring(0,1) == '#') {
          color = color.substring(1);
      }
      if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
          this.options.color = '#' +color.toUpperCase();
          this.setColor(this.options.color);
      }
    },
    setColor: function(c) {
        this.options.color = c;
        this.setValue(c);
        this.icon.setStyle('background-color', c);
    },
    changed: function() {
        var c = Jx.Field.Color.ColorPalette.options.color;
        this.setColor(c);
    },
    hide: function() {
        this.button.setActive(false);
        Jx.Field.Color.ColorPalette.removeEvent('change', this.changed);
        Jx.Field.Color.ColorPalette.removeEvent('click', this.hide);

        this.button.hide();
        Jx.Field.Color.ColorPalette.currentButton = null;
    },
    changeText: function(lang) {
      this.parent();
    }
  });
/*

  OpenLayers.js -- OpenLayers Map Viewer Library

  Copyright (c) 2006-2013 by OpenLayers Contributors
  Published under the 2-clause BSD license.
  See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.

  Includes compressed code under the following licenses:

  (For uncompressed versions of the code used, please see the
  OpenLayers Github repository: <https://github.com/openlayers/openlayers>)

*/

/**
 * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
 * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 */

/**
 * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
 * Copyright (c) 2006, Yahoo! Inc.
 * All rights reserved.
 * 
 * Redistribution and use of this software in source and binary forms, with or
 * without modification, are permitted provided that the following conditions
 * are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission of Yahoo! Inc.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */
/* ======================================================================
    OpenLayers/SingleFile.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

var OpenLayers = {
    /**
     * Constant: VERSION_NUMBER
     */
    VERSION_NUMBER: "Release 2.13.1",

    /**
     * Constant: singleFile
     * TODO: remove this in 3.0 when we stop supporting build profiles that
     * include OpenLayers.js
     */
    singleFile: true,

    /**
     * Method: _getScriptLocation
     * Return the path to this script. This is also implemented in
     * OpenLayers.js
     *
     * Returns:
     * {String} Path to this script
     */
    _getScriptLocation: (function() {
        var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
            s = document.getElementsByTagName('script'),
            src, m, l = "";
        for(var i=0, len=s.length; i<len; i++) {
            src = s[i].getAttribute('src');
            if(src) {
                m = src.match(r);
                if(m) {
                    l = m[1];
                    break;
                }
            }
        }
        return (function() { return l; });
    })(),
    
    /**
     * Property: ImgPath
     * {String} Set this to the path where control images are stored, a path  
     * given here must end with a slash. If set to '' (which is the default) 
     * OpenLayers will use its script location + "img/".
     * 
     * You will need to set this property when you have a singlefile build of 
     * OpenLayers that either is not named "OpenLayers.js" or if you move
     * the file in a way such that the image directory cannot be derived from 
     * the script location.
     * 
     * If your custom OpenLayers build is named "my-custom-ol.js" and the images
     * of OpenLayers are in a folder "/resources/external/images/ol" a correct
     * way of including OpenLayers in your HTML would be:
     * 
     * (code)
     *   <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
     *   <script type="text/javascript">
     *      // tell OpenLayers where the control images are
     *      // remember the trailing slash
     *      OpenLayers.ImgPath = "/resources/external/images/ol/";
     *   </script>
     * (end code)
     * 
     * Please remember that when your OpenLayers script is not named 
     * "OpenLayers.js" you will have to make sure that the default theme is 
     * loaded into the page by including an appropriate <link>-tag, 
     * e.g.:
     * 
     * (code)
     *   <link rel="stylesheet" href="/path/to/default/style.css"  type="text/css">
     * (end code)
     */
    ImgPath : ''
};
/* ======================================================================
    OpenLayers/BaseTypes.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/SingleFile.js
 */

/** 
 * Header: OpenLayers Base Types
 * OpenLayers custom string, number and function functions are described here.
 */

/**
 * Namespace: OpenLayers.String
 * Contains convenience functions for string manipulation.
 */
OpenLayers.String = {

    /**
     * APIFunction: startsWith
     * Test whether a string starts with another string. 
     * 
     * Parameters:
     * str - {String} The string to test.
     * sub - {String} The substring to look for.
     *  
     * Returns:
     * {Boolean} The first string starts with the second.
     */
    startsWith: function(str, sub) {
        return (str.indexOf(sub) == 0);
    },

    /**
     * APIFunction: contains
     * Test whether a string contains another string.
     * 
     * Parameters:
     * str - {String} The string to test.
     * sub - {String} The substring to look for.
     * 
     * Returns:
     * {Boolean} The first string contains the second.
     */
    contains: function(str, sub) {
        return (str.indexOf(sub) != -1);
    },
    
    /**
     * APIFunction: trim
     * Removes leading and trailing whitespace characters from a string.
     * 
     * Parameters:
     * str - {String} The (potentially) space padded string.  This string is not
     *     modified.
     * 
     * Returns:
     * {String} A trimmed version of the string with all leading and 
     *     trailing spaces removed.
     */
    trim: function(str) {
        return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    },
    
    /**
     * APIFunction: camelize
     * Camel-case a hyphenated string. 
     *     Ex. "chicken-head" becomes "chickenHead", and
     *     "-chicken-head" becomes "ChickenHead".
     *
     * Parameters:
     * str - {String} The string to be camelized.  The original is not modified.
     * 
     * Returns:
     * {String} The string, camelized
     */
    camelize: function(str) {
        var oStringList = str.split('-');
        var camelizedString = oStringList[0];
        for (var i=1, len=oStringList.length; i<len; i++) {
            var s = oStringList[i];
            camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
        }
        return camelizedString;
    },
    
    /**
     * APIFunction: format
     * Given a string with tokens in the form ${token}, return a string
     *     with tokens replaced with properties from the given context
     *     object.  Represent a literal "${" by doubling it, e.g. "${${".
     *
     * Parameters:
     * template - {String} A string with tokens to be replaced.  A template
     *     has the form "literal ${token}" where the token will be replaced
     *     by the value of context["token"].
     * context - {Object} An optional object with properties corresponding
     *     to the tokens in the format string.  If no context is sent, the
     *     window object will be used.
     * args - {Array} Optional arguments to pass to any functions found in
     *     the context.  If a context property is a function, the token
     *     will be replaced by the return from the function called with
     *     these arguments.
     *
     * Returns:
     * {String} A string with tokens replaced from the context object.
     */
    format: function(template, context, args) {
        if(!context) {
            context = window;
        }

        // Example matching: 
        // str   = ${foo.bar}
        // match = foo.bar
        var replacer = function(str, match) {
            var replacement;

            // Loop through all subs. Example: ${a.b.c}
            // 0 -> replacement = context[a];
            // 1 -> replacement = context[a][b];
            // 2 -> replacement = context[a][b][c];
            var subs = match.split(/\.+/);
            for (var i=0; i< subs.length; i++) {
                if (i == 0) {
                    replacement = context;
                }
                if (replacement === undefined) {
                    break;
                }
                replacement = replacement[subs[i]];
            }

            if(typeof replacement == "function") {
                replacement = args ?
                    replacement.apply(null, args) :
                    replacement();
            }

            // If replacement is undefined, return the string 'undefined'.
            // This is a workaround for a bugs in browsers not properly 
            // dealing with non-participating groups in regular expressions:
            // http://blog.stevenlevithan.com/archives/npcg-javascript
            if (typeof replacement == 'undefined') {
                return 'undefined';
            } else {
                return replacement; 
            }
        };

        return template.replace(OpenLayers.String.tokenRegEx, replacer);
    },

    /**
     * Property: tokenRegEx
     * Used to find tokens in a string.
     * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
     */
    tokenRegEx:  /\$\{([\w.]+?)\}/g,
    
    /**
     * Property: numberRegEx
     * Used to test strings as numbers.
     */
    numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
    
    /**
     * APIFunction: isNumeric
     * Determine whether a string contains only a numeric value.
     *
     * Examples:
     * (code)
     * OpenLayers.String.isNumeric("6.02e23") // true
     * OpenLayers.String.isNumeric("12 dozen") // false
     * OpenLayers.String.isNumeric("4") // true
     * OpenLayers.String.isNumeric(" 4 ") // false
     * (end)
     *
     * Returns:
     * {Boolean} String contains only a number.
     */
    isNumeric: function(value) {
        return OpenLayers.String.numberRegEx.test(value);
    },
    
    /**
     * APIFunction: numericIf
     * Converts a string that appears to be a numeric value into a number.
     * 
     * Parameters:
     * value - {String}
     * trimWhitespace - {Boolean}
     *
     * Returns:
     * {Number|String} a Number if the passed value is a number, a String
     *     otherwise. 
     */
    numericIf: function(value, trimWhitespace) {
        var originalValue = value;
        if (trimWhitespace === true && value != null && value.replace) {
            value = value.replace(/^\s*|\s*$/g, "");
        }
        return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
    }

};

/**
 * Namespace: OpenLayers.Number
 * Contains convenience functions for manipulating numbers.
 */
OpenLayers.Number = {

    /**
     * Property: decimalSeparator
     * Decimal separator to use when formatting numbers.
     */
    decimalSeparator: ".",
    
    /**
     * Property: thousandsSeparator
     * Thousands separator to use when formatting numbers.
     */
    thousandsSeparator: ",",
    
    /**
     * APIFunction: limitSigDigs
     * Limit the number of significant digits on a float.
     * 
     * Parameters:
     * num - {Float}
     * sig - {Integer}
     * 
     * Returns:
     * {Float} The number, rounded to the specified number of significant
     *     digits.
     */
    limitSigDigs: function(num, sig) {
        var fig = 0;
        if (sig > 0) {
            fig = parseFloat(num.toPrecision(sig));
        }
        return fig;
    },
    
    /**
     * APIFunction: format
     * Formats a number for output.
     * 
     * Parameters:
     * num  - {Float}
     * dec  - {Integer} Number of decimal places to round to.
     *        Defaults to 0. Set to null to leave decimal places unchanged.
     * tsep - {String} Thousands separator.
     *        Default is ",".
     * dsep - {String} Decimal separator.
     *        Default is ".".
     *
     * Returns:
     * {String} A string representing the formatted number.
     */
    format: function(num, dec, tsep, dsep) {
        dec = (typeof dec != "undefined") ? dec : 0; 
        tsep = (typeof tsep != "undefined") ? tsep :
            OpenLayers.Number.thousandsSeparator; 
        dsep = (typeof dsep != "undefined") ? dsep :
            OpenLayers.Number.decimalSeparator;

        if (dec != null) {
            num = parseFloat(num.toFixed(dec));
        }

        var parts = num.toString().split(".");
        if (parts.length == 1 && dec == null) {
            // integer where we do not want to touch the decimals
            dec = 0;
        }
        
        var integer = parts[0];
        if (tsep) {
            var thousands = /(-?[0-9]+)([0-9]{3})/; 
            while(thousands.test(integer)) { 
                integer = integer.replace(thousands, "$1" + tsep + "$2"); 
            }
        }
        
        var str;
        if (dec == 0) {
            str = integer;
        } else {
            var rem = parts.length > 1 ? parts[1] : "0";
            if (dec != null) {
                rem = rem + new Array(dec - rem.length + 1).join("0");
            }
            str = integer + dsep + rem;
        }
        return str;
    },

    /**
     * Method: zeroPad
     * Create a zero padded string optionally with a radix for casting numbers.
     *
     * Parameters:
     * num - {Number} The number to be zero padded.
     * len - {Number} The length of the string to be returned.
     * radix - {Number} An integer between 2 and 36 specifying the base to use
     *     for representing numeric values.
     */
    zeroPad: function(num, len, radix) {
        var str = num.toString(radix || 10);
        while (str.length < len) {
            str = "0" + str;
        }
        return str;
    }    
};

/**
 * Namespace: OpenLayers.Function
 * Contains convenience functions for function manipulation.
 */
OpenLayers.Function = {
    /**
     * APIFunction: bind
     * Bind a function to an object.  Method to easily create closures with
     *     'this' altered.
     * 
     * Parameters:
     * func - {Function} Input function.
     * object - {Object} The object to bind to the input function (as this).
     * 
     * Returns:
     * {Function} A closure with 'this' set to the passed in object.
     */
    bind: function(func, object) {
        // create a reference to all arguments past the second one
        var args = Array.prototype.slice.apply(arguments, [2]);
        return function() {
            // Push on any additional arguments from the actual function call.
            // These will come after those sent to the bind call.
            var newArgs = args.concat(
                Array.prototype.slice.apply(arguments, [0])
            );
            return func.apply(object, newArgs);
        };
    },
    
    /**
     * APIFunction: bindAsEventListener
     * Bind a function to an object, and configure it to receive the event
     *     object as first parameter when called. 
     * 
     * Parameters:
     * func - {Function} Input function to serve as an event listener.
     * object - {Object} A reference to this.
     * 
     * Returns:
     * {Function}
     */
    bindAsEventListener: function(func, object) {
        return function(event) {
            return func.call(object, event || window.event);
        };
    },
    
    /**
     * APIFunction: False
     * A simple function to that just does "return false". We use this to 
     * avoid attaching anonymous functions to DOM event handlers, which 
     * causes "issues" on IE<8.
     * 
     * Usage:
     * document.onclick = OpenLayers.Function.False;
     * 
     * Returns:
     * {Boolean}
     */
    False : function() {
        return false;
    },

    /**
     * APIFunction: True
     * A simple function to that just does "return true". We use this to 
     * avoid attaching anonymous functions to DOM event handlers, which 
     * causes "issues" on IE<8.
     * 
     * Usage:
     * document.onclick = OpenLayers.Function.True;
     * 
     * Returns:
     * {Boolean}
     */
    True : function() {
        return true;
    },
    
    /**
     * APIFunction: Void
     * A reusable function that returns ``undefined``.
     *
     * Returns:
     * {undefined}
     */
    Void: function() {}

};

/**
 * Namespace: OpenLayers.Array
 * Contains convenience functions for array manipulation.
 */
OpenLayers.Array = {

    /**
     * APIMethod: filter
     * Filter an array.  Provides the functionality of the
     *     Array.prototype.filter extension to the ECMA-262 standard.  Where
     *     available, Array.prototype.filter will be used.
     *
     * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
     *
     * Parameters:
     * array - {Array} The array to be filtered.  This array is not mutated.
     *     Elements added to this array by the callback will not be visited.
     * callback - {Function} A function that is called for each element in
     *     the array.  If this function returns true, the element will be
     *     included in the return.  The function will be called with three
     *     arguments: the element in the array, the index of that element, and
     *     the array itself.  If the optional caller parameter is specified
     *     the callback will be called with this set to caller.
     * caller - {Object} Optional object to be set as this when the callback
     *     is called.
     *
     * Returns:
     * {Array} An array of elements from the passed in array for which the
     *     callback returns true.
     */
    filter: function(array, callback, caller) {
        var selected = [];
        if (Array.prototype.filter) {
            selected = array.filter(callback, caller);
        } else {
            var len = array.length;
            if (typeof callback != "function") {
                throw new TypeError();
            }
            for(var i=0; i<len; i++) {
                if (i in array) {
                    var val = array[i];
                    if (callback.call(caller, val, i, array)) {
                        selected.push(val);
                    }
                }
            }        
        }
        return selected;
    }
    
};
/* ======================================================================
    OpenLayers/BaseTypes/Class.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/SingleFile.js
 */

/**
 * Constructor: OpenLayers.Class
 * Base class used to construct all other classes. Includes support for 
 *     multiple inheritance. 
 *     
 * This constructor is new in OpenLayers 2.5.  At OpenLayers 3.0, the old 
 *     syntax for creating classes and dealing with inheritance 
 *     will be removed.
 * 
 * To create a new OpenLayers-style class, use the following syntax:
 * (code)
 *     var MyClass = OpenLayers.Class(prototype);
 * (end)
 *
 * To create a new OpenLayers-style class with multiple inheritance, use the
 *     following syntax:
 * (code)
 *     var MyClass = OpenLayers.Class(Class1, Class2, prototype);
 * (end)
 * 
 * Note that instanceof reflection will only reveal Class1 as superclass.
 *
 */
OpenLayers.Class = function() {
    var len = arguments.length;
    var P = arguments[0];
    var F = arguments[len-1];

    var C = typeof F.initialize == "function" ?
        F.initialize :
        function(){ P.prototype.initialize.apply(this, arguments); };

    if (len > 1) {
        var newArgs = [C, P].concat(
                Array.prototype.slice.call(arguments).slice(1, len-1), F);
        OpenLayers.inherit.apply(null, newArgs);
    } else {
        C.prototype = F;
    }
    return C;
};

/**
 * Function: OpenLayers.inherit
 *
 * Parameters:
 * C - {Object} the class that inherits
 * P - {Object} the superclass to inherit from
 *
 * In addition to the mandatory C and P parameters, an arbitrary number of
 * objects can be passed, which will extend C.
 */
OpenLayers.inherit = function(C, P) {
   var F = function() {};
   F.prototype = P.prototype;
   C.prototype = new F;
   var i, l, o;
   for(i=2, l=arguments.length; i<l; i++) {
       o = arguments[i];
       if(typeof o === "function") {
           o = o.prototype;
       }
       OpenLayers.Util.extend(C.prototype, o);
   }
};

/**
 * APIFunction: extend
 * Copy all properties of a source object to a destination object.  Modifies
 *     the passed in destination object.  Any properties on the source object
 *     that are set to undefined will not be (re)set on the destination object.
 *
 * Parameters:
 * destination - {Object} The object that will be modified
 * source - {Object} The object with properties to be set on the destination
 *
 * Returns:
 * {Object} The destination object.
 */
OpenLayers.Util = OpenLayers.Util || {};
OpenLayers.Util.extend = function(destination, source) {
    destination = destination || {};
    if (source) {
        for (var property in source) {
            var value = source[property];
            if (value !== undefined) {
                destination[property] = value;
            }
        }

        /**
         * IE doesn't include the toString property when iterating over an object's
         * properties with the for(property in object) syntax.  Explicitly check if
         * the source has its own toString property.
         */

        /*
         * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
         * prototype object" when calling hawOwnProperty if the source object
         * is an instance of window.Event.
         */

        var sourceIsEvt = typeof window.Event == "function"
                          && source instanceof window.Event;

        if (!sourceIsEvt
           && source.hasOwnProperty && source.hasOwnProperty("toString")) {
            destination.toString = source.toString;
        }
    }
    return destination;
};
/* ======================================================================
    OpenLayers/BaseTypes/Bounds.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.Bounds
 * Instances of this class represent bounding boxes.  Data stored as left,
 * bottom, right, top floats. All values are initialized to null, however,
 * you should make sure you set them before using the bounds for anything.
 * 
 * Possible use case:
 * (code)
 *     bounds = new OpenLayers.Bounds();
 *     bounds.extend(new OpenLayers.LonLat(4,5));
 *     bounds.extend(new OpenLayers.LonLat(5,6));
 *     bounds.toBBOX(); // returns 4,5,5,6
 * (end)
 */
OpenLayers.Bounds = OpenLayers.Class({

    /**
     * Property: left
     * {Number} Minimum horizontal coordinate.
     */
    left: null,

    /**
     * Property: bottom
     * {Number} Minimum vertical coordinate.
     */
    bottom: null,

    /**
     * Property: right
     * {Number} Maximum horizontal coordinate.
     */
    right: null,

    /**
     * Property: top
     * {Number} Maximum vertical coordinate.
     */
    top: null,
    
    /**
     * Property: centerLonLat
     * {<OpenLayers.LonLat>} A cached center location.  This should not be
     *     accessed directly.  Use <getCenterLonLat> instead.
     */
    centerLonLat: null,

    /**
     * Constructor: OpenLayers.Bounds
     * Construct a new bounds object. Coordinates can either be passed as four
     * arguments, or as a single argument.
     *
     * Parameters (four arguments):
     * left - {Number} The left bounds of the box.  Note that for width
     *        calculations, this is assumed to be less than the right value.
     * bottom - {Number} The bottom bounds of the box.  Note that for height
     *          calculations, this is assumed to be less than the top value.
     * right - {Number} The right bounds.
     * top - {Number} The top bounds.
     *
     * Parameters (single argument):
     * bounds - {Array(Number)} [left, bottom, right, top]
     */
    initialize: function(left, bottom, right, top) {
        if (OpenLayers.Util.isArray(left)) {
            top = left[3];
            right = left[2];
            bottom = left[1];
            left = left[0];
        }
        if (left != null) {
            this.left = OpenLayers.Util.toFloat(left);
        }
        if (bottom != null) {
            this.bottom = OpenLayers.Util.toFloat(bottom);
        }
        if (right != null) {
            this.right = OpenLayers.Util.toFloat(right);
        }
        if (top != null) {
            this.top = OpenLayers.Util.toFloat(top);
        }
    },

    /**
     * Method: clone
     * Create a cloned instance of this bounds.
     *
     * Returns:
     * {<OpenLayers.Bounds>} A fresh copy of the bounds
     */
    clone:function() {
        return new OpenLayers.Bounds(this.left, this.bottom, 
                                     this.right, this.top);
    },

    /**
     * Method: equals
     * Test a two bounds for equivalence.
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     *
     * Returns:
     * {Boolean} The passed-in bounds object has the same left,
     *           right, top, bottom components as this.  Note that if bounds 
     *           passed in is null, returns false.
     */
    equals:function(bounds) {
        var equals = false;
        if (bounds != null) {
            equals = ((this.left == bounds.left) && 
                      (this.right == bounds.right) &&
                      (this.top == bounds.top) && 
                      (this.bottom == bounds.bottom));
        }
        return equals;
    },

    /** 
     * APIMethod: toString
     * Returns a string representation of the bounds object.
     * 
     * Returns:
     * {String} String representation of bounds object. 
     */
    toString:function() {
        return [this.left, this.bottom, this.right, this.top].join(",");
    },

    /**
     * APIMethod: toArray
     * Returns an array representation of the bounds object.
     *
     * Returns an array of left, bottom, right, top properties, or -- when the
     *     optional parameter is true -- an array of the  bottom, left, top,
     *     right properties.
     *
     * Parameters:
     * reverseAxisOrder - {Boolean} Should we reverse the axis order?
     *
     * Returns:
     * {Array} array of left, bottom, right, top
     */
    toArray: function(reverseAxisOrder) {
        if (reverseAxisOrder === true) {
            return [this.bottom, this.left, this.top, this.right];
        } else {
            return [this.left, this.bottom, this.right, this.top];
        }
    },    

    /** 
     * APIMethod: toBBOX
     * Returns a boundingbox-string representation of the bounds object.
     * 
     * Parameters:
     * decimal - {Integer} How many significant digits in the bbox coords?
     *                     Default is 6
     * reverseAxisOrder - {Boolean} Should we reverse the axis order?
     * 
     * Returns:
     * {String} Simple String representation of bounds object.
     *          (e.g. "5,42,10,45")
     */
    toBBOX:function(decimal, reverseAxisOrder) {
        if (decimal== null) {
            decimal = 6; 
        }
        var mult = Math.pow(10, decimal);
        var xmin = Math.round(this.left * mult) / mult;
        var ymin = Math.round(this.bottom * mult) / mult;
        var xmax = Math.round(this.right * mult) / mult;
        var ymax = Math.round(this.top * mult) / mult;
        if (reverseAxisOrder === true) {
            return ymin + "," + xmin + "," + ymax + "," + xmax;
        } else {
            return xmin + "," + ymin + "," + xmax + "," + ymax;
        }
    },
 
    /**
     * APIMethod: toGeometry
     * Create a new polygon geometry based on this bounds.
     *
     * Returns:
     * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
     *     of this bounds.
     */
    toGeometry: function() {
        return new OpenLayers.Geometry.Polygon([
            new OpenLayers.Geometry.LinearRing([
                new OpenLayers.Geometry.Point(this.left, this.bottom),
                new OpenLayers.Geometry.Point(this.right, this.bottom),
                new OpenLayers.Geometry.Point(this.right, this.top),
                new OpenLayers.Geometry.Point(this.left, this.top)
            ])
        ]);
    },
    
    /**
     * APIMethod: getWidth
     * Returns the width of the bounds.
     * 
     * Returns:
     * {Float} The width of the bounds (right minus left).
     */
    getWidth:function() {
        return (this.right - this.left);
    },

    /**
     * APIMethod: getHeight
     * Returns the height of the bounds.
     * 
     * Returns:
     * {Float} The height of the bounds (top minus bottom).
     */
    getHeight:function() {
        return (this.top - this.bottom);
    },

    /**
     * APIMethod: getSize
     * Returns an <OpenLayers.Size> object of the bounds.
     * 
     * Returns:
     * {<OpenLayers.Size>} The size of the bounds.
     */
    getSize:function() {
        return new OpenLayers.Size(this.getWidth(), this.getHeight());
    },

    /**
     * APIMethod: getCenterPixel
     * Returns the <OpenLayers.Pixel> object which represents the center of the
     *     bounds.
     * 
     * Returns:
     * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
     */
    getCenterPixel:function() {
        return new OpenLayers.Pixel( (this.left + this.right) / 2,
                                     (this.bottom + this.top) / 2);
    },

    /**
     * APIMethod: getCenterLonLat
     * Returns the <OpenLayers.LonLat> object which represents the center of the
     *     bounds.
     *
     * Returns:
     * {<OpenLayers.LonLat>} The center of the bounds in map space.
     */
    getCenterLonLat:function() {
        if(!this.centerLonLat) {
            this.centerLonLat = new OpenLayers.LonLat(
                (this.left + this.right) / 2, (this.bottom + this.top) / 2
            );
        }
        return this.centerLonLat;
    },

    /**
     * APIMethod: scale
     * Scales the bounds around a pixel or lonlat. Note that the new 
     *     bounds may return non-integer properties, even if a pixel
     *     is passed. 
     * 
     * Parameters:
     * ratio - {Float} 
     * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
     *          Default is center.
     *
     * Returns:
     * {<OpenLayers.Bounds>} A new bounds that is scaled by ratio
     *                      from origin.
     */
    scale: function(ratio, origin){
        if(origin == null){
            origin = this.getCenterLonLat();
        }
        
        var origx,origy;

        // get origin coordinates
        if(origin.CLASS_NAME == "OpenLayers.LonLat"){
            origx = origin.lon;
            origy = origin.lat;
        } else {
            origx = origin.x;
            origy = origin.y;
        }

        var left = (this.left - origx) * ratio + origx;
        var bottom = (this.bottom - origy) * ratio + origy;
        var right = (this.right - origx) * ratio + origx;
        var top = (this.top - origy) * ratio + origy;
        
        return new OpenLayers.Bounds(left, bottom, right, top);
    },

    /**
     * APIMethod: add
     * Shifts the coordinates of the bound by the given horizontal and vertical
     *     deltas.
     *
     * (start code)
     * var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
     * bounds.toString();
     * // => "0,0,10,10"
     *
     * bounds.add(-1.5, 4).toString();
     * // => "-1.5,4,8.5,14"
     * (end)
     *
     * This method will throw a TypeError if it is passed null as an argument.
     *
     * Parameters:
     * x - {Float} horizontal delta
     * y - {Float} vertical delta
     *
     * Returns:
     * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
     *     this, but shifted by the passed-in x and y values.
     */
    add:function(x, y) {
        if ( (x == null) || (y == null) ) {
            throw new TypeError('Bounds.add cannot receive null values');
        }
        return new OpenLayers.Bounds(this.left + x, this.bottom + y,
                                     this.right + x, this.top + y);
    },
    
    /**
     * APIMethod: extend
     * Extend the bounds to include the <OpenLayers.LonLat>,
     *     <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
     *
     * Please note that this function assumes that left < right and
     *     bottom < top.
     *
     * Parameters:
     * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
     *     <OpenLayers.Bounds>} The object to be included in the new bounds
     *     object.
     */
    extend:function(object) {
        if (object) {
            switch(object.CLASS_NAME) {
                case "OpenLayers.LonLat":
                    this.extendXY(object.lon, object.lat);
                    break;
                case "OpenLayers.Geometry.Point":
                    this.extendXY(object.x, object.y);
                    break;

                case "OpenLayers.Bounds":
                    // clear cached center location
                    this.centerLonLat = null;

                    if ( (this.left == null) || (object.left < this.left)) {
                        this.left = object.left;
                    }
                    if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
                        this.bottom = object.bottom;
                    }
                    if ( (this.right == null) || (object.right > this.right) ) {
                        this.right = object.right;
                    }
                    if ( (this.top == null) || (object.top > this.top) ) {
                        this.top = object.top;
                    }
                    break;
            }
        }
    },

    /**
     * APIMethod: extendXY
     * Extend the bounds to include the XY coordinate specified.
     *
     * Parameters:
     * x - {number} The X part of the the coordinate.
     * y - {number} The Y part of the the coordinate.
     */
    extendXY:function(x, y) {
        // clear cached center location
        this.centerLonLat = null;

        if ((this.left == null) || (x < this.left)) {
            this.left = x;
        }
        if ((this.bottom == null) || (y < this.bottom)) {
            this.bottom = y;
        }
        if ((this.right == null) || (x > this.right)) {
            this.right = x;
        }
        if ((this.top == null) || (y > this.top)) {
            this.top = y;
        }
    },

    /**
     * APIMethod: containsLonLat
     * Returns whether the bounds object contains the given <OpenLayers.LonLat>.
     * 
     * Parameters:
     * ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
     *     object with a 'lon' and 'lat' properties.
     * options - {Object} Optional parameters
     *
     * Acceptable options:
     * inclusive - {Boolean} Whether or not to include the border.
     *     Default is true.
     * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
     *     ll will be considered as contained if it exceeds the world bounds,
     *     but can be wrapped around the dateline so it is contained by this
     *     bounds.
     *
     * Returns:
     * {Boolean} The passed-in lonlat is within this bounds.
     */
    containsLonLat: function(ll, options) {
        if (typeof options === "boolean") {
            options =  {inclusive: options};
        }
        options = options || {};
        var contains = this.contains(ll.lon, ll.lat, options.inclusive),
            worldBounds = options.worldBounds;
        if (worldBounds && !contains) {
            var worldWidth = worldBounds.getWidth();
            var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
            var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
            contains = this.containsLonLat({
                lon: ll.lon - worldsAway * worldWidth,
                lat: ll.lat
            }, {inclusive: options.inclusive});
        }
        return contains;
    },

    /**
     * APIMethod: containsPixel
     * Returns whether the bounds object contains the given <OpenLayers.Pixel>.
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     * inclusive - {Boolean} Whether or not to include the border. Default is
     *     true.
     *
     * Returns:
     * {Boolean} The passed-in pixel is within this bounds.
     */
    containsPixel:function(px, inclusive) {
        return this.contains(px.x, px.y, inclusive);
    },
    
    /**
     * APIMethod: contains
     * Returns whether the bounds object contains the given x and y.
     * 
     * Parameters:
     * x - {Float}
     * y - {Float}
     * inclusive - {Boolean} Whether or not to include the border. Default is
     *     true.
     *
     * Returns:
     * {Boolean} Whether or not the passed-in coordinates are within this
     *     bounds.
     */
    contains:function(x, y, inclusive) {
        //set default
        if (inclusive == null) {
            inclusive = true;
        }

        if (x == null || y == null) {
            return false;
        }

        x = OpenLayers.Util.toFloat(x);
        y = OpenLayers.Util.toFloat(y);

        var contains = false;
        if (inclusive) {
            contains = ((x >= this.left) && (x <= this.right) && 
                        (y >= this.bottom) && (y <= this.top));
        } else {
            contains = ((x > this.left) && (x < this.right) && 
                        (y > this.bottom) && (y < this.top));
        }              
        return contains;
    },

    /**
     * APIMethod: intersectsBounds
     * Determine whether the target bounds intersects this bounds.  Bounds are
     *     considered intersecting if any of their edges intersect or if one
     *     bounds contains the other.
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>} The target bounds.
     * options - {Object} Optional parameters.
     * 
     * Acceptable options:
     * inclusive - {Boolean} Treat coincident borders as intersecting.  Default
     *     is true.  If false, bounds that do not overlap but only touch at the
     *     border will not be considered as intersecting.
     * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
     *     bounds will be considered as intersecting if they intersect when 
     *     shifted to within the world bounds.  This applies only to bounds that
     *     cross or are completely outside the world bounds.
     *
     * Returns:
     * {Boolean} The passed-in bounds object intersects this bounds.
     */
    intersectsBounds:function(bounds, options) {
        if (typeof options === "boolean") {
            options =  {inclusive: options};
        }
        options = options || {};
        if (options.worldBounds) {
            var self = this.wrapDateLine(options.worldBounds);
            bounds = bounds.wrapDateLine(options.worldBounds);
        } else {
            self = this;
        }
        if (options.inclusive == null) {
            options.inclusive = true;
        }
        var intersects = false;
        var mightTouch = (
            self.left == bounds.right ||
            self.right == bounds.left ||
            self.top == bounds.bottom ||
            self.bottom == bounds.top
        );
        
        // if the two bounds only touch at an edge, and inclusive is false,
        // then the bounds don't *really* intersect.
        if (options.inclusive || !mightTouch) {
            // otherwise, if one of the boundaries even partially contains another,
            // inclusive of the edges, then they do intersect.
            var inBottom = (
                ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
                ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
            );
            var inTop = (
                ((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
                ((self.top > bounds.bottom) && (self.top < bounds.top))
            );
            var inLeft = (
                ((bounds.left >= self.left) && (bounds.left <= self.right)) ||
                ((self.left >= bounds.left) && (self.left <= bounds.right))
            );
            var inRight = (
                ((bounds.right >= self.left) && (bounds.right <= self.right)) ||
                ((self.right >= bounds.left) && (self.right <= bounds.right))
            );
            intersects = ((inBottom || inTop) && (inLeft || inRight));
        }
        // document me
        if (options.worldBounds && !intersects) {
            var world = options.worldBounds;
            var width = world.getWidth();
            var selfCrosses = !world.containsBounds(self);
            var boundsCrosses = !world.containsBounds(bounds);
            if (selfCrosses && !boundsCrosses) {
                bounds = bounds.add(-width, 0);
                intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
            } else if (boundsCrosses && !selfCrosses) {
                self = self.add(-width, 0);
                intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});                
            }
        }
        return intersects;
    },
    
    /**
     * APIMethod: containsBounds
     * Returns whether the bounds object contains the given <OpenLayers.Bounds>.
     * 
     * bounds - {<OpenLayers.Bounds>} The target bounds.
     * partial - {Boolean} If any of the target corners is within this bounds
     *     consider the bounds contained.  Default is false.  If false, the
     *     entire target bounds must be contained within this bounds.
     * inclusive - {Boolean} Treat shared edges as contained.  Default is
     *     true.
     *
     * Returns:
     * {Boolean} The passed-in bounds object is contained within this bounds. 
     */
    containsBounds:function(bounds, partial, inclusive) {
        if (partial == null) {
            partial = false;
        }
        if (inclusive == null) {
            inclusive = true;
        }
        var bottomLeft  = this.contains(bounds.left, bounds.bottom, inclusive);
        var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
        var topLeft  = this.contains(bounds.left, bounds.top, inclusive);
        var topRight = this.contains(bounds.right, bounds.top, inclusive);
        
        return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
                         : (bottomLeft && bottomRight && topLeft && topRight);
    },

    /** 
     * APIMethod: determineQuadrant
     * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
     *     <OpenLayers.LonLat> lies.
     *
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>}
     *
     * Returns:
     * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
     *     coordinate lies.
     */
    determineQuadrant: function(lonlat) {
    
        var quadrant = "";
        var center = this.getCenterLonLat();
        
        quadrant += (lonlat.lat < center.lat) ? "b" : "t";
        quadrant += (lonlat.lon < center.lon) ? "l" : "r";
    
        return quadrant; 
    },
    
    /**
     * APIMethod: transform
     * Transform the Bounds object from source to dest. 
     *
     * Parameters: 
     * source - {<OpenLayers.Projection>} Source projection. 
     * dest   - {<OpenLayers.Projection>} Destination projection. 
     *
     * Returns:
     * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
     */
    transform: function(source, dest) {
        // clear cached center location
        this.centerLonLat = null;
        var ll = OpenLayers.Projection.transform(
            {'x': this.left, 'y': this.bottom}, source, dest);
        var lr = OpenLayers.Projection.transform(
            {'x': this.right, 'y': this.bottom}, source, dest);
        var ul = OpenLayers.Projection.transform(
            {'x': this.left, 'y': this.top}, source, dest);
        var ur = OpenLayers.Projection.transform(
            {'x': this.right, 'y': this.top}, source, dest);
        this.left   = Math.min(ll.x, ul.x);
        this.bottom = Math.min(ll.y, lr.y);
        this.right  = Math.max(lr.x, ur.x);
        this.top    = Math.max(ul.y, ur.y);
        return this;
    },

    /**
     * APIMethod: wrapDateLine
     * Wraps the bounds object around the dateline.
     *  
     * Parameters:
     * maxExtent - {<OpenLayers.Bounds>}
     * options - {Object} Some possible options are:
     *
     * Allowed Options:
     *                    leftTolerance - {float} Allow for a margin of error 
     *                                            with the 'left' value of this 
     *                                            bound.
     *                                            Default is 0.
     *                    rightTolerance - {float} Allow for a margin of error 
     *                                             with the 'right' value of 
     *                                             this bound.
     *                                             Default is 0.
     * 
     * Returns:
     * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the 
     *                       "dateline" (as specified by the borders of 
     *                       maxExtent). Note that this function only returns 
     *                       a different bounds value if this bounds is 
     *                       *entirely* outside of the maxExtent. If this 
     *                       bounds straddles the dateline (is part in/part 
     *                       out of maxExtent), the returned bounds will always 
     *                       cross the left edge of the given maxExtent.
     *.
     */
    wrapDateLine: function(maxExtent, options) {    
        options = options || {};
        
        var leftTolerance = options.leftTolerance || 0;
        var rightTolerance = options.rightTolerance || 0;

        var newBounds = this.clone();
    
        if (maxExtent) {
            var width = maxExtent.getWidth();

            //shift right?
            while (newBounds.left < maxExtent.left && 
                   newBounds.right - rightTolerance <= maxExtent.left ) { 
                newBounds = newBounds.add(width, 0);
            }

            //shift left?
            while (newBounds.left + leftTolerance >= maxExtent.right && 
                   newBounds.right > maxExtent.right ) { 
                newBounds = newBounds.add(-width, 0);
            }
           
            // crosses right only? force left
            var newLeft = newBounds.left + leftTolerance;
            if (newLeft < maxExtent.right && newLeft > maxExtent.left && 
                   newBounds.right - rightTolerance > maxExtent.right) {
                newBounds = newBounds.add(-width, 0);
            }
        }
                
        return newBounds;
    },

    CLASS_NAME: "OpenLayers.Bounds"
});

/** 
 * APIFunction: fromString
 * Alternative constructor that builds a new OpenLayers.Bounds from a 
 *     parameter string.
 *
 * (begin code)
 * OpenLayers.Bounds.fromString("5,42,10,45");
 * // => equivalent to ...
 * new OpenLayers.Bounds(5, 42, 10, 45);
 * (end)
 *
 * Parameters: 
 * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
 * reverseAxisOrder - {Boolean} Does the string use reverse axis order?
 *
 * Returns:
 * {<OpenLayers.Bounds>} New bounds object built from the 
 *                       passed-in String.
 */
OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
    var bounds = str.split(",");
    return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
};

/** 
 * APIFunction: fromArray
 * Alternative constructor that builds a new OpenLayers.Bounds from an array.
 *
 * (begin code)
 * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
 * // => equivalent to ...
 * new OpenLayers.Bounds(5, 42, 10, 45);
 * (end)
 *
 * Parameters:
 * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
 * reverseAxisOrder - {Boolean} Does the array use reverse axis order?
 *
 * Returns:
 * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
 */
OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
    return reverseAxisOrder === true ?
           new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
           new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
};

/** 
 * APIFunction: fromSize
 * Alternative constructor that builds a new OpenLayers.Bounds from a size.
 *
 * (begin code)
 * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
 * // => equivalent to ...
 * new OpenLayers.Bounds(0, 20, 10, 0);
 * (end)
 *
 * Parameters:
 * size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
 *     both 'w' and 'h' properties.
 *
 * Returns:
 * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
 */
OpenLayers.Bounds.fromSize = function(size) {
    return new OpenLayers.Bounds(0,
                                 size.h,
                                 size.w,
                                 0);
};

/**
 * Function: oppositeQuadrant
 * Get the opposite quadrant for a given quadrant string.
 *
 * (begin code)
 * OpenLayers.Bounds.oppositeQuadrant( "tl" );
 * // => "br"
 *
 * OpenLayers.Bounds.oppositeQuadrant( "tr" );
 * // => "bl"
 * (end)
 *
 * Parameters:
 * quadrant - {String} two character quadrant shortstring
 *
 * Returns:
 * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if 
 *          you pass in "bl" it returns "tr", if you pass in "br" it 
 *          returns "tl", etc.
 */
OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
    var opp = "";
    
    opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
    opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
    
    return opp;
};
/* ======================================================================
    OpenLayers/BaseTypes/Element.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/BaseTypes.js
 */

/**
 * Namespace: OpenLayers.Element
 */
OpenLayers.Element = {

    /**
     * APIFunction: visible
     * 
     * Parameters: 
     * element - {DOMElement}
     * 
     * Returns:
     * {Boolean} Is the element visible?
     */
    visible: function(element) {
        return OpenLayers.Util.getElement(element).style.display != 'none';
    },

    /**
     * APIFunction: toggle
     * Toggle the visibility of element(s) passed in
     * 
     * Parameters:
     * element - {DOMElement} Actually user can pass any number of elements
     */
    toggle: function() {
        for (var i=0, len=arguments.length; i<len; i++) {
            var element = OpenLayers.Util.getElement(arguments[i]);
            var display = OpenLayers.Element.visible(element) ? 'none' 
                                                              : '';
            element.style.display = display;
        }
    },

    /**
     * APIFunction: remove
     * Remove the specified element from the DOM.
     * 
     * Parameters:
     * element - {DOMElement}
     */
    remove: function(element) {
        element = OpenLayers.Util.getElement(element);
        element.parentNode.removeChild(element);
    },

    /**
     * APIFunction: getHeight
     *  
     * Parameters:
     * element - {DOMElement}
     * 
     * Returns:
     * {Integer} The offset height of the element passed in
     */
    getHeight: function(element) {
        element = OpenLayers.Util.getElement(element);
        return element.offsetHeight;
    },

    /**
     * Function: hasClass
     * Tests if an element has the given CSS class name.
     *
     * Parameters:
     * element - {DOMElement} A DOM element node.
     * name - {String} The CSS class name to search for.
     *
     * Returns:
     * {Boolean} The element has the given class name.
     */
    hasClass: function(element, name) {
        var names = element.className;
        return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
    },
    
    /**
     * Function: addClass
     * Add a CSS class name to an element.  Safe where element already has
     *     the class name.
     *
     * Parameters:
     * element - {DOMElement} A DOM element node.
     * name - {String} The CSS class name to add.
     *
     * Returns:
     * {DOMElement} The element.
     */
    addClass: function(element, name) {
        if(!OpenLayers.Element.hasClass(element, name)) {
            element.className += (element.className ? " " : "") + name;
        }
        return element;
    },

    /**
     * Function: removeClass
     * Remove a CSS class name from an element.  Safe where element does not
     *     have the class name.
     *
     * Parameters:
     * element - {DOMElement} A DOM element node.
     * name - {String} The CSS class name to remove.
     *
     * Returns:
     * {DOMElement} The element.
     */
    removeClass: function(element, name) {
        var names = element.className;
        if(names) {
            element.className = OpenLayers.String.trim(
                names.replace(
                    new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
                )
            );
        }
        return element;
    },

    /**
     * Function: toggleClass
     * Remove a CSS class name from an element if it exists.  Add the class name
     *     if it doesn't exist.
     *
     * Parameters:
     * element - {DOMElement} A DOM element node.
     * name - {String} The CSS class name to toggle.
     *
     * Returns:
     * {DOMElement} The element.
     */
    toggleClass: function(element, name) {
        if(OpenLayers.Element.hasClass(element, name)) {
            OpenLayers.Element.removeClass(element, name);
        } else {
            OpenLayers.Element.addClass(element, name);
        }
        return element;
    },

    /**
     * APIFunction: getStyle
     * 
     * Parameters:
     * element - {DOMElement}
     * style - {?}
     * 
     * Returns:
     * {?}
     */
    getStyle: function(element, style) {
        element = OpenLayers.Util.getElement(element);

        var value = null;
        if (element && element.style) {
            value = element.style[OpenLayers.String.camelize(style)];
            if (!value) {
                if (document.defaultView && 
                    document.defaultView.getComputedStyle) {
                    
                    var css = document.defaultView.getComputedStyle(element, null);
                    value = css ? css.getPropertyValue(style) : null;
                } else if (element.currentStyle) {
                    value = element.currentStyle[OpenLayers.String.camelize(style)];
                }
            }
        
            var positions = ['left', 'top', 'right', 'bottom'];
            if (window.opera &&
                (OpenLayers.Util.indexOf(positions,style) != -1) &&
                (OpenLayers.Element.getStyle(element, 'position') == 'static')) { 
                value = 'auto';
            }
        }
    
        return value == 'auto' ? null : value;
    }

};
/* ======================================================================
    OpenLayers/BaseTypes/LonLat.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.LonLat
 * This class represents a longitude and latitude pair
 */
OpenLayers.LonLat = OpenLayers.Class({

    /** 
     * APIProperty: lon
     * {Float} The x-axis coodinate in map units
     */
    lon: 0.0,
    
    /** 
     * APIProperty: lat
     * {Float} The y-axis coordinate in map units
     */
    lat: 0.0,

    /**
     * Constructor: OpenLayers.LonLat
     * Create a new map location. Coordinates can be passed either as two
     * arguments, or as a single argument.
     *
     * Parameters (two arguments):
     * lon - {Number} The x-axis coordinate in map units.  If your map is in
     *     a geographic projection, this will be the Longitude.  Otherwise,
     *     it will be the x coordinate of the map location in your map units.
     * lat - {Number} The y-axis coordinate in map units.  If your map is in
     *     a geographic projection, this will be the Latitude.  Otherwise,
     *     it will be the y coordinate of the map location in your map units.
     *
     * Parameters (single argument):
     * location - {Array(Float)} [lon, lat]
     */
    initialize: function(lon, lat) {
        if (OpenLayers.Util.isArray(lon)) {
            lat = lon[1];
            lon = lon[0];
        }
        this.lon = OpenLayers.Util.toFloat(lon);
        this.lat = OpenLayers.Util.toFloat(lat);
    },
    
    /**
     * Method: toString
     * Return a readable string version of the lonlat
     *
     * Returns:
     * {String} String representation of OpenLayers.LonLat object. 
     *           (e.g. <i>"lon=5,lat=42"</i>)
     */
    toString:function() {
        return ("lon=" + this.lon + ",lat=" + this.lat);
    },

    /** 
     * APIMethod: toShortString
     * 
     * Returns:
     * {String} Shortened String representation of OpenLayers.LonLat object. 
     *         (e.g. <i>"5, 42"</i>)
     */
    toShortString:function() {
        return (this.lon + ", " + this.lat);
    },

    /** 
     * APIMethod: clone
     * 
     * Returns:
     * {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon 
     *                       and lat values
     */
    clone:function() {
        return new OpenLayers.LonLat(this.lon, this.lat);
    },

    /** 
     * APIMethod: add
     * 
     * Parameters:
     * lon - {Float}
     * lat - {Float}
     * 
     * Returns:
     * {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and 
     *                       lat passed-in added to this's. 
     */
    add:function(lon, lat) {
        if ( (lon == null) || (lat == null) ) {
            throw new TypeError('LonLat.add cannot receive null values');
        }
        return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon), 
                                     this.lat + OpenLayers.Util.toFloat(lat));
    },

    /** 
     * APIMethod: equals
     * 
     * Parameters:
     * ll - {<OpenLayers.LonLat>}
     * 
     * Returns:
     * {Boolean} Boolean value indicating whether the passed-in 
     *           <OpenLayers.LonLat> object has the same lon and lat 
     *           components as this.
     *           Note: if ll passed in is null, returns false
     */
    equals:function(ll) {
        var equals = false;
        if (ll != null) {
            equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
                      (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
        }
        return equals;
    },

    /**
     * APIMethod: transform
     * Transform the LonLat object from source to dest. This transformation is
     *    *in place*: if you want a *new* lonlat, use .clone() first.
     *
     * Parameters: 
     * source - {<OpenLayers.Projection>} Source projection. 
     * dest   - {<OpenLayers.Projection>} Destination projection. 
     *
     * Returns:
     * {<OpenLayers.LonLat>} Itself, for use in chaining operations.
     */
    transform: function(source, dest) {
        var point = OpenLayers.Projection.transform(
            {'x': this.lon, 'y': this.lat}, source, dest);
        this.lon = point.x;
        this.lat = point.y;
        return this;
    },
    
    /**
     * APIMethod: wrapDateLine
     * 
     * Parameters:
     * maxExtent - {<OpenLayers.Bounds>}
     * 
     * Returns:
     * {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the 
     *                       "dateline" (as specified by the borders of 
     *                       maxExtent)
     */
    wrapDateLine: function(maxExtent) {    

        var newLonLat = this.clone();
    
        if (maxExtent) {
            //shift right?
            while (newLonLat.lon < maxExtent.left) {
                newLonLat.lon +=  maxExtent.getWidth();
            }    
           
            //shift left?
            while (newLonLat.lon > maxExtent.right) {
                newLonLat.lon -= maxExtent.getWidth();
            }    
        }
                
        return newLonLat;
    },

    CLASS_NAME: "OpenLayers.LonLat"
});

/** 
 * Function: fromString
 * Alternative constructor that builds a new <OpenLayers.LonLat> from a 
 *     parameter string
 * 
 * Parameters:
 * str - {String} Comma-separated Lon,Lat coordinate string. 
 *                 (e.g. <i>"5,40"</i>)
 * 
 * Returns:
 * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the 
 *                       passed-in String.
 */
OpenLayers.LonLat.fromString = function(str) {
    var pair = str.split(",");
    return new OpenLayers.LonLat(pair[0], pair[1]);
};

/** 
 * Function: fromArray
 * Alternative constructor that builds a new <OpenLayers.LonLat> from an 
 *     array of two numbers that represent lon- and lat-values.
 * 
 * Parameters:
 * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
 * 
 * Returns:
 * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the 
 *                       passed-in array.
 */
OpenLayers.LonLat.fromArray = function(arr) {
    var gotArr = OpenLayers.Util.isArray(arr),
        lon = gotArr && arr[0],
        lat = gotArr && arr[1];
    return new OpenLayers.LonLat(lon, lat);
};
/* ======================================================================
    OpenLayers/BaseTypes/Pixel.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.Pixel
 * This class represents a screen coordinate, in x and y coordinates
 */
OpenLayers.Pixel = OpenLayers.Class({
    
    /**
     * APIProperty: x
     * {Number} The x coordinate
     */
    x: 0.0,

    /**
     * APIProperty: y
     * {Number} The y coordinate
     */
    y: 0.0,
    
    /**
     * Constructor: OpenLayers.Pixel
     * Create a new OpenLayers.Pixel instance
     *
     * Parameters:
     * x - {Number} The x coordinate
     * y - {Number} The y coordinate
     *
     * Returns:
     * An instance of OpenLayers.Pixel
     */
    initialize: function(x, y) {
        this.x = parseFloat(x);
        this.y = parseFloat(y);
    },
    
    /**
     * Method: toString
     * Cast this object into a string
     *
     * Returns:
     * {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
     */
    toString:function() {
        return ("x=" + this.x + ",y=" + this.y);
    },

    /**
     * APIMethod: clone
     * Return a clone of this pixel object
     *
     * Returns:
     * {<OpenLayers.Pixel>} A clone pixel
     */
    clone:function() {
        return new OpenLayers.Pixel(this.x, this.y); 
    },
    
    /**
     * APIMethod: equals
     * Determine whether one pixel is equivalent to another
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
     *                                  a 'x' and 'y' properties.
     *
     * Returns:
     * {Boolean} The point passed in as parameter is equal to this. Note that
     * if px passed in is null, returns false.
     */
    equals:function(px) {
        var equals = false;
        if (px != null) {
            equals = ((this.x == px.x && this.y == px.y) ||
                      (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
        }
        return equals;
    },

    /**
     * APIMethod: distanceTo
     * Returns the distance to the pixel point passed in as a parameter.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     *
     * Returns:
     * {Float} The pixel point passed in as parameter to calculate the
     *     distance to.
     */
    distanceTo:function(px) {
        return Math.sqrt(
            Math.pow(this.x - px.x, 2) +
            Math.pow(this.y - px.y, 2)
        );
    },

    /**
     * APIMethod: add
     *
     * Parameters:
     * x - {Integer}
     * y - {Integer}
     *
     * Returns:
     * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the 
     * values passed in.
     */
    add:function(x, y) {
        if ( (x == null) || (y == null) ) {
            throw new TypeError('Pixel.add cannot receive null values');
        }
        return new OpenLayers.Pixel(this.x + x, this.y + y);
    },

    /**
    * APIMethod: offset
    * 
    * Parameters
    * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
    *                                  a 'x' and 'y' properties.
    * 
    * Returns:
    * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the 
    *                      x&y values of the pixel passed in.
    */
    offset:function(px) {
        var newPx = this.clone();
        if (px) {
            newPx = this.add(px.x, px.y);
        }
        return newPx;
    },

    CLASS_NAME: "OpenLayers.Pixel"
});
/* ======================================================================
    OpenLayers/BaseTypes/Size.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.Size
 * Instances of this class represent a width/height pair
 */
OpenLayers.Size = OpenLayers.Class({

    /**
     * APIProperty: w
     * {Number} width
     */
    w: 0.0,
    
    /**
     * APIProperty: h
     * {Number} height
     */
    h: 0.0,


    /**
     * Constructor: OpenLayers.Size
     * Create an instance of OpenLayers.Size
     *
     * Parameters:
     * w - {Number} width
     * h - {Number} height
     */
    initialize: function(w, h) {
        this.w = parseFloat(w);
        this.h = parseFloat(h);
    },

    /**
     * Method: toString
     * Return the string representation of a size object
     *
     * Returns:
     * {String} The string representation of OpenLayers.Size object. 
     * (e.g. <i>"w=55,h=66"</i>)
     */
    toString:function() {
        return ("w=" + this.w + ",h=" + this.h);
    },

    /**
     * APIMethod: clone
     * Create a clone of this size object
     *
     * Returns:
     * {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
     * values
     */
    clone:function() {
        return new OpenLayers.Size(this.w, this.h);
    },

    /**
     *
     * APIMethod: equals
     * Determine where this size is equal to another
     *
     * Parameters:
     * sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
     *                                  a 'w' and 'h' properties.
     *
     * Returns: 
     * {Boolean} The passed in size has the same h and w properties as this one.
     * Note that if sz passed in is null, returns false.
     */
    equals:function(sz) {
        var equals = false;
        if (sz != null) {
            equals = ((this.w == sz.w && this.h == sz.h) ||
                      (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
        }
        return equals;
    },

    CLASS_NAME: "OpenLayers.Size"
});
/* ======================================================================
    OpenLayers/Console.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Namespace: OpenLayers.Console
 * The OpenLayers.Console namespace is used for debugging and error logging.
 * If the Firebug Lite (../Firebug/firebug.js) is included before this script,
 * calls to OpenLayers.Console methods will get redirected to window.console.
 * This makes use of the Firebug extension where available and allows for
 * cross-browser debugging Firebug style.
 *
 * Note:
 * Note that behavior will differ with the Firebug extention and Firebug Lite.
 * Most notably, the Firebug Lite console does not currently allow for
 * hyperlinks to code or for clicking on object to explore their properties.
 * 
 */
OpenLayers.Console = {
    /**
     * Create empty functions for all console methods.  The real value of these
     * properties will be set if Firebug Lite (../Firebug/firebug.js script) is
     * included.  We explicitly require the Firebug Lite script to trigger
     * functionality of the OpenLayers.Console methods.
     */
    
    /**
     * APIFunction: log
     * Log an object in the console.  The Firebug Lite console logs string
     * representation of objects.  Given multiple arguments, they will
     * be cast to strings and logged with a space delimiter.  If the first
     * argument is a string with printf-like formatting, subsequent arguments
     * will be used in string substitution.  Any additional arguments (beyond
     * the number substituted in a format string) will be appended in a space-
     * delimited line.
     * 
     * Parameters:
     * object - {Object}
     */
    log: function() {},

    /**
     * APIFunction: debug
     * Writes a message to the console, including a hyperlink to the line
     * where it was called.
     *
     * May be called with multiple arguments as with OpenLayers.Console.log().
     * 
     * Parameters:
     * object - {Object}
     */
    debug: function() {},

    /**
     * APIFunction: info
     * Writes a message to the console with the visual "info" icon and color
     * coding and a hyperlink to the line where it was called.
     *
     * May be called with multiple arguments as with OpenLayers.Console.log().
     * 
     * Parameters:
     * object - {Object}
     */
    info: function() {},

    /**
     * APIFunction: warn
     * Writes a message to the console with the visual "warning" icon and
     * color coding and a hyperlink to the line where it was called.
     *
     * May be called with multiple arguments as with OpenLayers.Console.log().
     * 
     * Parameters:
     * object - {Object}
     */
    warn: function() {},

    /**
     * APIFunction: error
     * Writes a message to the console with the visual "error" icon and color
     * coding and a hyperlink to the line where it was called.
     *
     * May be called with multiple arguments as with OpenLayers.Console.log().
     * 
     * Parameters:
     * object - {Object}
     */
    error: function() {},
    
    /**
     * APIFunction: userError
     * A single interface for showing error messages to the user. The default
     * behavior is a Javascript alert, though this can be overridden by
     * reassigning OpenLayers.Console.userError to a different function.
     *
     * Expects a single error message
     * 
     * Parameters:
     * error - {Object}
     */
    userError: function(error) {
        alert(error);
    },

    /**
     * APIFunction: assert
     * Tests that an expression is true. If not, it will write a message to
     * the console and throw an exception.
     *
     * May be called with multiple arguments as with OpenLayers.Console.log().
     * 
     * Parameters:
     * object - {Object}
     */
    assert: function() {},

    /**
     * APIFunction: dir
     * Prints an interactive listing of all properties of the object. This
     * looks identical to the view that you would see in the DOM tab.
     * 
     * Parameters:
     * object - {Object}
     */
    dir: function() {},

    /**
     * APIFunction: dirxml
     * Prints the XML source tree of an HTML or XML element. This looks
     * identical to the view that you would see in the HTML tab. You can click
     * on any node to inspect it in the HTML tab.
     * 
     * Parameters:
     * object - {Object}
     */
    dirxml: function() {},

    /**
     * APIFunction: trace
     * Prints an interactive stack trace of JavaScript execution at the point
     * where it is called.  The stack trace details the functions on the stack,
     * as well as the values that were passed as arguments to each function.
     * You can click each function to take you to its source in the Script tab,
     * and click each argument value to inspect it in the DOM or HTML tabs.
     * 
     */
    trace: function() {},

    /**
     * APIFunction: group
     * Writes a message to the console and opens a nested block to indent all
     * future messages sent to the console. Call OpenLayers.Console.groupEnd()
     * to close the block.
     *
     * May be called with multiple arguments as with OpenLayers.Console.log().
     * 
     * Parameters:
     * object - {Object}
     */
    group: function() {},

    /**
     * APIFunction: groupEnd
     * Closes the most recently opened block created by a call to
     * OpenLayers.Console.group
     */
    groupEnd: function() {},
    
    /**
     * APIFunction: time
     * Creates a new timer under the given name. Call
     * OpenLayers.Console.timeEnd(name)
     * with the same name to stop the timer and print the time elapsed.
     *
     * Parameters:
     * name - {String}
     */
    time: function() {},

    /**
     * APIFunction: timeEnd
     * Stops a timer created by a call to OpenLayers.Console.time(name) and
     * writes the time elapsed.
     *
     * Parameters:
     * name - {String}
     */
    timeEnd: function() {},

    /**
     * APIFunction: profile
     * Turns on the JavaScript profiler. The optional argument title would
     * contain the text to be printed in the header of the profile report.
     *
     * This function is not currently implemented in Firebug Lite.
     * 
     * Parameters:
     * title - {String} Optional title for the profiler
     */
    profile: function() {},

    /**
     * APIFunction: profileEnd
     * Turns off the JavaScript profiler and prints its report.
     * 
     * This function is not currently implemented in Firebug Lite.
     */
    profileEnd: function() {},

    /**
     * APIFunction: count
     * Writes the number of times that the line of code where count was called
     * was executed. The optional argument title will print a message in
     * addition to the number of the count.
     *
     * This function is not currently implemented in Firebug Lite.
     *
     * Parameters:
     * title - {String} Optional title to be printed with count
     */
    count: function() {},

    CLASS_NAME: "OpenLayers.Console"
};

/**
 * Execute an anonymous function to extend the OpenLayers.Console namespace
 * if the firebug.js script is included.  This closure is used so that the
 * "scripts" and "i" variables don't pollute the global namespace.
 */
(function() {
    /**
     * If Firebug Lite is included (before this script), re-route all
     * OpenLayers.Console calls to the console object.
     */
    var scripts = document.getElementsByTagName("script");
    for(var i=0, len=scripts.length; i<len; ++i) {
        if(scripts[i].src.indexOf("firebug.js") != -1) {
            if(console) {
                OpenLayers.Util.extend(OpenLayers.Console, console);
                break;
            }
        }
    }
})();
/* ======================================================================
    OpenLayers/Lang.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes.js
 * @requires OpenLayers/Console.js
 */

/**
 * Namespace: OpenLayers.Lang
 * Internationalization namespace.  Contains dictionaries in various languages
 *     and methods to set and get the current language.
 */
OpenLayers.Lang = {
    
    /** 
     * Property: code
     * {String}  Current language code to use in OpenLayers.  Use the
     *     <setCode> method to set this value and the <getCode> method to
     *     retrieve it.
     */
    code: null,

    /** 
     * APIProperty: defaultCode
     * {String} Default language to use when a specific language can't be
     *     found.  Default is "en".
     */
    defaultCode: "en",
        
    /**
     * APIFunction: getCode
     * Get the current language code.
     *
     * Returns:
     * {String} The current language code.
     */
    getCode: function() {
        if(!OpenLayers.Lang.code) {
            OpenLayers.Lang.setCode();
        }
        return OpenLayers.Lang.code;
    },
    
    /**
     * APIFunction: setCode
     * Set the language code for string translation.  This code is used by
     *     the <OpenLayers.Lang.translate> method.
     *
     * Parameters:
     * code - {String} These codes follow the IETF recommendations at
     *     http://www.ietf.org/rfc/rfc3066.txt.  If no value is set, the
     *     browser's language setting will be tested.  If no <OpenLayers.Lang>
     *     dictionary exists for the code, the <OpenLayers.String.defaultLang>
     *     will be used.
     */
    setCode: function(code) {
        var lang;
        if(!code) {
            code = (OpenLayers.BROWSER_NAME == "msie") ?
                navigator.userLanguage : navigator.language;
        }
        var parts = code.split('-');
        parts[0] = parts[0].toLowerCase();
        if(typeof OpenLayers.Lang[parts[0]] == "object") {
            lang = parts[0];
        }

        // check for regional extensions
        if(parts[1]) {
            var testLang = parts[0] + '-' + parts[1].toUpperCase();
            if(typeof OpenLayers.Lang[testLang] == "object") {
                lang = testLang;
            }
        }
        if(!lang) {
            OpenLayers.Console.warn(
                'Failed to find OpenLayers.Lang.' + parts.join("-") +
                ' dictionary, falling back to default language'
            );
            lang = OpenLayers.Lang.defaultCode;
        }
        
        OpenLayers.Lang.code = lang;
    },

    /**
     * APIMethod: translate
     * Looks up a key from a dictionary based on the current language string.
     *     The value of <getCode> will be used to determine the appropriate
     *     dictionary.  Dictionaries are stored in <OpenLayers.Lang>.
     *
     * Parameters:
     * key - {String} The key for an i18n string value in the dictionary.
     * context - {Object} Optional context to be used with
     *     <OpenLayers.String.format>.
     * 
     * Returns:
     * {String} A internationalized string.
     */
    translate: function(key, context) {
        var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
        var message = dictionary && dictionary[key];
        if(!message) {
            // Message not found, fall back to message key
            message = key;
        }
        if(context) {
            message = OpenLayers.String.format(message, context);
        }
        return message;
    }
    
};


/**
 * APIMethod: OpenLayers.i18n
 * Alias for <OpenLayers.Lang.translate>.  Looks up a key from a dictionary
 *     based on the current language string. The value of
 *     <OpenLayers.Lang.getCode> will be used to determine the appropriate
 *     dictionary.  Dictionaries are stored in <OpenLayers.Lang>.
 *
 * Parameters:
 * key - {String} The key for an i18n string value in the dictionary.
 * context - {Object} Optional context to be used with
 *     <OpenLayers.String.format>.
 * 
 * Returns:
 * {String} A internationalized string.
 */
OpenLayers.i18n = OpenLayers.Lang.translate;
/* ======================================================================
    OpenLayers/Util.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes.js
 * @requires OpenLayers/BaseTypes/Bounds.js
 * @requires OpenLayers/BaseTypes/Element.js
 * @requires OpenLayers/BaseTypes/LonLat.js
 * @requires OpenLayers/BaseTypes/Pixel.js
 * @requires OpenLayers/BaseTypes/Size.js
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: Util
 */
OpenLayers.Util = OpenLayers.Util || {};

/** 
 * Function: getElement
 * This is the old $() from prototype
 *
 * Parameters:
 * e - {String or DOMElement or Window}
 *
 * Returns:
 * {Array(DOMElement) or DOMElement}
 */
OpenLayers.Util.getElement = function() {
    var elements = [];

    for (var i=0, len=arguments.length; i<len; i++) {
        var element = arguments[i];
        if (typeof element == 'string') {
            element = document.getElementById(element);
        }
        if (arguments.length == 1) {
            return element;
        }
        elements.push(element);
    }
    return elements;
};

/**
 * Function: isElement
 * A cross-browser implementation of "e instanceof Element".
 *
 * Parameters:
 * o - {Object} The object to test.
 *
 * Returns:
 * {Boolean}
 */
OpenLayers.Util.isElement = function(o) {
    return !!(o && o.nodeType === 1);
};

/**
 * Function: isArray
 * Tests that the provided object is an array.
 * This test handles the cross-IFRAME case not caught
 * by "a instanceof Array" and should be used instead.
 * 
 * Parameters:
 * a - {Object} the object test.
 * 
 * Returns:
 * {Boolean} true if the object is an array.
 */
OpenLayers.Util.isArray = function(a) {
    return (Object.prototype.toString.call(a) === '[object Array]');
};

/** 
 * Function: removeItem
 * Remove an object from an array. Iterates through the array
 *     to find the item, then removes it.
 *
 * Parameters:
 * array - {Array}
 * item - {Object}
 * 
 * Returns:
 * {Array} A reference to the array
 */
OpenLayers.Util.removeItem = function(array, item) {
    for(var i = array.length - 1; i >= 0; i--) {
        if(array[i] == item) {
            array.splice(i,1);
            //break;more than once??
        }
    }
    return array;
};

/** 
 * Function: indexOf
 * Seems to exist already in FF, but not in MOZ.
 * 
 * Parameters:
 * array - {Array}
 * obj - {*}
 * 
 * Returns:
 * {Integer} The index at which the first object was found in the array.
 *           If not found, returns -1.
 */
OpenLayers.Util.indexOf = function(array, obj) {
    // use the build-in function if available.
    if (typeof array.indexOf == "function") {
        return array.indexOf(obj);
    } else {
        for (var i = 0, len = array.length; i < len; i++) {
            if (array[i] == obj) {
                return i;
            }
        }
        return -1;   
    }
};


/**
 * Property: dotless
 * {RegExp}
 * Compiled regular expression to match dots (".").  This is used for replacing
 *     dots in identifiers.  Because object identifiers are frequently used for
 *     DOM element identifiers by the library, we avoid using dots to make for
 *     more sensible CSS selectors.
 *
 * TODO: Use a module pattern to avoid bloating the API with stuff like this.
 */
OpenLayers.Util.dotless = /\./g;

/**
 * Function: modifyDOMElement
 * 
 * Modifies many properties of a DOM element all at once.  Passing in 
 * null to an individual parameter will avoid setting the attribute.
 *
 * Parameters:
 * element - {DOMElement} DOM element to modify.
 * id - {String} The element id attribute to set.  Note that dots (".") will be
 *     replaced with underscore ("_") in setting the element id.
 * px - {<OpenLayers.Pixel>|Object} The element left and top position,
 *                                  OpenLayers.Pixel or an object with
 *                                  a 'x' and 'y' properties.
 * sz - {<OpenLayers.Size>|Object} The element width and height,
 *                                 OpenLayers.Size or an object with a
 *                                 'w' and 'h' properties.
 * position - {String}       The position attribute.  eg: absolute, 
 *                           relative, etc.
 * border - {String}         The style.border attribute.  eg:
 *                           solid black 2px
 * overflow - {String}       The style.overview attribute.  
 * opacity - {Float}         Fractional value (0.0 - 1.0)
 */
OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position, 
                                            border, overflow, opacity) {

    if (id) {
        element.id = id.replace(OpenLayers.Util.dotless, "_");
    }
    if (px) {
        element.style.left = px.x + "px";
        element.style.top = px.y + "px";
    }
    if (sz) {
        element.style.width = sz.w + "px";
        element.style.height = sz.h + "px";
    }
    if (position) {
        element.style.position = position;
    }
    if (border) {
        element.style.border = border;
    }
    if (overflow) {
        element.style.overflow = overflow;
    }
    if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
        element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
        element.style.opacity = opacity;
    } else if (parseFloat(opacity) == 1.0) {
        element.style.filter = '';
        element.style.opacity = '';
    }
};

/** 
 * Function: createDiv
 * Creates a new div and optionally set some standard attributes.
 * Null may be passed to each parameter if you do not wish to
 * set a particular attribute.
 * Note - zIndex is NOT set on the resulting div.
 * 
 * Parameters:
 * id - {String} An identifier for this element.  If no id is
 *               passed an identifier will be created 
 *               automatically.  Note that dots (".") will be replaced with
 *               underscore ("_") when generating ids.
 * px - {<OpenLayers.Pixel>|Object} The element left and top position,
 *                                  OpenLayers.Pixel or an object with
 *                                  a 'x' and 'y' properties.
 * sz - {<OpenLayers.Size>|Object} The element width and height,
 *                                 OpenLayers.Size or an object with a
 *                                 'w' and 'h' properties.
 * imgURL - {String} A url pointing to an image to use as a 
 *                   background image.
 * position - {String} The style.position value. eg: absolute,
 *                     relative etc.
 * border - {String} The the style.border value. 
 *                   eg: 2px solid black
 * overflow - {String} The style.overflow value. Eg. hidden
 * opacity - {Float} Fractional value (0.0 - 1.0)
 * 
 * Returns: 
 * {DOMElement} A DOM Div created with the specified attributes.
 */
OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position, 
                                     border, overflow, opacity) {

    var dom = document.createElement('div');

    if (imgURL) {
        dom.style.backgroundImage = 'url(' + imgURL + ')';
    }

    //set generic properties
    if (!id) {
        id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
    }
    if (!position) {
        position = "absolute";
    }
    OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position, 
                                     border, overflow, opacity);

    return dom;
};

/**
 * Function: createImage
 * Creates an img element with specific attribute values.
 *  
 * Parameters:
 * id - {String} The id field for the img.  If none assigned one will be
 *               automatically generated.
 * px - {<OpenLayers.Pixel>|Object} The element left and top position,
 *                                  OpenLayers.Pixel or an object with
 *                                  a 'x' and 'y' properties.
 * sz - {<OpenLayers.Size>|Object} The element width and height,
 *                                 OpenLayers.Size or an object with a
 *                                 'w' and 'h' properties.
 * imgURL - {String} The url to use as the image source.
 * position - {String} The style.position value.
 * border - {String} The border to place around the image.
 * opacity - {Float} Fractional value (0.0 - 1.0)
 * delayDisplay - {Boolean} If true waits until the image has been
 *                          loaded.
 * 
 * Returns:
 * {DOMElement} A DOM Image created with the specified attributes.
 */
OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
                                       opacity, delayDisplay) {

    var image = document.createElement("img");

    //set generic properties
    if (!id) {
        id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
    }
    if (!position) {
        position = "relative";
    }
    OpenLayers.Util.modifyDOMElement(image, id, px, sz, position, 
                                     border, null, opacity);

    if (delayDisplay) {
        image.style.display = "none";
        function display() {
            image.style.display = "";
            OpenLayers.Event.stopObservingElement(image);
        }
        OpenLayers.Event.observe(image, "load", display);
        OpenLayers.Event.observe(image, "error", display);
    }
    
    //set special properties
    image.style.alt = id;
    image.galleryImg = "no";
    if (imgURL) {
        image.src = imgURL;
    }
        
    return image;
};

/**
 * Property: IMAGE_RELOAD_ATTEMPTS
 * {Integer} How many times should we try to reload an image before giving up?
 *           Default is 0
 */
OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;

/**
 * Property: alphaHackNeeded
 * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
 */
OpenLayers.Util.alphaHackNeeded = null;

/**
 * Function: alphaHack
 * Checks whether it's necessary (and possible) to use the png alpha
 * hack which allows alpha transparency for png images under Internet
 * Explorer.
 * 
 * Returns:
 * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
 */
OpenLayers.Util.alphaHack = function() {
    if (OpenLayers.Util.alphaHackNeeded == null) {
        var arVersion = navigator.appVersion.split("MSIE");
        var version = parseFloat(arVersion[1]);
        var filter = false;
    
        // IEs4Lin dies when trying to access document.body.filters, because 
        // the property is there, but requires a DLL that can't be provided. This
        // means that we need to wrap this in a try/catch so that this can
        // continue.
    
        try { 
            filter = !!(document.body.filters);
        } catch (e) {}    
    
        OpenLayers.Util.alphaHackNeeded = (filter && 
                                           (version >= 5.5) && (version < 7));
    }
    return OpenLayers.Util.alphaHackNeeded;
};

/** 
 * Function: modifyAlphaImageDiv
 * 
 * Parameters:
 * div - {DOMElement} Div containing Alpha-adjusted Image
 * id - {String}
 * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
 *                                  a 'x' and 'y' properties.
 * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
 *                                 a 'w' and 'h' properties.
 * imgURL - {String}
 * position - {String}
 * border - {String}
 * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
 * opacity - {Float} Fractional value (0.0 - 1.0)
 */ 
OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL, 
                                               position, border, sizing, 
                                               opacity) {

    OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
                                     null, null, opacity);

    var img = div.childNodes[0];

    if (imgURL) {
        img.src = imgURL;
    }
    OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz, 
                                     "relative", border);
    
    if (OpenLayers.Util.alphaHack()) {
        if(div.style.display != "none") {
            div.style.display = "inline-block";
        }
        if (sizing == null) {
            sizing = "scale";
        }
        
        div.style.filter = "progid:DXImageTransform.Microsoft" +
                           ".AlphaImageLoader(src='" + img.src + "', " +
                           "sizingMethod='" + sizing + "')";
        if (parseFloat(div.style.opacity) >= 0.0 && 
            parseFloat(div.style.opacity) < 1.0) {
            div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
        }

        img.style.filter = "alpha(opacity=0)";
    }
};

/** 
 * Function: createAlphaImageDiv
 * 
 * Parameters:
 * id - {String}
 * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
 *                                  a 'x' and 'y' properties.
 * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
 *                                 a 'w' and 'h' properties.
 * imgURL - {String}
 * position - {String}
 * border - {String}
 * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
 * opacity - {Float} Fractional value (0.0 - 1.0)
 * delayDisplay - {Boolean} If true waits until the image has been
 *                          loaded.
 * 
 * Returns:
 * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is 
 *              needed for transparency in IE, it is added.
 */ 
OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL, 
                                               position, border, sizing, 
                                               opacity, delayDisplay) {
    
    var div = OpenLayers.Util.createDiv();
    var img = OpenLayers.Util.createImage(null, null, null, null, null, null, 
                                          null, delayDisplay);
    img.className = "olAlphaImg";
    div.appendChild(img);

    OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position, 
                                        border, sizing, opacity);
    
    return div;
};


/** 
 * Function: upperCaseObject
 * Creates a new hashtable and copies over all the keys from the 
 *     passed-in object, but storing them under an uppercased
 *     version of the key at which they were stored.
 * 
 * Parameters: 
 * object - {Object}
 * 
 * Returns: 
 * {Object} A new Object with all the same keys but uppercased
 */
OpenLayers.Util.upperCaseObject = function (object) {
    var uObject = {};
    for (var key in object) {
        uObject[key.toUpperCase()] = object[key];
    }
    return uObject;
};

/** 
 * Function: applyDefaults
 * Takes an object and copies any properties that don't exist from
 *     another properties, by analogy with OpenLayers.Util.extend() from
 *     Prototype.js.
 * 
 * Parameters:
 * to - {Object} The destination object.
 * from - {Object} The source object.  Any properties of this object that
 *     are undefined in the to object will be set on the to object.
 *
 * Returns:
 * {Object} A reference to the to object.  Note that the to argument is modified
 *     in place and returned by this function.
 */
OpenLayers.Util.applyDefaults = function (to, from) {
    to = to || {};
    /*
     * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
     * prototype object" when calling hawOwnProperty if the source object is an
     * instance of window.Event.
     */
    var fromIsEvt = typeof window.Event == "function"
                    && from instanceof window.Event;

    for (var key in from) {
        if (to[key] === undefined ||
            (!fromIsEvt && from.hasOwnProperty
             && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
            to[key] = from[key];
        }
    }
    /**
     * IE doesn't include the toString property when iterating over an object's
     * properties with the for(property in object) syntax.  Explicitly check if
     * the source has its own toString property.
     */
    if(!fromIsEvt && from && from.hasOwnProperty
       && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
        to.toString = from.toString;
    }
    
    return to;
};

/**
 * Function: getParameterString
 * 
 * Parameters:
 * params - {Object}
 * 
 * Returns:
 * {String} A concatenation of the properties of an object in 
 *          http parameter notation. 
 *          (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
 *          If a parameter is actually a list, that parameter will then
 *          be set to a comma-seperated list of values (foo,bar) instead
 *          of being URL escaped (foo%3Abar). 
 */
OpenLayers.Util.getParameterString = function(params) {
    var paramsArray = [];
    
    for (var key in params) {
      var value = params[key];
      if ((value != null) && (typeof value != 'function')) {
        var encodedValue;
        if (typeof value == 'object' && value.constructor == Array) {
          /* value is an array; encode items and separate with "," */
          var encodedItemArray = [];
          var item;
          for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
            item = value[itemIndex];
            encodedItemArray.push(encodeURIComponent(
                (item === null || item === undefined) ? "" : item)
            );
          }
          encodedValue = encodedItemArray.join(",");
        }
        else {
          /* value is a string; simply encode */
          encodedValue = encodeURIComponent(value);
        }
        paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
      }
    }
    
    return paramsArray.join("&");
};

/**
 * Function: urlAppend
 * Appends a parameter string to a url. This function includes the logic for
 * using the appropriate character (none, & or ?) to append to the url before
 * appending the param string.
 * 
 * Parameters:
 * url - {String} The url to append to
 * paramStr - {String} The param string to append
 * 
 * Returns:
 * {String} The new url
 */
OpenLayers.Util.urlAppend = function(url, paramStr) {
    var newUrl = url;
    if(paramStr) {
        var parts = (url + " ").split(/[?&]/);
        newUrl += (parts.pop() === " " ?
            paramStr :
            parts.length ? "&" + paramStr : "?" + paramStr);
    }
    return newUrl;
};

/** 
 * Function: getImagesLocation
 * 
 * Returns:
 * {String} The fully formatted image location string
 */
OpenLayers.Util.getImagesLocation = function() {
    return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
};

/** 
 * Function: getImageLocation
 * 
 * Returns:
 * {String} The fully formatted location string for a specified image
 */
OpenLayers.Util.getImageLocation = function(image) {
    return OpenLayers.Util.getImagesLocation() + image;
};


/** 
 * Function: Try
 * Execute functions until one of them doesn't throw an error. 
 *     Capitalized because "try" is a reserved word in JavaScript.
 *     Taken directly from OpenLayers.Util.Try()
 * 
 * Parameters:
 * [*] - {Function} Any number of parameters may be passed to Try()
 *    It will attempt to execute each of them until one of them 
 *    successfully executes. 
 *    If none executes successfully, returns null.
 * 
 * Returns:
 * {*} The value returned by the first successfully executed function.
 */
OpenLayers.Util.Try = function() {
    var returnValue = null;

    for (var i=0, len=arguments.length; i<len; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
};

/**
 * Function: getXmlNodeValue
 * 
 * Parameters:
 * node - {XMLNode}
 * 
 * Returns:
 * {String} The text value of the given node, without breaking in firefox or IE
 */
OpenLayers.Util.getXmlNodeValue = function(node) {
    var val = null;
    OpenLayers.Util.Try( 
        function() {
            val = node.text;
            if (!val) {
                val = node.textContent;
            }
            if (!val) {
                val = node.firstChild.nodeValue;
            }
        }, 
        function() {
            val = node.textContent;
        }); 
    return val;
};

/** 
 * Function: mouseLeft
 * 
 * Parameters:
 * evt - {Event}
 * div - {HTMLDivElement}
 * 
 * Returns:
 * {Boolean}
 */
OpenLayers.Util.mouseLeft = function (evt, div) {
    // start with the element to which the mouse has moved
    var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
    // walk up the DOM tree.
    while (target != div && target != null) {
        target = target.parentNode;
    }
    // if the target we stop at isn't the div, then we've left the div.
    return (target != div);
};

/**
 * Property: precision
 * {Number} The number of significant digits to retain to avoid
 * floating point precision errors.
 *
 * We use 14 as a "safe" default because, although IEEE 754 double floats
 * (standard on most modern operating systems) support up to about 16
 * significant digits, 14 significant digits are sufficient to represent
 * sub-millimeter accuracy in any coordinate system that anyone is likely to
 * use with OpenLayers.
 *
 * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
 * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
 * with certain projections, e.g. spherical Mercator.
 *
 */
OpenLayers.Util.DEFAULT_PRECISION = 14;

/**
 * Function: toFloat
 * Convenience method to cast an object to a Number, rounded to the
 * desired floating point precision.
 *
 * Parameters:
 * number    - {Number} The number to cast and round.
 * precision - {Number} An integer suitable for use with
 *      Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
 *      If set to 0, no rounding is performed.
 *
 * Returns:
 * {Number} The cast, rounded number.
 */
OpenLayers.Util.toFloat = function (number, precision) {
    if (precision == null) {
        precision = OpenLayers.Util.DEFAULT_PRECISION;
    }
    if (typeof number !== "number") {
        number = parseFloat(number);
    }
    return precision === 0 ? number :
                             parseFloat(number.toPrecision(precision));
};

/**
 * Function: rad
 * 
 * Parameters:
 * x - {Float}
 * 
 * Returns:
 * {Float}
 */
OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};

/**
 * Function: deg
 *
 * Parameters:
 * x - {Float}
 *
 * Returns:
 * {Float}
 */
OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};

/**
 * Property: VincentyConstants
 * {Object} Constants for Vincenty functions.
 */
OpenLayers.Util.VincentyConstants = {
    a: 6378137,
    b: 6356752.3142,
    f: 1/298.257223563
};

/**
 * APIFunction: distVincenty
 * Given two objects representing points with geographic coordinates, this
 *     calculates the distance between those points on the surface of an
 *     ellipsoid.
 *
 * Parameters:
 * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
 * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
 *
 * Returns:
 * {Float} The distance (in km) between the two input points as measured on an
 *     ellipsoid.  Note that the input point objects must be in geographic
 *     coordinates (decimal degrees) and the return distance is in kilometers.
 */
OpenLayers.Util.distVincenty = function(p1, p2) {
    var ct = OpenLayers.Util.VincentyConstants;
    var a = ct.a, b = ct.b, f = ct.f;

    var L = OpenLayers.Util.rad(p2.lon - p1.lon);
    var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
    var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
    var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
    var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
    var lambda = L, lambdaP = 2*Math.PI;
    var iterLimit = 20;
    while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
        var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
        var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
        (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
        if (sinSigma==0) {
            return 0;  // co-incident points
        }
        var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
        var sigma = Math.atan2(sinSigma, cosSigma);
        var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
        var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
        var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
        var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
        lambdaP = lambda;
        lambda = L + (1-C) * f * Math.sin(alpha) *
        (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
    }
    if (iterLimit==0) {
        return NaN;  // formula failed to converge
    }
    var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
    var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
    var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
    var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
        B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
    var s = b*A*(sigma-deltaSigma);
    var d = s.toFixed(3)/1000; // round to 1mm precision
    return d;
};

/**
 * APIFunction: destinationVincenty
 * Calculate destination point given start point lat/long (numeric degrees),
 * bearing (numeric degrees) & distance (in m).
 * Adapted from Chris Veness work, see
 * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
 *
 * Parameters:
 * lonlat  - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
 *     properties) The start point.
 * brng     - {Float} The bearing (degrees).
 * dist     - {Float} The ground distance (meters).
 *
 * Returns:
 * {<OpenLayers.LonLat>} The destination point.
 */
OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
    var u = OpenLayers.Util;
    var ct = u.VincentyConstants;
    var a = ct.a, b = ct.b, f = ct.f;

    var lon1 = lonlat.lon;
    var lat1 = lonlat.lat;

    var s = dist;
    var alpha1 = u.rad(brng);
    var sinAlpha1 = Math.sin(alpha1);
    var cosAlpha1 = Math.cos(alpha1);

    var tanU1 = (1-f) * Math.tan(u.rad(lat1));
    var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
    var sigma1 = Math.atan2(tanU1, cosAlpha1);
    var sinAlpha = cosU1 * sinAlpha1;
    var cosSqAlpha = 1 - sinAlpha*sinAlpha;
    var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
    var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
    var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));

    var sigma = s / (b*A), sigmaP = 2*Math.PI;
    while (Math.abs(sigma-sigmaP) > 1e-12) {
        var cos2SigmaM = Math.cos(2*sigma1 + sigma);
        var sinSigma = Math.sin(sigma);
        var cosSigma = Math.cos(sigma);
        var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
            B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
        sigmaP = sigma;
        sigma = s / (b*A) + deltaSigma;
    }

    var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
    var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
        (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
    var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
    var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
    var L = lambda - (1-C) * f * sinAlpha *
        (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));

    var revAz = Math.atan2(sinAlpha, -tmp);  // final bearing

    return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
};

/**
 * Function: getParameters
 * Parse the parameters from a URL or from the current page itself into a 
 *     JavaScript Object. Note that parameter values with commas are separated
 *     out into an Array.
 * 
 * Parameters:
 * url - {String} Optional url used to extract the query string.
 *                If url is null or is not supplied, query string is taken 
 *                from the page location.
 * options - {Object} Additional options. Optional.
 *
 * Valid options:
 *   splitArgs - {Boolean} Split comma delimited params into arrays? Default is
 *       true.
 * 
 * Returns:
 * {Object} An object of key/value pairs from the query string.
 */
OpenLayers.Util.getParameters = function(url, options) {
    options = options || {};
    // if no url specified, take it from the location bar
    url = (url === null || url === undefined) ? window.location.href : url;

    //parse out parameters portion of url string
    var paramsString = "";
    if (OpenLayers.String.contains(url, '?')) {
        var start = url.indexOf('?') + 1;
        var end = OpenLayers.String.contains(url, "#") ?
                    url.indexOf('#') : url.length;
        paramsString = url.substring(start, end);
    }

    var parameters = {};
    var pairs = paramsString.split(/[&;]/);
    for(var i=0, len=pairs.length; i<len; ++i) {
        var keyValue = pairs[i].split('=');
        if (keyValue[0]) {

            var key = keyValue[0];
            try {
                key = decodeURIComponent(key);
            } catch (err) {
                key = unescape(key);
            }
            
            // being liberal by replacing "+" with " "
            var value = (keyValue[1] || '').replace(/\+/g, " ");

            try {
                value = decodeURIComponent(value);
            } catch (err) {
                value = unescape(value);
            }
            
            // follow OGC convention of comma delimited values
            if (options.splitArgs !== false) {
                value = value.split(",");
            }

            //if there's only one value, do not return as array                    
            if (value.length == 1) {
                value = value[0];
            }                
            
            parameters[key] = value;
         }
     }
    return parameters;
};

/**
 * Property: lastSeqID
 * {Integer} The ever-incrementing count variable.
 *           Used for generating unique ids.
 */
OpenLayers.Util.lastSeqID = 0;

/**
 * Function: createUniqueID
 * Create a unique identifier for this session.  Each time this function
 *     is called, a counter is incremented.  The return will be the optional
 *     prefix (defaults to "id_") appended with the counter value.
 * 
 * Parameters:
 * prefix - {String} Optional string to prefix unique id. Default is "id_".
 *     Note that dots (".") in the prefix will be replaced with underscore ("_").
 * 
 * Returns:
 * {String} A unique id string, built on the passed in prefix.
 */
OpenLayers.Util.createUniqueID = function(prefix) {
    if (prefix == null) {
        prefix = "id_";
    } else {
        prefix = prefix.replace(OpenLayers.Util.dotless, "_");
    }
    OpenLayers.Util.lastSeqID += 1; 
    return prefix + OpenLayers.Util.lastSeqID;        
};

/**
 * Constant: INCHES_PER_UNIT
 * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
 * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
 * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
 * and PROJ.4 (http://trac.osgeo.org/proj/)
 * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
 * The hardcoded table of PROJ.4 units are in pj_units.c.
 */
OpenLayers.INCHES_PER_UNIT = { 
    'inches': 1.0,
    'ft': 12.0,
    'mi': 63360.0,
    'm': 39.37,
    'km': 39370,
    'dd': 4374754,
    'yd': 36
};
OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;

// Units from CS-Map
OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
    "Inch": OpenLayers.INCHES_PER_UNIT.inches,
    "Meter": 1.0 / OpenLayers.METERS_PER_INCH,   //EPSG:9001
    "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH,   //EPSG:9003
    "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9002
    "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH,   //EPSG:9005
    "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH,   //EPSG:9041
    "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH,   //EPSG:9094
    "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
    "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
    "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
    "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
    "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9036
    "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
    "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH,   //EPSG:9040
    "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH,   //EPSG:9084
    "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH,   //EPSG:9085
    "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH,   //EPSG:9086
    "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH,   //EPSG:9087
    "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH,   //EPSG:9080
    "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH,   //EPSG:9081
    "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH,   //EPSG:9082
    "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH,   //EPSG:9083
    "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
    "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9096
    "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9093
    "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9030
    "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
    "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
    "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
    "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
    "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
    "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
    "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
    "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH,   //EPSG:9031
    "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
    "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9038
    "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9033
    "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9062
    "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9042
    "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9039
    "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9034
    "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9063
    "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9043
    "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
    "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH,   //EPSG:9097
    "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH,   //EPSG:9098
    "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
    "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
    "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
    "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
    "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
    "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
    "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
    "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
    "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
    "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
    "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
});

//unit abbreviations supported by PROJ.4
OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
    "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
    "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
    "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
    "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
    "kmi": OpenLayers.INCHES_PER_UNIT["nmi"],    //International Nautical Mile
    "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
    "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"],  //International Chain
    "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
    "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
    "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
    "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
    "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
    "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"],   //U.S. Surveyor's Statute Mile
    "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"],  //Indian Yard
    "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"],  //Indian Foot
    "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH  //Indian Chain
});

/** 
 * Constant: DOTS_PER_INCH
 * {Integer} 72 (A sensible default)
 */
OpenLayers.DOTS_PER_INCH = 72;

/**
 * Function: normalizeScale
 * 
 * Parameters:
 * scale - {float}
 * 
 * Returns:
 * {Float} A normalized scale value, in 1 / X format. 
 *         This means that if a value less than one ( already 1/x) is passed
 *         in, it just returns scale directly. Otherwise, it returns 
 *         1 / scale
 */
OpenLayers.Util.normalizeScale = function (scale) {
    var normScale = (scale > 1.0) ? (1.0 / scale) 
                                  : scale;
    return normScale;
};

/**
 * Function: getResolutionFromScale
 * 
 * Parameters:
 * scale - {Float}
 * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
 *                  Default is degrees
 * 
 * Returns:
 * {Float} The corresponding resolution given passed-in scale and unit 
 *     parameters.  If the given scale is falsey, the returned resolution will
 *     be undefined.
 */
OpenLayers.Util.getResolutionFromScale = function (scale, units) {
    var resolution;
    if (scale) {
        if (units == null) {
            units = "degrees";
        }
        var normScale = OpenLayers.Util.normalizeScale(scale);
        resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
                                        * OpenLayers.DOTS_PER_INCH);        
    }
    return resolution;
};

/**
 * Function: getScaleFromResolution
 * 
 * Parameters:
 * resolution - {Float}
 * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
 *                  Default is degrees
 * 
 * Returns:
 * {Float} The corresponding scale given passed-in resolution and unit 
 *         parameters.
 */
OpenLayers.Util.getScaleFromResolution = function (resolution, units) {

    if (units == null) {
        units = "degrees";
    }

    var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
                    OpenLayers.DOTS_PER_INCH;
    return scale;
};

/**
 * Function: pagePosition
 * Calculates the position of an element on the page (see
 * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
 *
 * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
 * Copyright (c) 2006, Yahoo! Inc.
 * All rights reserved.
 * 
 * Redistribution and use of this software in source and binary forms, with or
 * without modification, are permitted provided that the following conditions
 * are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission of Yahoo! Inc.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Parameters:
 * forElement - {DOMElement}
 * 
 * Returns:
 * {Array} two item array, Left value then Top value.
 */
OpenLayers.Util.pagePosition =  function(forElement) {
    // NOTE: If element is hidden (display none or disconnected or any the
    // ancestors are hidden) we get (0,0) by default but we still do the
    // accumulation of scroll position.

    var pos = [0, 0];
    var viewportElement = OpenLayers.Util.getViewportElement();
    if (!forElement || forElement == window || forElement == viewportElement) {
        // viewport is always at 0,0 as that defined the coordinate system for
        // this function - this avoids special case checks in the code below
        return pos;
    }

    // Gecko browsers normally use getBoxObjectFor to calculate the position.
    // When invoked for an element with an implicit absolute position though it
    // can be off by one. Therefore the recursive implementation is used in
    // those (relatively rare) cases.
    var BUGGY_GECKO_BOX_OBJECT =
        OpenLayers.IS_GECKO && document.getBoxObjectFor &&
        OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
        (forElement.style.top == '' || forElement.style.left == '');

    var parent = null;
    var box;

    if (forElement.getBoundingClientRect) { // IE
        box = forElement.getBoundingClientRect();
        var scrollTop = window.pageYOffset || viewportElement.scrollTop;
        var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
        
        pos[0] = box.left + scrollLeft;
        pos[1] = box.top + scrollTop;

    } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
        // Gecko ignores the scroll values for ancestors, up to 1.9.  See:
        // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
        // https://bugzilla.mozilla.org/show_bug.cgi?id=330619

        box = document.getBoxObjectFor(forElement);
        var vpBox = document.getBoxObjectFor(viewportElement);
        pos[0] = box.screenX - vpBox.screenX;
        pos[1] = box.screenY - vpBox.screenY;

    } else { // safari/opera
        pos[0] = forElement.offsetLeft;
        pos[1] = forElement.offsetTop;
        parent = forElement.offsetParent;
        if (parent != forElement) {
            while (parent) {
                pos[0] += parent.offsetLeft;
                pos[1] += parent.offsetTop;
                parent = parent.offsetParent;
            }
        }

        var browser = OpenLayers.BROWSER_NAME;

        // opera & (safari absolute) incorrectly account for body offsetTop
        if (browser == "opera" || (browser == "safari" &&
              OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
            pos[1] -= document.body.offsetTop;
        }

        // accumulate the scroll positions for everything but the body element
        parent = forElement.offsetParent;
        while (parent && parent != document.body) {
            pos[0] -= parent.scrollLeft;
            // see https://bugs.opera.com/show_bug.cgi?id=249965
            if (browser != "opera" || parent.tagName != 'TR') {
                pos[1] -= parent.scrollTop;
            }
            parent = parent.offsetParent;
        }
    }
    
    return pos;
};

/**
 * Function: getViewportElement
 * Returns die viewport element of the document. The viewport element is
 * usually document.documentElement, except in IE,where it is either
 * document.body or document.documentElement, depending on the document's
 * compatibility mode (see
 * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
 *
 * Returns:
 * {DOMElement}
 */
OpenLayers.Util.getViewportElement = function() {
    var viewportElement = arguments.callee.viewportElement;
    if (viewportElement == undefined) {
        viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
            document.compatMode != 'CSS1Compat') ? document.body :
            document.documentElement;
        arguments.callee.viewportElement = viewportElement;
    }
    return viewportElement;
};

/** 
 * Function: isEquivalentUrl
 * Test two URLs for equivalence. 
 * 
 * Setting 'ignoreCase' allows for case-independent comparison.
 * 
 * Comparison is based on: 
 *  - Protocol
 *  - Host (evaluated without the port)
 *  - Port (set 'ignorePort80' to ignore "80" values)
 *  - Hash ( set 'ignoreHash' to disable)
 *  - Pathname (for relative <-> absolute comparison) 
 *  - Arguments (so they can be out of order)
 *  
 * Parameters:
 * url1 - {String}
 * url2 - {String}
 * options - {Object} Allows for customization of comparison:
 *                    'ignoreCase' - Default is True
 *                    'ignorePort80' - Default is True
 *                    'ignoreHash' - Default is True
 *
 * Returns:
 * {Boolean} Whether or not the two URLs are equivalent
 */
OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
    options = options || {};

    OpenLayers.Util.applyDefaults(options, {
        ignoreCase: true,
        ignorePort80: true,
        ignoreHash: true,
        splitArgs: false
    });

    var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
    var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);

    //compare all keys except for "args" (treated below)
    for(var key in urlObj1) {
        if(key !== "args") {
            if(urlObj1[key] != urlObj2[key]) {
                return false;
            }
        }
    }

    // compare search args - irrespective of order
    for(var key in urlObj1.args) {
        if(urlObj1.args[key] != urlObj2.args[key]) {
            return false;
        }
        delete urlObj2.args[key];
    }
    // urlObj2 shouldn't have any args left
    for(var key in urlObj2.args) {
        return false;
    }
    
    return true;
};

/**
 * Function: createUrlObject
 * 
 * Parameters:
 * url - {String}
 * options - {Object} A hash of options.
 *
 * Valid options:
 *   ignoreCase - {Boolean} lowercase url,
 *   ignorePort80 - {Boolean} don't include explicit port if port is 80,
 *   ignoreHash - {Boolean} Don't include part of url after the hash (#).
 *   splitArgs - {Boolean} Split comma delimited params into arrays? Default is
 *       true.
 * 
 * Returns:
 * {Object} An object with separate url, a, port, host, and args parsed out 
 *          and ready for comparison
 */
OpenLayers.Util.createUrlObject = function(url, options) {
    options = options || {};

    // deal with relative urls first
    if(!(/^\w+:\/\//).test(url)) {
        var loc = window.location;
        var port = loc.port ? ":" + loc.port : "";
        var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
        if(url.indexOf("/") === 0) {
            // full pathname
            url = fullUrl + url;
        } else {
            // relative to current path
            var parts = loc.pathname.split("/");
            parts.pop();
            url = fullUrl + parts.join("/") + "/" + url;
        }
    }
  
    if (options.ignoreCase) {
        url = url.toLowerCase(); 
    }

    var a = document.createElement('a');
    a.href = url;
    
    var urlObject = {};
    
    //host (without port)
    urlObject.host = a.host.split(":").shift();

    //protocol
    urlObject.protocol = a.protocol;  

    //port (get uniform browser behavior with port 80 here)
    if(options.ignorePort80) {
        urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
    } else {
        urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
    }

    //hash
    urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;  
    
    //args
    var queryString = a.search;
    if (!queryString) {
        var qMark = url.indexOf("?");
        queryString = (qMark != -1) ? url.substr(qMark) : "";
    }
    urlObject.args = OpenLayers.Util.getParameters(queryString,
            {splitArgs: options.splitArgs});

    // pathname
    //
    // This is a workaround for Internet Explorer where
    // window.location.pathname has a leading "/", but
    // a.pathname has no leading "/".
    urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
    
    return urlObject; 
};
 
/**
 * Function: removeTail
 * Takes a url and removes everything after the ? and #
 * 
 * Parameters:
 * url - {String} The url to process
 * 
 * Returns:
 * {String} The string with all queryString and Hash removed
 */
OpenLayers.Util.removeTail = function(url) {
    var head = null;
    
    var qMark = url.indexOf("?");
    var hashMark = url.indexOf("#");

    if (qMark == -1) {
        head = (hashMark != -1) ? url.substr(0,hashMark) : url;
    } else {
        head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark)) 
                                  : url.substr(0, qMark);
    }
    return head;
};

/**
 * Constant: IS_GECKO
 * {Boolean} True if the userAgent reports the browser to use the Gecko engine
 */
OpenLayers.IS_GECKO = (function() {
    var ua = navigator.userAgent.toLowerCase();
    return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
})();

/**
 * Constant: CANVAS_SUPPORTED
 * {Boolean} True if canvas 2d is supported.
 */
OpenLayers.CANVAS_SUPPORTED = (function() {
    var elem = document.createElement('canvas');
    return !!(elem.getContext && elem.getContext('2d'));
})();

/**
 * Constant: BROWSER_NAME
 * {String}
 * A substring of the navigator.userAgent property.  Depending on the userAgent
 *     property, this will be the empty string or one of the following:
 *     * "opera" -- Opera
 *     * "msie"  -- Internet Explorer
 *     * "safari" -- Safari
 *     * "firefox" -- Firefox
 *     * "mozilla" -- Mozilla
 */
OpenLayers.BROWSER_NAME = (function() {
    var name = "";
    var ua = navigator.userAgent.toLowerCase();
    if (ua.indexOf("opera") != -1) {
        name = "opera";
    } else if (ua.indexOf("msie") != -1) {
        name = "msie";
    } else if (ua.indexOf("safari") != -1) {
        name = "safari";
    } else if (ua.indexOf("mozilla") != -1) {
        if (ua.indexOf("firefox") != -1) {
            name = "firefox";
        } else {
            name = "mozilla";
        }
    }
    return name;
})();

/**
 * Function: getBrowserName
 * 
 * Returns:
 * {String} A string which specifies which is the current 
 *          browser in which we are running. 
 * 
 *          Currently-supported browser detection and codes:
 *           * 'opera' -- Opera
 *           * 'msie'  -- Internet Explorer
 *           * 'safari' -- Safari
 *           * 'firefox' -- Firefox
 *           * 'mozilla' -- Mozilla
 * 
 *          If we are unable to property identify the browser, we 
 *           return an empty string.
 */
OpenLayers.Util.getBrowserName = function() {
    return OpenLayers.BROWSER_NAME;
};

/**
 * Method: getRenderedDimensions
 * Renders the contentHTML offscreen to determine actual dimensions for
 *     popup sizing. As we need layout to determine dimensions the content
 *     is rendered -9999px to the left and absolute to ensure the 
 *     scrollbars do not flicker
 *     
 * Parameters:
 * contentHTML
 * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is 
 *     specified, we fix that dimension of the div to be measured. This is 
 *     useful in the case where we have a limit in one dimension and must 
 *     therefore meaure the flow in the other dimension.
 * options - {Object}
 *
 * Allowed Options:
 *     displayClass - {String} Optional parameter.  A CSS class name(s) string
 *         to provide the CSS context of the rendered content.
 *     containerElement - {DOMElement} Optional parameter. Insert the HTML to 
 *         this node instead of the body root when calculating dimensions. 
 * 
 * Returns:
 * {<OpenLayers.Size>}
 */
OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
    
    var w, h;
    
    // create temp container div with restricted size
    var container = document.createElement("div");
    container.style.visibility = "hidden";
        
    var containerElement = (options && options.containerElement) 
        ? options.containerElement : document.body;
    
    // Opera and IE7 can't handle a node with position:aboslute if it inherits
    // position:absolute from a parent.
    var parentHasPositionAbsolute = false;
    var superContainer = null;
    var parent = containerElement;
    while (parent && parent.tagName.toLowerCase()!="body") {
        var parentPosition = OpenLayers.Element.getStyle(parent, "position");
        if(parentPosition == "absolute") {
            parentHasPositionAbsolute = true;
            break;
        } else if (parentPosition && parentPosition != "static") {
            break;
        }
        parent = parent.parentNode;
    }
    if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 || 
                                     containerElement.clientWidth === 0) ){
        superContainer = document.createElement("div");
        superContainer.style.visibility = "hidden";
        superContainer.style.position = "absolute";
        superContainer.style.overflow = "visible";
        superContainer.style.width = document.body.clientWidth + "px";
        superContainer.style.height = document.body.clientHeight + "px";
        superContainer.appendChild(container);
    }
    container.style.position = "absolute";

    //fix a dimension, if specified.
    if (size) {
        if (size.w) {
            w = size.w;
            container.style.width = w + "px";
        } else if (size.h) {
            h = size.h;
            container.style.height = h + "px";
        }
    }

    //add css classes, if specified
    if (options && options.displayClass) {
        container.className = options.displayClass;
    }
    
    // create temp content div and assign content
    var content = document.createElement("div");
    content.innerHTML = contentHTML;
    
    // we need overflow visible when calculating the size
    content.style.overflow = "visible";
    if (content.childNodes) {
        for (var i=0, l=content.childNodes.length; i<l; i++) {
            if (!content.childNodes[i].style) continue;
            content.childNodes[i].style.overflow = "visible";
        }
    }
    
    // add content to restricted container 
    container.appendChild(content);
    
    // append container to body for rendering
    if (superContainer) {
        containerElement.appendChild(superContainer);
    } else {
        containerElement.appendChild(container);
    }
    
    // calculate scroll width of content and add corners and shadow width
    if (!w) {
        w = parseInt(content.scrollWidth);
    
        // update container width to allow height to adjust
        container.style.width = w + "px";
    }        
    // capture height and add shadow and corner image widths
    if (!h) {
        h = parseInt(content.scrollHeight);
    }

    // remove elements
    container.removeChild(content);
    if (superContainer) {
        superContainer.removeChild(container);
        containerElement.removeChild(superContainer);
    } else {
        containerElement.removeChild(container);
    }
    
    return new OpenLayers.Size(w, h);
};

/**
 * APIFunction: getScrollbarWidth
 * This function has been modified by the OpenLayers from the original version,
 *     written by Matthew Eernisse and released under the Apache 2 
 *     license here:
 * 
 *     http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
 * 
 *     It has been modified simply to cache its value, since it is physically 
 *     impossible that this code could ever run in more than one browser at 
 *     once. 
 * 
 * Returns:
 * {Integer}
 */
OpenLayers.Util.getScrollbarWidth = function() {
    
    var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
    
    if (scrollbarWidth == null) {
        var scr = null;
        var inn = null;
        var wNoScroll = 0;
        var wScroll = 0;
    
        // Outer scrolling div
        scr = document.createElement('div');
        scr.style.position = 'absolute';
        scr.style.top = '-1000px';
        scr.style.left = '-1000px';
        scr.style.width = '100px';
        scr.style.height = '50px';
        // Start with no scrollbar
        scr.style.overflow = 'hidden';
    
        // Inner content div
        inn = document.createElement('div');
        inn.style.width = '100%';
        inn.style.height = '200px';
    
        // Put the inner div in the scrolling div
        scr.appendChild(inn);
        // Append the scrolling div to the doc
        document.body.appendChild(scr);
    
        // Width of the inner div sans scrollbar
        wNoScroll = inn.offsetWidth;
    
        // Add the scrollbar
        scr.style.overflow = 'scroll';
        // Width of the inner div width scrollbar
        wScroll = inn.offsetWidth;
    
        // Remove the scrolling div from the doc
        document.body.removeChild(document.body.lastChild);
    
        // Pixel width of the scroller
        OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
        scrollbarWidth = OpenLayers.Util._scrollbarWidth;
    }

    return scrollbarWidth;
};

/**
 * APIFunction: getFormattedLonLat
 * This function will return latitude or longitude value formatted as 
 *
 * Parameters:
 * coordinate - {Float} the coordinate value to be formatted
 * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
 *          to be formatted (default = lat)
 * dmsOption - {String} specify the precision of the output can be one of:
 *           'dms' show degrees minutes and seconds
 *           'dm' show only degrees and minutes
 *           'd' show only degrees
 * 
 * Returns:
 * {String} the coordinate value formatted as a string
 */
OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
    if (!dmsOption) {
        dmsOption = 'dms';    //default to show degree, minutes, seconds
    }

    coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round

    var abscoordinate = Math.abs(coordinate);
    var coordinatedegrees = Math.floor(abscoordinate);

    var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
    var tempcoordinateminutes = coordinateminutes;
    coordinateminutes = Math.floor(coordinateminutes);
    var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
    coordinateseconds =  Math.round(coordinateseconds*10);
    coordinateseconds /= 10;

    if( coordinateseconds >= 60) { 
        coordinateseconds -= 60; 
        coordinateminutes += 1; 
        if( coordinateminutes >= 60) { 
            coordinateminutes -= 60; 
            coordinatedegrees += 1; 
        } 
    }
    
    if( coordinatedegrees < 10 ) {
        coordinatedegrees = "0" + coordinatedegrees;
    }
    var str = coordinatedegrees + "\u00B0";

    if (dmsOption.indexOf('dm') >= 0) {
        if( coordinateminutes < 10 ) {
            coordinateminutes = "0" + coordinateminutes;
        }
        str += coordinateminutes + "'";
  
        if (dmsOption.indexOf('dms') >= 0) {
            if( coordinateseconds < 10 ) {
                coordinateseconds = "0" + coordinateseconds;
            }
            str += coordinateseconds + '"';
        }
    }
    
    if (axis == "lon") {
        str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
    } else {
        str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
    }
    return str;
};

/* ======================================================================
    OpenLayers/Events.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Util.js
 */

/**
 * Namespace: OpenLayers.Event
 * Utility functions for event handling.
 */
OpenLayers.Event = {

    /** 
     * Property: observers 
     * {Object} A hashtable cache of the event observers. Keyed by
     * element._eventCacheID 
     */
    observers: false,

    /**
     * Constant: KEY_SPACE
     * {int}
     */
    KEY_SPACE: 32,
    
    /** 
     * Constant: KEY_BACKSPACE 
     * {int} 
     */
    KEY_BACKSPACE: 8,

    /** 
     * Constant: KEY_TAB 
     * {int} 
     */
    KEY_TAB: 9,

    /** 
     * Constant: KEY_RETURN 
     * {int} 
     */
    KEY_RETURN: 13,

    /** 
     * Constant: KEY_ESC 
     * {int} 
     */
    KEY_ESC: 27,

    /** 
     * Constant: KEY_LEFT 
     * {int} 
     */
    KEY_LEFT: 37,

    /** 
     * Constant: KEY_UP 
     * {int} 
     */
    KEY_UP: 38,

    /** 
     * Constant: KEY_RIGHT 
     * {int} 
     */
    KEY_RIGHT: 39,

    /** 
     * Constant: KEY_DOWN 
     * {int} 
     */
    KEY_DOWN: 40,

    /** 
     * Constant: KEY_DELETE 
     * {int} 
     */
    KEY_DELETE: 46,


    /**
     * Method: element
     * Cross browser event element detection.
     * 
     * Parameters:
     * event - {Event} 
     * 
     * Returns:
     * {DOMElement} The element that caused the event 
     */
    element: function(event) {
        return event.target || event.srcElement;
    },

    /**
     * Method: isSingleTouch
     * Determine whether event was caused by a single touch
     *
     * Parameters:
     * event - {Event}
     *
     * Returns:
     * {Boolean}
     */
    isSingleTouch: function(event) {
        return event.touches && event.touches.length == 1;
    },

    /**
     * Method: isMultiTouch
     * Determine whether event was caused by a multi touch
     *
     * Parameters:
     * event - {Event}
     *
     * Returns:
     * {Boolean}
     */
    isMultiTouch: function(event) {
        return event.touches && event.touches.length > 1;
    },

    /**
     * Method: isLeftClick
     * Determine whether event was caused by a left click. 
     *
     * Parameters:
     * event - {Event} 
     * 
     * Returns:
     * {Boolean}
     */
    isLeftClick: function(event) {
        return (((event.which) && (event.which == 1)) ||
                ((event.button) && (event.button == 1)));
    },

    /**
     * Method: isRightClick
     * Determine whether event was caused by a right mouse click. 
     *
     * Parameters:
     * event - {Event} 
     * 
     * Returns:
     * {Boolean}
     */
     isRightClick: function(event) {
        return (((event.which) && (event.which == 3)) ||
                ((event.button) && (event.button == 2)));
    },
     
    /**
     * Method: stop
     * Stops an event from propagating. 
     *
     * Parameters: 
     * event - {Event} 
     * allowDefault - {Boolean} If true, we stop the event chain but 
     *     still allow the default browser behaviour (text selection,
     *     radio-button clicking, etc).  Default is false.
     */
    stop: function(event, allowDefault) {
        
        if (!allowDefault) { 
            OpenLayers.Event.preventDefault(event);
        }
                
        if (event.stopPropagation) {
            event.stopPropagation();
        } else {
            event.cancelBubble = true;
        }
    },

    /**
     * Method: preventDefault
     * Cancels the event if it is cancelable, without stopping further
     * propagation of the event.
     *
     * Parameters:
     * event - {Event}
     */
    preventDefault: function(event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },

    /** 
     * Method: findElement
     * 
     * Parameters:
     * event - {Event} 
     * tagName - {String} 
     * 
     * Returns:
     * {DOMElement} The first node with the given tagName, starting from the
     * node the event was triggered on and traversing the DOM upwards
     */
    findElement: function(event, tagName) {
        var element = OpenLayers.Event.element(event);
        while (element.parentNode && (!element.tagName ||
              (element.tagName.toUpperCase() != tagName.toUpperCase()))){
            element = element.parentNode;
        }
        return element;
    },

    /** 
     * Method: observe
     * 
     * Parameters:
     * elementParam - {DOMElement || String} 
     * name - {String} 
     * observer - {function} 
     * useCapture - {Boolean} 
     */
    observe: function(elementParam, name, observer, useCapture) {
        var element = OpenLayers.Util.getElement(elementParam);
        useCapture = useCapture || false;

        if (name == 'keypress' &&
           (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
           || element.attachEvent)) {
            name = 'keydown';
        }

        //if observers cache has not yet been created, create it
        if (!this.observers) {
            this.observers = {};
        }

        //if not already assigned, make a new unique cache ID
        if (!element._eventCacheID) {
            var idPrefix = "eventCacheID_";
            if (element.id) {
                idPrefix = element.id + "_" + idPrefix;
            }
            element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
        }

        var cacheID = element._eventCacheID;

        //if there is not yet a hash entry for this element, add one
        if (!this.observers[cacheID]) {
            this.observers[cacheID] = [];
        }

        //add a new observer to this element's list
        this.observers[cacheID].push({
            'element': element,
            'name': name,
            'observer': observer,
            'useCapture': useCapture
        });

        //add the actual browser event listener
        if (element.addEventListener) {
            element.addEventListener(name, observer, useCapture);
        } else if (element.attachEvent) {
            element.attachEvent('on' + name, observer);
        }
    },

    /** 
     * Method: stopObservingElement
     * Given the id of an element to stop observing, cycle through the 
     *   element's cached observers, calling stopObserving on each one, 
     *   skipping those entries which can no longer be removed.
     * 
     * parameters:
     * elementParam - {DOMElement || String} 
     */
    stopObservingElement: function(elementParam) {
        var element = OpenLayers.Util.getElement(elementParam);
        var cacheID = element._eventCacheID;

        this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
    },

    /**
     * Method: _removeElementObservers
     *
     * Parameters:
     * elementObservers - {Array(Object)} Array of (element, name, 
     *                                         observer, usecapture) objects, 
     *                                         taken directly from hashtable
     */
    _removeElementObservers: function(elementObservers) {
        if (elementObservers) {
            for(var i = elementObservers.length-1; i >= 0; i--) {
                var entry = elementObservers[i];
                OpenLayers.Event.stopObserving.apply(this, [
                    entry.element, entry.name, entry.observer, entry.useCapture
                ]);
            }
        }
    },

    /**
     * Method: stopObserving
     * 
     * Parameters:
     * elementParam - {DOMElement || String} 
     * name - {String} 
     * observer - {function} 
     * useCapture - {Boolean} 
     *  
     * Returns:
     * {Boolean} Whether or not the event observer was removed
     */
    stopObserving: function(elementParam, name, observer, useCapture) {
        useCapture = useCapture || false;
    
        var element = OpenLayers.Util.getElement(elementParam);
        var cacheID = element._eventCacheID;

        if (name == 'keypress') {
            if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) || 
                 element.detachEvent) {
              name = 'keydown';
            }
        }

        // find element's entry in this.observers cache and remove it
        var foundEntry = false;
        var elementObservers = OpenLayers.Event.observers[cacheID];
        if (elementObservers) {
    
            // find the specific event type in the element's list
            var i=0;
            while(!foundEntry && i < elementObservers.length) {
                var cacheEntry = elementObservers[i];
    
                if ((cacheEntry.name == name) &&
                    (cacheEntry.observer == observer) &&
                    (cacheEntry.useCapture == useCapture)) {
    
                    elementObservers.splice(i, 1);
                    if (elementObservers.length == 0) {
                        delete OpenLayers.Event.observers[cacheID];
                    }
                    foundEntry = true;
                    break; 
                }
                i++;           
            }
        }
    
        //actually remove the event listener from browser
        if (foundEntry) {
            if (element.removeEventListener) {
                element.removeEventListener(name, observer, useCapture);
            } else if (element && element.detachEvent) {
                element.detachEvent('on' + name, observer);
            }
        }
        return foundEntry;
    },
    
    /** 
     * Method: unloadCache
     * Cycle through all the element entries in the events cache and call
     *   stopObservingElement on each. 
     */
    unloadCache: function() {
        // check for OpenLayers.Event before checking for observers, because
        // OpenLayers.Event may be undefined in IE if no map instance was
        // created
        if (OpenLayers.Event && OpenLayers.Event.observers) {
            for (var cacheID in OpenLayers.Event.observers) {
                var elementObservers = OpenLayers.Event.observers[cacheID];
                OpenLayers.Event._removeElementObservers.apply(this, 
                                                           [elementObservers]);
            }
            OpenLayers.Event.observers = false;
        }
    },

    CLASS_NAME: "OpenLayers.Event"
};

/* prevent memory leaks in IE */
OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);

/**
 * Class: OpenLayers.Events
 */
OpenLayers.Events = OpenLayers.Class({

    /** 
     * Constant: BROWSER_EVENTS
     * {Array(String)} supported events 
     */
    BROWSER_EVENTS: [
        "mouseover", "mouseout",
        "mousedown", "mouseup", "mousemove", 
        "click", "dblclick", "rightclick", "dblrightclick",
        "resize", "focus", "blur",
        "touchstart", "touchmove", "touchend",
        "keydown"
    ],

    /** 
     * Property: listeners 
     * {Object} Hashtable of Array(Function): events listener functions  
     */
    listeners: null,

    /** 
     * Property: object 
     * {Object}  the code object issuing application events 
     */
    object: null,

    /** 
     * Property: element 
     * {DOMElement}  the DOM element receiving browser events 
     */
    element: null,

    /** 
     * Property: eventHandler 
     * {Function}  bound event handler attached to elements 
     */
    eventHandler: null,

    /** 
     * APIProperty: fallThrough 
     * {Boolean} 
     */
    fallThrough: null,

    /** 
     * APIProperty: includeXY
     * {Boolean} Should the .xy property automatically be created for browser
     *    mouse events? In general, this should be false. If it is true, then
     *    mouse events will automatically generate a '.xy' property on the 
     *    event object that is passed. (Prior to OpenLayers 2.7, this was true
     *    by default.) Otherwise, you can call the getMousePosition on the
     *    relevant events handler on the object available via the 'evt.object'
     *    property of the evt object. So, for most events, you can call:
     *    function named(evt) { 
     *        this.xy = this.object.events.getMousePosition(evt) 
     *    } 
     *
     *    This option typically defaults to false for performance reasons:
     *    when creating an events object whose primary purpose is to manage
     *    relatively positioned mouse events within a div, it may make
     *    sense to set it to true.
     *
     *    This option is also used to control whether the events object caches
     *    offsets. If this is false, it will not: the reason for this is that
     *    it is only expected to be called many times if the includeXY property
     *    is set to true. If you set this to true, you are expected to clear 
     *    the offset cache manually (using this.clearMouseCache()) if:
     *        the border of the element changes
     *        the location of the element in the page changes
    */
    includeXY: false,      
    
    /**
     * APIProperty: extensions
     * {Object} Event extensions registered with this instance. Keys are
     *     event types, values are {OpenLayers.Events.*} extension instances or
     *     {Boolean} for events that an instantiated extension provides in
     *     addition to the one it was created for.
     *
     * Extensions create an event in addition to browser events, which usually
     * fires when a sequence of browser events is completed. Extensions are
     * automatically instantiated when a listener is registered for an event
     * provided by an extension.
     *
     * Extensions are created in the <OpenLayers.Events> namespace using
     * <OpenLayers.Class>, and named after the event they provide.
     * The constructor receives the target <OpenLayers.Events> instance as
     * argument. Extensions that need to capture browser events before they
     * propagate can register their listeners events using <register>, with
     * {extension: true} as 4th argument.
     *
     * If an extension creates more than one event, an alias for each event
     * type should be created and reference the same class. The constructor
     * should set a reference in the target's extensions registry to itself.
     *
     * Below is a minimal extension that provides the "foostart" and "fooend"
     * event types, which replace the native "click" event type if clicked on
     * an element with the css class "foo":
     *
     * (code)
     *   OpenLayers.Events.foostart = OpenLayers.Class({
     *       initialize: function(target) {
     *           this.target = target;
     *           this.target.register("click", this, this.doStuff, {extension: true});
     *           // only required if extension provides more than one event type
     *           this.target.extensions["foostart"] = true;
     *           this.target.extensions["fooend"] = true;
     *       },
     *       destroy: function() {
     *           var target = this.target;
     *           target.unregister("click", this, this.doStuff);
     *           delete this.target;
     *           // only required if extension provides more than one event type
     *           delete target.extensions["foostart"];
     *           delete target.extensions["fooend"];
     *       },
     *       doStuff: function(evt) {
     *           var propagate = true;
     *           if (OpenLayers.Event.element(evt).className === "foo") {
     *               propagate = false;
     *               var target = this.target;
     *               target.triggerEvent("foostart");
     *               window.setTimeout(function() {
     *                   target.triggerEvent("fooend");
     *               }, 1000);
     *           }
     *           return propagate;
     *       }
     *   });
     *   // only required if extension provides more than one event type
     *   OpenLayers.Events.fooend = OpenLayers.Events.foostart;
     * (end)
     * 
     */
    extensions: null,
    
    /**
     * Property: extensionCount
     * {Object} Keys are event types (like in <listeners>), values are the
     *     number of extension listeners for each event type.
     */
    extensionCount: null,

    /**
     * Method: clearMouseListener
     * A version of <clearMouseCache> that is bound to this instance so that
     *     it can be used with <OpenLayers.Event.observe> and
     *     <OpenLayers.Event.stopObserving>.
     */
    clearMouseListener: null,

    /**
     * Constructor: OpenLayers.Events
     * Construct an OpenLayers.Events object.
     *
     * Parameters:
     * object - {Object} The js object to which this Events object  is being added
     * element - {DOMElement} A dom element to respond to browser events
     * eventTypes - {Array(String)} Deprecated.  Array of custom application
     *     events.  A listener may be registered for any named event, regardless
     *     of the values provided here.
     * fallThrough - {Boolean} Allow events to fall through after these have
     *                         been handled?
     * options - {Object} Options for the events object.
     */
    initialize: function (object, element, eventTypes, fallThrough, options) {
        OpenLayers.Util.extend(this, options);
        this.object     = object;
        this.fallThrough = fallThrough;
        this.listeners  = {};
        this.extensions = {};
        this.extensionCount = {};
        this._msTouches = [];
        
        // if a dom element is specified, add a listeners list 
        // for browser events on the element and register them
        if (element != null) {
            this.attachToElement(element);
        }
    },

    /**
     * APIMethod: destroy
     */
    destroy: function () {
        for (var e in this.extensions) {
            if (typeof this.extensions[e] !== "boolean") {
                this.extensions[e].destroy();
            }
        }
        this.extensions = null;
        if (this.element) {
            OpenLayers.Event.stopObservingElement(this.element);
            if(this.element.hasScrollEvent) {
                OpenLayers.Event.stopObserving(
                    window, "scroll", this.clearMouseListener
                );
            }
        }
        this.element = null;

        this.listeners = null;
        this.object = null;
        this.fallThrough = null;
        this.eventHandler = null;
    },

    /**
     * APIMethod: addEventType
     * Deprecated.  Any event can be triggered without adding it first.
     * 
     * Parameters:
     * eventName - {String}
     */
    addEventType: function(eventName) {
    },

    /**
     * Method: attachToElement
     *
     * Parameters:
     * element - {HTMLDOMElement} a DOM element to attach browser events to
     */
    attachToElement: function (element) {
        if (this.element) {
            OpenLayers.Event.stopObservingElement(this.element);
        } else {
            // keep a bound copy of handleBrowserEvent() so that we can
            // pass the same function to both Event.observe() and .stopObserving()
            this.eventHandler = OpenLayers.Function.bindAsEventListener(
                this.handleBrowserEvent, this
            );
            
            // to be used with observe and stopObserving
            this.clearMouseListener = OpenLayers.Function.bind(
                this.clearMouseCache, this
            );
        }
        this.element = element;
        var msTouch = !!window.navigator.msMaxTouchPoints;
        var type;
        for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
            type = this.BROWSER_EVENTS[i];
            // register the event cross-browser
            OpenLayers.Event.observe(element, type, this.eventHandler
            );
            if (msTouch && type.indexOf('touch') === 0) {
                this.addMsTouchListener(element, type, this.eventHandler);
            }
        }
        // disable dragstart in IE so that mousedown/move/up works normally
        OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
    },
    
    /**
     * APIMethod: on
     * Convenience method for registering listeners with a common scope.
     *     Internally, this method calls <register> as shown in the examples
     *     below.
     *
     * Example use:
     * (code)
     * // register a single listener for the "loadstart" event
     * events.on({"loadstart": loadStartListener});
     *
     * // this is equivalent to the following
     * events.register("loadstart", undefined, loadStartListener);
     *
     * // register multiple listeners to be called with the same `this` object
     * events.on({
     *     "loadstart": loadStartListener,
     *     "loadend": loadEndListener,
     *     scope: object
     * });
     *
     * // this is equivalent to the following
     * events.register("loadstart", object, loadStartListener);
     * events.register("loadend", object, loadEndListener);
     * (end)
     *
     * Parameters:
     *  object - {Object}     
     */
    on: function(object) {
        for(var type in object) {
            if(type != "scope" && object.hasOwnProperty(type)) {
                this.register(type, object.scope, object[type]);
            }
        }
    },

    /**
     * APIMethod: register
     * Register an event on the events object.
     *
     * When the event is triggered, the 'func' function will be called, in the
     * context of 'obj'. Imagine we were to register an event, specifying an 
     * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the 
     * context in the callback function will be our Bounds object. This means
     * that within our callback function, we can access the properties and 
     * methods of the Bounds object through the "this" variable. So our 
     * callback could execute something like: 
     * :    leftStr = "Left: " + this.left;
     *   
     *                   or
     *  
     * :    centerStr = "Center: " + this.getCenterLonLat();
     *
     * Parameters:
     * type - {String} Name of the event to register
     * obj - {Object} The object to bind the context to for the callback#.
     *     If no object is specified, default is the Events's 'object' property.
     * func - {Function} The callback function. If no callback is 
     *     specified, this function does nothing.
     * priority - {Boolean|Object} If true, adds the new listener to the
     *     *front* of the events queue instead of to the end.
     *
     * Valid options for priority:
     * extension - {Boolean} If true, then the event will be registered as
     *     extension event. Extension events are handled before all other
     *     events.
     */
    register: function (type, obj, func, priority) {
        if (type in OpenLayers.Events && !this.extensions[type]) {
            this.extensions[type] = new OpenLayers.Events[type](this);
        }
        if (func != null) {
            if (obj == null)  {
                obj = this.object;
            }
            var listeners = this.listeners[type];
            if (!listeners) {
                listeners = [];
                this.listeners[type] = listeners;
                this.extensionCount[type] = 0;
            }
            var listener = {obj: obj, func: func};
            if (priority) {
                listeners.splice(this.extensionCount[type], 0, listener);
                if (typeof priority === "object" && priority.extension) {
                    this.extensionCount[type]++;
                }
            } else {
                listeners.push(listener);
            }
        }
    },

    /**
     * APIMethod: registerPriority
     * Same as register() but adds the new listener to the *front* of the
     *     events queue instead of to the end.
     *    
     *     TODO: get rid of this in 3.0 - Decide whether listeners should be 
     *     called in the order they were registered or in reverse order.
     *
     *
     * Parameters:
     * type - {String} Name of the event to register
     * obj - {Object} The object to bind the context to for the callback#.
     *                If no object is specified, default is the Events's 
     *                'object' property.
     * func - {Function} The callback function. If no callback is 
     *                   specified, this function does nothing.
     */
    registerPriority: function (type, obj, func) {
        this.register(type, obj, func, true);
    },
    
    /**
     * APIMethod: un
     * Convenience method for unregistering listeners with a common scope.
     *     Internally, this method calls <unregister> as shown in the examples
     *     below.
     *
     * Example use:
     * (code)
     * // unregister a single listener for the "loadstart" event
     * events.un({"loadstart": loadStartListener});
     *
     * // this is equivalent to the following
     * events.unregister("loadstart", undefined, loadStartListener);
     *
     * // unregister multiple listeners with the same `this` object
     * events.un({
     *     "loadstart": loadStartListener,
     *     "loadend": loadEndListener,
     *     scope: object
     * });
     *
     * // this is equivalent to the following
     * events.unregister("loadstart", object, loadStartListener);
     * events.unregister("loadend", object, loadEndListener);
     * (end)
     */
    un: function(object) {
        for(var type in object) {
            if(type != "scope" && object.hasOwnProperty(type)) {
                this.unregister(type, object.scope, object[type]);
            }
        }
    },

    /**
     * APIMethod: unregister
     *
     * Parameters:
     * type - {String} 
     * obj - {Object} If none specified, defaults to this.object
     * func - {Function} 
     */
    unregister: function (type, obj, func) {
        if (obj == null)  {
            obj = this.object;
        }
        var listeners = this.listeners[type];
        if (listeners != null) {
            for (var i=0, len=listeners.length; i<len; i++) {
                if (listeners[i].obj == obj && listeners[i].func == func) {
                    listeners.splice(i, 1);
                    break;
                }
            }
        }
    },

    /** 
     * Method: remove
     * Remove all listeners for a given event type. If type is not registered,
     *     does nothing.
     *
     * Parameters:
     * type - {String} 
     */
    remove: function(type) {
        if (this.listeners[type] != null) {
            this.listeners[type] = [];
        }
    },

    /**
     * APIMethod: triggerEvent
     * Trigger a specified registered event.  
     * 
     * Parameters:
     * type - {String} 
     * evt - {Event || Object} will be passed to the listeners.
     *
     * Returns:
     * {Boolean} The last listener return.  If a listener returns false, the
     *     chain of listeners will stop getting called.
     */
    triggerEvent: function (type, evt) {
        var listeners = this.listeners[type];

        // fast path
        if(!listeners || listeners.length == 0) {
            return undefined;
        }

        // prep evt object with object & div references
        if (evt == null) {
            evt = {};
        }
        evt.object = this.object;
        evt.element = this.element;
        if(!evt.type) {
            evt.type = type;
        }
    
        // execute all callbacks registered for specified type
        // get a clone of the listeners array to
        // allow for splicing during callbacks
        listeners = listeners.slice();
        var continueChain;
        for (var i=0, len=listeners.length; i<len; i++) {
            var callback = listeners[i];
            // bind the context to callback.obj
            continueChain = callback.func.apply(callback.obj, [evt]);

            if ((continueChain != undefined) && (continueChain == false)) {
                // if callback returns false, execute no more callbacks.
                break;
            }
        }
        // don't fall through to other DOM elements
        if (!this.fallThrough) {           
            OpenLayers.Event.stop(evt, true);
        }
        return continueChain;
    },

    /**
     * Method: handleBrowserEvent
     * Basically just a wrapper to the triggerEvent() function, but takes 
     *     care to set a property 'xy' on the event with the current mouse 
     *     position.
     *
     * Parameters:
     * evt - {Event} 
     */
    handleBrowserEvent: function (evt) {
        var type = evt.type, listeners = this.listeners[type];
        if(!listeners || listeners.length == 0) {
            // noone's listening, bail out
            return;
        }
        // add clientX & clientY to all events - corresponds to average x, y
        var touches = evt.touches;
        if (touches && touches[0]) {
            var x = 0;
            var y = 0;
            var num = touches.length;
            var touch;
            for (var i=0; i<num; ++i) {
                touch = this.getTouchClientXY(touches[i]);
                x += touch.clientX;
                y += touch.clientY;
            }
            evt.clientX = x / num;
            evt.clientY = y / num;
        }
        if (this.includeXY) {
            evt.xy = this.getMousePosition(evt);
        } 
        this.triggerEvent(type, evt);
    },
    
    /**
     * Method: getTouchClientXY
     * WebKit has a few bugs for clientX/clientY. This method detects them
     * and calculate the correct values.
     *
     * Parameters:
     * evt - {Touch} a Touch object from a TouchEvent
     * 
     * Returns:
     * {Object} An object with only clientX and clientY properties with the
     * calculated values.
     */
    getTouchClientXY: function (evt) {
        // olMochWin is to override window, used for testing
        var win = window.olMockWin || window,
            winPageX = win.pageXOffset,
            winPageY = win.pageYOffset,
            x = evt.clientX,
            y = evt.clientY;
        
        if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
            evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
            // iOS4 include scroll offset in clientX/Y
            x = x - winPageX;
            y = y - winPageY;
        } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
            // Some Android browsers have totally bogus values for clientX/Y
            // when scrolling/zooming a page
            x = evt.pageX - winPageX;
            y = evt.pageY - winPageY;
        }
        
        evt.olClientX = x;
        evt.olClientY = y;
        
        return {
            clientX: x,
            clientY: y
        };
    },
    
    /**
     * APIMethod: clearMouseCache
     * Clear cached data about the mouse position. This should be called any 
     *     time the element that events are registered on changes position 
     *     within the page.
     */
    clearMouseCache: function() { 
        this.element.scrolls = null;
        this.element.lefttop = null;
        this.element.offsets = null;
    },      

    /**
     * Method: getMousePosition
     * 
     * Parameters:
     * evt - {Event} 
     * 
     * Returns:
     * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
     *                      for offsets
     */
    getMousePosition: function (evt) {
        if (!this.includeXY) {
            this.clearMouseCache();
        } else if (!this.element.hasScrollEvent) {
            OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
            this.element.hasScrollEvent = true;
        }
        
        if (!this.element.scrolls) {
            var viewportElement = OpenLayers.Util.getViewportElement();
            this.element.scrolls = [
                window.pageXOffset || viewportElement.scrollLeft,
                window.pageYOffset || viewportElement.scrollTop
            ];
        }

        if (!this.element.lefttop) {
            this.element.lefttop = [
                (document.documentElement.clientLeft || 0),
                (document.documentElement.clientTop  || 0)
            ];
        }
        
        if (!this.element.offsets) {
            this.element.offsets = OpenLayers.Util.pagePosition(this.element);
        }

        return new OpenLayers.Pixel(
            (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
                         - this.element.lefttop[0], 
            (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
                         - this.element.lefttop[1]
        ); 
    },

    /**
     * Method: addMsTouchListener
     *
     * Parameters:
     * element - {DOMElement} The DOM element to register the listener on
     * type - {String} The event type
     * handler - {Function} the handler
     */
    addMsTouchListener: function (element, type, handler) {
        var eventHandler = this.eventHandler;
        var touches = this._msTouches;

        function msHandler(evt) {
            handler(OpenLayers.Util.applyDefaults({
                stopPropagation: function() {
                    for (var i=touches.length-1; i>=0; --i) {
                        touches[i].stopPropagation();
                    }
                },
                preventDefault: function() {
                    for (var i=touches.length-1; i>=0; --i) {
                        touches[i].preventDefault();
                    }
                },
                type: type
            }, evt));
        }

        switch (type) {
            case 'touchstart':
                return this.addMsTouchListenerStart(element, type, msHandler);
            case 'touchend':
                return this.addMsTouchListenerEnd(element, type, msHandler);
            case 'touchmove':
                return this.addMsTouchListenerMove(element, type, msHandler);
            default:
                throw 'Unknown touch event type';
        }
    },

    /**
     * Method: addMsTouchListenerStart
     *
     * Parameters:
     * element - {DOMElement} The DOM element to register the listener on
     * type - {String} The event type
     * handler - {Function} the handler
     */
    addMsTouchListenerStart: function(element, type, handler) {
        var touches = this._msTouches;

        var cb = function(e) {

            var alreadyInArray = false;
            for (var i=0, ii=touches.length; i<ii; ++i) {
                if (touches[i].pointerId == e.pointerId) {
                    alreadyInArray = true;
                    break;
                }
            }
            if (!alreadyInArray) {
                touches.push(e);
            }

            e.touches = touches.slice();
            handler(e);
        };

        OpenLayers.Event.observe(element, 'MSPointerDown', cb);

        // Need to also listen for end events to keep the _msTouches list
        // accurate
        var internalCb = function(e) {
            for (var i=0, ii=touches.length; i<ii; ++i) {
                if (touches[i].pointerId == e.pointerId) {
                    touches.splice(i, 1);
                    break;
                }
            }
        };
        OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
    },

    /**
     * Method: addMsTouchListenerMove
     *
     * Parameters:
     * element - {DOMElement} The DOM element to register the listener on
     * type - {String} The event type
     * handler - {Function} the handler
     */
    addMsTouchListenerMove: function (element, type, handler) {
        var touches = this._msTouches;
        var cb = function(e) {

            //Don't fire touch moves when mouse isn't down
            if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
                return;
            }

            if (touches.length == 1 && touches[0].pageX == e.pageX &&
                    touches[0].pageY == e.pageY) {
                // don't trigger event when pointer has not moved
                return;
            }
            for (var i=0, ii=touches.length; i<ii; ++i) {
                if (touches[i].pointerId == e.pointerId) {
                    touches[i] = e;
                    break;
                }
            }

            e.touches = touches.slice();
            handler(e);
        };

        OpenLayers.Event.observe(element, 'MSPointerMove', cb);
    },

    /**
     * Method: addMsTouchListenerEnd
     *
     * Parameters:
     * element - {DOMElement} The DOM element to register the listener on
     * type - {String} The event type
     * handler - {Function} the handler
     */
    addMsTouchListenerEnd: function (element, type, handler) {
        var touches = this._msTouches;

        var cb = function(e) {

            for (var i=0, ii=touches.length; i<ii; ++i) {
                if (touches[i].pointerId == e.pointerId) {
                    touches.splice(i, 1);
                    break;
                }
            }
            
            e.touches = touches.slice();
            handler(e);
        };

        OpenLayers.Event.observe(element, 'MSPointerUp', cb);
    },

    CLASS_NAME: "OpenLayers.Events"
});
/* ======================================================================
    OpenLayers/Events/buttonclick.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Events.js
 */

/**
 * Class: OpenLayers.Events.buttonclick
 * Extension event type for handling buttons on top of a dom element. This
 *     event type fires "buttonclick" on its <target> when a button was
 *     clicked. Buttons are detected by the "olButton" class.
 *
 * This event type makes sure that button clicks do not interfere with other
 *     events that are registered on the same <element>.
 *
 * Event types provided by this extension:
 * - *buttonclick* Triggered when a button is clicked. Listeners receive an
 *     object with a *buttonElement* property referencing the dom element of
 *     the clicked button, and an *buttonXY* property with the click position
 *     relative to the button.
 */
OpenLayers.Events.buttonclick = OpenLayers.Class({
    
    /**
     * Property: target
     * {<OpenLayers.Events>} The events instance that the buttonclick event will
     * be triggered on.
     */
    target: null,
    
    /**
     * Property: events
     * {Array} Events to observe and conditionally stop from propagating when
     *     an element with the olButton class (or its olAlphaImg child) is
     *     clicked.
     */
    events: [
        'mousedown', 'mouseup', 'click', 'dblclick',
        'touchstart', 'touchmove', 'touchend', 'keydown'
    ],
    
    /**
     * Property: startRegEx
     * {RegExp} Regular expression to test Event.type for events that start
     *     a buttonclick sequence.
     */
    startRegEx: /^mousedown|touchstart$/,

    /**
     * Property: cancelRegEx
     * {RegExp} Regular expression to test Event.type for events that cancel
     *     a buttonclick sequence.
     */
    cancelRegEx: /^touchmove$/,

    /**
     * Property: completeRegEx
     * {RegExp} Regular expression to test Event.type for events that complete
     *     a buttonclick sequence.
     */
    completeRegEx: /^mouseup|touchend$/,
    
    /**
     * Property: startEvt
     * {Event} The event that started the click sequence
     */
    
    /**
     * Constructor: OpenLayers.Events.buttonclick
     * Construct a buttonclick event type. Applications are not supposed to
     *     create instances of this class - they are created on demand by
     *     <OpenLayers.Events> instances.
     *
     * Parameters:
     * target - {<OpenLayers.Events>} The events instance that the buttonclick
     *     event will be triggered on.
     */
    initialize: function(target) {
        this.target = target;
        for (var i=this.events.length-1; i>=0; --i) {
            this.target.register(this.events[i], this, this.buttonClick, {
                extension: true
            });
        }
    },
    
    /**
     * Method: destroy
     */
    destroy: function() {
        for (var i=this.events.length-1; i>=0; --i) {
            this.target.unregister(this.events[i], this, this.buttonClick);
        }
        delete this.target;
    },

    /**
     * Method: getPressedButton
     * Get the pressed button, if any. Returns undefined if no button
     * was pressed.
     *
     * Arguments:
     * element - {DOMElement} The event target.
     *
     * Returns:
     * {DOMElement} The button element, or undefined.
     */
    getPressedButton: function(element) {
        var depth = 3, // limit the search depth
            button;
        do {
            if(OpenLayers.Element.hasClass(element, "olButton")) {
                // hit!
                button = element;
                break;
            }
            element = element.parentNode;
        } while(--depth > 0 && element);
        return button;
    },
    
    /**
     * Method: ignore
     * Check for event target elements that should be ignored by OpenLayers.
     *
     * Parameters:
     * element - {DOMElement} The event target.
     */
    ignore: function(element) {
        var depth = 3,
            ignore = false;
        do {
            if (element.nodeName.toLowerCase() === 'a') {
                ignore = true;
                break;
            }
            element = element.parentNode;
        } while (--depth > 0 && element);
        return ignore;
    },

    /**
     * Method: buttonClick
     * Check if a button was clicked, and fire the buttonclick event
     *
     * Parameters:
     * evt - {Event}
     */
    buttonClick: function(evt) {
        var propagate = true,
            element = OpenLayers.Event.element(evt);
        if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
            // was a button pressed?
            var button = this.getPressedButton(element);
            if (button) {
                if (evt.type === "keydown") {
                    switch (evt.keyCode) {
                    case OpenLayers.Event.KEY_RETURN:
                    case OpenLayers.Event.KEY_SPACE:
                        this.target.triggerEvent("buttonclick", {
                            buttonElement: button
                        });
                        OpenLayers.Event.stop(evt);
                        propagate = false;
                        break;
                    }
                } else if (this.startEvt) {
                    if (this.completeRegEx.test(evt.type)) {
                        var pos = OpenLayers.Util.pagePosition(button);
                        var viewportElement = OpenLayers.Util.getViewportElement();
                        var scrollTop = window.pageYOffset || viewportElement.scrollTop;
                        var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
                        pos[0] = pos[0] - scrollLeft;
                        pos[1] = pos[1] - scrollTop;
                        
                        this.target.triggerEvent("buttonclick", {
                            buttonElement: button,
                            buttonXY: {
                                x: this.startEvt.clientX - pos[0],
                                y: this.startEvt.clientY - pos[1]
                            }
                        });
                    }
                    if (this.cancelRegEx.test(evt.type)) {
                        delete this.startEvt;
                    }
                    OpenLayers.Event.stop(evt);
                    propagate = false;
                }
                if (this.startRegEx.test(evt.type)) {
                    this.startEvt = evt;
                    OpenLayers.Event.stop(evt);
                    propagate = false;
                }
            } else {
                propagate = !this.ignore(OpenLayers.Event.element(evt));
                delete this.startEvt;
            }
        }
        return propagate;
    }
    
});
/* ======================================================================
    OpenLayers/Util/vendorPrefix.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/SingleFile.js
 */

OpenLayers.Util = OpenLayers.Util || {};
/**
 * Namespace: OpenLayers.Util.vendorPrefix
 * A collection of utility functions to detect vendor prefixed features
 */
OpenLayers.Util.vendorPrefix = (function() {
    "use strict";
    
    var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"],
        divStyle = document.createElement("div").style,
        cssCache = {},
        jsCache = {};

    
    /**
     * Function: domToCss
     * Converts a upper camel case DOM style property name to a CSS property
     *      i.e. transformOrigin -> transform-origin
     *      or   WebkitTransformOrigin -> -webkit-transform-origin
     *
     * Parameters:
     * prefixedDom - {String} The property to convert
     *
     * Returns:
     * {String} The CSS property
     */
    function domToCss(prefixedDom) {
        if (!prefixedDom) { return null; }
        return prefixedDom.
            replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }).
            replace(/^ms-/, "-ms-");
    }

    /**
     * APIMethod: css
     * Detect which property is used for a CSS property
     *
     * Parameters:
     * property - {String} The standard (unprefixed) CSS property name
     *
     * Returns:
     * {String} The standard CSS property, prefixed property or null if not
     *          supported
     */
    function css(property) {
        if (cssCache[property] === undefined) {
            var domProperty = property.
                replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); });
            var prefixedDom = style(domProperty);
            cssCache[property] = domToCss(prefixedDom);
        }
        return cssCache[property];
    }

    /**
     * APIMethod: js
     * Detect which property is used for a JS property/method
     *
     * Parameters:
     * obj - {Object} The object to test on
     * property - {String} The standard (unprefixed) JS property name
     *
     * Returns:
     * {String} The standard JS property, prefixed property or null if not
     *          supported
     */
    function js(obj, property) {
        if (jsCache[property] === undefined) {
            var tmpProp,
                i = 0,
                l = VENDOR_PREFIXES.length,
                prefix,
                isStyleObj = (typeof obj.cssText !== "undefined");

            jsCache[property] = null;
            for(; i<l; i++) {
                prefix = VENDOR_PREFIXES[i];
                if(prefix) {
                    if (!isStyleObj) {
                        // js prefix should be lower-case, while style
                        // properties have upper case on first character
                        prefix = prefix.toLowerCase();
                    }
                    tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
                } else {
                    tmpProp = property;
                }

                if(obj[tmpProp] !== undefined) {
                    jsCache[property] = tmpProp;
                    break;
                }
            }
        }
        return jsCache[property];
    }
    
    /**
     * APIMethod: style
     * Detect which property is used for a DOM style property
     *
     * Parameters:
     * property - {String} The standard (unprefixed) style property name
     *
     * Returns:
     * {String} The standard style property, prefixed property or null if not
     *          supported
     */
    function style(property) {
        return js(divStyle, property);
    }
    
    return {
        css:      css,
        js:       js,
        style:    style,
        
        // used for testing
        cssCache:       cssCache,
        jsCache:        jsCache
    };
}());
/* ======================================================================
    OpenLayers/Animation.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/SingleFile.js
 * @requires OpenLayers/Util/vendorPrefix.js
 */

/**
 * Namespace: OpenLayers.Animation
 * A collection of utility functions for executing methods that repaint a 
 *     portion of the browser window.  These methods take advantage of the
 *     browser's scheduled repaints where requestAnimationFrame is available.
 */
OpenLayers.Animation = (function(window) {
    
    /**
     * Property: isNative
     * {Boolean} true if a native requestAnimationFrame function is available
     */
    var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
    var isNative = !!(requestAnimationFrame);
    
    /**
     * Function: requestFrame
     * Schedule a function to be called at the next available animation frame.
     *     Uses the native method where available.  Where requestAnimationFrame is
     *     not available, setTimeout will be called with a 16ms delay.
     *
     * Parameters:
     * callback - {Function} The function to be called at the next animation frame.
     * element - {DOMElement} Optional element that visually bounds the animation.
     */
    var requestFrame = (function() {
        var request = window[requestAnimationFrame] ||
            function(callback, element) {
                window.setTimeout(callback, 16);
            };
        // bind to window to avoid illegal invocation of native function
        return function(callback, element) {
            request.apply(window, [callback, element]);
        };
    })();
    
    // private variables for animation loops
    var counter = 0;
    var loops = {};
    
    /**
     * Function: start
     * Executes a method with <requestFrame> in series for some 
     *     duration.
     *
     * Parameters:
     * callback - {Function} The function to be called at the next animation frame.
     * duration - {Number} Optional duration for the loop.  If not provided, the
     *     animation loop will execute indefinitely.
     * element - {DOMElement} Optional element that visually bounds the animation.
     *
     * Returns:
     * {Number} Identifier for the animation loop.  Used to stop animations with
     *     <stop>.
     */
    function start(callback, duration, element) {
        duration = duration > 0 ? duration : Number.POSITIVE_INFINITY;
        var id = ++counter;
        var start = +new Date;
        loops[id] = function() {
            if (loops[id] && +new Date - start <= duration) {
                callback();
                if (loops[id]) {
                    requestFrame(loops[id], element);
                }
            } else {
                delete loops[id];
            }
        };
        requestFrame(loops[id], element);
        return id;
    }
    
    /**
     * Function: stop
     * Terminates an animation loop started with <start>.
     *
     * Parameters:
     * id - {Number} Identifier returned from <start>.
     */
    function stop(id) {
        delete loops[id];
    }
    
    return {
        isNative: isNative,
        requestFrame: requestFrame,
        start: start,
        stop: stop
    };
    
})(window);
/* ======================================================================
    OpenLayers/Tween.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Animation.js
 */

/**
 * Namespace: OpenLayers.Tween
 */
OpenLayers.Tween = OpenLayers.Class({
    
    /**
     * APIProperty: easing
     * {<OpenLayers.Easing>(Function)} Easing equation used for the animation
     *     Defaultly set to OpenLayers.Easing.Expo.easeOut
     */
    easing: null,
    
    /**
     * APIProperty: begin
     * {Object} Values to start the animation with
     */
    begin: null,
    
    /**
     * APIProperty: finish
     * {Object} Values to finish the animation with
     */
    finish: null,
    
    /**
     * APIProperty: duration
     * {int} duration of the tween (number of steps)
     */
    duration: null,
    
    /**
     * APIProperty: callbacks
     * {Object} An object with start, eachStep and done properties whose values
     *     are functions to be call during the animation. They are passed the
     *     current computed value as argument.
     */
    callbacks: null,
    
    /**
     * Property: time
     * {int} Step counter
     */
    time: null,
    
    /**
     * APIProperty: minFrameRate
     * {Number} The minimum framerate for animations in frames per second. After
     * each step, the time spent in the animation is compared to the calculated
     * time at this frame rate. If the animation runs longer than the calculated
     * time, the next step is skipped. Default is 30.
     */
    minFrameRate: null,

    /**
     * Property: startTime
     * {Number} The timestamp of the first execution step. Used for skipping
     * frames
     */
    startTime: null,
    
    /**
     * Property: animationId
     * {int} Loop id returned by OpenLayers.Animation.start
     */
    animationId: null,
    
    /**
     * Property: playing
     * {Boolean} Tells if the easing is currently playing
     */
    playing: false,
    
    /** 
     * Constructor: OpenLayers.Tween
     * Creates a Tween.
     *
     * Parameters:
     * easing - {<OpenLayers.Easing>(Function)} easing function method to use
     */ 
    initialize: function(easing) {
        this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut;
    },
    
    /**
     * APIMethod: start
     * Plays the Tween, and calls the callback method on each step
     * 
     * Parameters:
     * begin - {Object} values to start the animation with
     * finish - {Object} values to finish the animation with
     * duration - {int} duration of the tween (number of steps)
     * options - {Object} hash of options (callbacks (start, eachStep, done),
     *     minFrameRate)
     */
    start: function(begin, finish, duration, options) {
        this.playing = true;
        this.begin = begin;
        this.finish = finish;
        this.duration = duration;
        this.callbacks = options.callbacks;
        this.minFrameRate = options.minFrameRate || 30;
        this.time = 0;
        this.startTime = new Date().getTime();
        OpenLayers.Animation.stop(this.animationId);
        this.animationId = null;
        if (this.callbacks && this.callbacks.start) {
            this.callbacks.start.call(this, this.begin);
        }
        this.animationId = OpenLayers.Animation.start(
            OpenLayers.Function.bind(this.play, this)
        );
    },
    
    /**
     * APIMethod: stop
     * Stops the Tween, and calls the done callback
     *     Doesn't do anything if animation is already finished
     */
    stop: function() {
        if (!this.playing) {
            return;
        }
        
        if (this.callbacks && this.callbacks.done) {
            this.callbacks.done.call(this, this.finish);
        }
        OpenLayers.Animation.stop(this.animationId);
        this.animationId = null;
        this.playing = false;
    },
    
    /**
     * Method: play
     * Calls the appropriate easing method
     */
    play: function() {
        var value = {};
        for (var i in this.begin) {
            var b = this.begin[i];
            var f = this.finish[i];
            if (b == null || f == null || isNaN(b) || isNaN(f)) {
                throw new TypeError('invalid value for Tween');
            }

            var c = f - b;
            value[i] = this.easing.apply(this, [this.time, b, c, this.duration]);
        }
        this.time++;
        
        if (this.callbacks && this.callbacks.eachStep) {
            // skip frames if frame rate drops below threshold
            if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) {
                this.callbacks.eachStep.call(this, value);
            }
        }
        
        if (this.time > this.duration) {
            this.stop();
        }
    },
    
    /**
     * Create empty functions for all easing methods.
     */
    CLASS_NAME: "OpenLayers.Tween"
});

/**
 * Namespace: OpenLayers.Easing
 * 
 * Credits:
 *      Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>
 */
OpenLayers.Easing = {
    /**
     * Create empty functions for all easing methods.
     */
    CLASS_NAME: "OpenLayers.Easing"
};

/**
 * Namespace: OpenLayers.Easing.Linear
 */
OpenLayers.Easing.Linear = {
    
    /**
     * Function: easeIn
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeIn: function(t, b, c, d) {
        return c*t/d + b;
    },
    
    /**
     * Function: easeOut
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeOut: function(t, b, c, d) {
        return c*t/d + b;
    },
    
    /**
     * Function: easeInOut
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeInOut: function(t, b, c, d) {
        return c*t/d + b;
    },

    CLASS_NAME: "OpenLayers.Easing.Linear"
};

/**
 * Namespace: OpenLayers.Easing.Expo
 */
OpenLayers.Easing.Expo = {
    
    /**
     * Function: easeIn
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeIn: function(t, b, c, d) {
        return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
    },
    
    /**
     * Function: easeOut
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeOut: function(t, b, c, d) {
        return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
    },
    
    /**
     * Function: easeInOut
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeInOut: function(t, b, c, d) {
        if (t==0) return b;
        if (t==d) return b+c;
        if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
        return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
    },

    CLASS_NAME: "OpenLayers.Easing.Expo"
};

/**
 * Namespace: OpenLayers.Easing.Quad
 */
OpenLayers.Easing.Quad = {
    
    /**
     * Function: easeIn
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeIn: function(t, b, c, d) {
        return c*(t/=d)*t + b;
    },
    
    /**
     * Function: easeOut
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeOut: function(t, b, c, d) {
        return -c *(t/=d)*(t-2) + b;
    },
    
    /**
     * Function: easeInOut
     * 
     * Parameters:
     * t - {Float} time
     * b - {Float} beginning position
     * c - {Float} total change
     * d - {Float} duration of the transition
     *
     * Returns:
     * {Float}
     */
    easeInOut: function(t, b, c, d) {
        if ((t/=d/2) < 1) return c/2*t*t + b;
        return -c/2 * ((--t)*(t-2) - 1) + b;
    },

    CLASS_NAME: "OpenLayers.Easing.Quad"
};
/* ======================================================================
    OpenLayers/Projection.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 */

/**
 * Namespace: OpenLayers.Projection
 * Methods for coordinate transforms between coordinate systems.  By default,
 *     OpenLayers ships with the ability to transform coordinates between
 *     geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.)
 *     coordinate reference systems.  See the <transform> method for details
 *     on usage.
 *
 * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
 *     library.  If the proj4js library is included, the <transform> method 
 *     will work between any two coordinate reference systems with proj4js 
 *     definitions.
 *
 * If the proj4js library is not included, or if you wish to allow transforms
 *     between arbitrary coordinate reference systems, use the <addTransform>
 *     method to register a custom transform method.
 */
OpenLayers.Projection = OpenLayers.Class({

    /**
     * Property: proj
     * {Object} Proj4js.Proj instance.
     */
    proj: null,
    
    /**
     * Property: projCode
     * {String}
     */
    projCode: null,
    
    /**
     * Property: titleRegEx
     * {RegExp} regular expression to strip the title from a proj4js definition
     */
    titleRegEx: /\+title=[^\+]*/,

    /**
     * Constructor: OpenLayers.Projection
     * This class offers several methods for interacting with a wrapped 
     *     pro4js projection object. 
     *
     * Parameters:
     * projCode - {String} A string identifying the Well Known Identifier for
     *    the projection.
     * options - {Object} An optional object to set additional properties
     *     on the projection.
     *
     * Returns:
     * {<OpenLayers.Projection>} A projection object.
     */
    initialize: function(projCode, options) {
        OpenLayers.Util.extend(this, options);
        this.projCode = projCode;
        if (typeof Proj4js == "object") {
            this.proj = new Proj4js.Proj(projCode);
        }
    },
    
    /**
     * APIMethod: getCode
     * Get the string SRS code.
     *
     * Returns:
     * {String} The SRS code.
     */
    getCode: function() {
        return this.proj ? this.proj.srsCode : this.projCode;
    },
   
    /**
     * APIMethod: getUnits
     * Get the units string for the projection -- returns null if 
     *     proj4js is not available.
     *
     * Returns:
     * {String} The units abbreviation.
     */
    getUnits: function() {
        return this.proj ? this.proj.units : null;
    },

    /**
     * Method: toString
     * Convert projection to string (getCode wrapper).
     *
     * Returns:
     * {String} The projection code.
     */
    toString: function() {
        return this.getCode();
    },

    /**
     * Method: equals
     * Test equality of two projection instances.  Determines equality based
     *     soley on the projection code.
     *
     * Returns:
     * {Boolean} The two projections are equivalent.
     */
    equals: function(projection) {
        var p = projection, equals = false;
        if (p) {
            if (!(p instanceof OpenLayers.Projection)) {
                p = new OpenLayers.Projection(p);
            }
            if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) {
                equals = this.proj.defData.replace(this.titleRegEx, "") ==
                    p.proj.defData.replace(this.titleRegEx, "");
            } else if (p.getCode) {
                var source = this.getCode(), target = p.getCode();
                equals = source == target ||
                    !!OpenLayers.Projection.transforms[source] &&
                    OpenLayers.Projection.transforms[source][target] ===
                        OpenLayers.Projection.nullTransform;
            }
        }
        return equals;   
    },

    /* Method: destroy
     * Destroy projection object.
     */
    destroy: function() {
        delete this.proj;
        delete this.projCode;
    },
    
    CLASS_NAME: "OpenLayers.Projection" 
});     

/**
 * Property: transforms
 * {Object} Transforms is an object, with from properties, each of which may
 * have a to property. This allows you to define projections without 
 * requiring support for proj4js to be included.
 *
 * This object has keys which correspond to a 'source' projection object.  The
 * keys should be strings, corresponding to the projection.getCode() value.
 * Each source projection object should have a set of destination projection
 * keys included in the object. 
 * 
 * Each value in the destination object should be a transformation function,
 * where the function is expected to be passed an object with a .x and a .y
 * property.  The function should return the object, with the .x and .y
 * transformed according to the transformation function.
 *
 * Note - Properties on this object should not be set directly.  To add a
 *     transform method to this object, use the <addTransform> method.  For an
 *     example of usage, see the OpenLayers.Layer.SphericalMercator file.
 */
OpenLayers.Projection.transforms = {};

/**
 * APIProperty: defaults
 * {Object} Defaults for the SRS codes known to OpenLayers (currently
 * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857,
 * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units,
 * maxExtent (the validity extent for the SRS) and yx (true if this SRS is
 * known to have a reverse axis order).
 */
OpenLayers.Projection.defaults = {
    "EPSG:4326": {
        units: "degrees",
        maxExtent: [-180, -90, 180, 90],
        yx: true
    },
    "CRS:84": {
        units: "degrees",
        maxExtent: [-180, -90, 180, 90]
    },
    "EPSG:900913": {
        units: "m",
        maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
    }
};

/**
 * APIMethod: addTransform
 * Set a custom transform method between two projections.  Use this method in
 *     cases where the proj4js lib is not available or where custom projections
 *     need to be handled.
 *
 * Parameters:
 * from - {String} The code for the source projection
 * to - {String} the code for the destination projection
 * method - {Function} A function that takes a point as an argument and
 *     transforms that point from the source to the destination projection
 *     in place.  The original point should be modified.
 */
OpenLayers.Projection.addTransform = function(from, to, method) {
    if (method === OpenLayers.Projection.nullTransform) {
        var defaults = OpenLayers.Projection.defaults[from];
        if (defaults && !OpenLayers.Projection.defaults[to]) {
            OpenLayers.Projection.defaults[to] = defaults;
        }
    }
    if(!OpenLayers.Projection.transforms[from]) {
        OpenLayers.Projection.transforms[from] = {};
    }
    OpenLayers.Projection.transforms[from][to] = method;
};

/**
 * APIMethod: transform
 * Transform a point coordinate from one projection to another.  Note that
 *     the input point is transformed in place.
 * 
 * Parameters:
 * point - {<OpenLayers.Geometry.Point> | Object} An object with x and y
 *     properties representing coordinates in those dimensions.
 * source - {OpenLayers.Projection} Source map coordinate system
 * dest - {OpenLayers.Projection} Destination map coordinate system
 *
 * Returns:
 * point - {object} A transformed coordinate.  The original point is modified.
 */
OpenLayers.Projection.transform = function(point, source, dest) {
    if (source && dest) {
        if (!(source instanceof OpenLayers.Projection)) {
            source = new OpenLayers.Projection(source);
        }
        if (!(dest instanceof OpenLayers.Projection)) {
            dest = new OpenLayers.Projection(dest);
        }
        if (source.proj && dest.proj) {
            point = Proj4js.transform(source.proj, dest.proj, point);
        } else {
            var sourceCode = source.getCode();
            var destCode = dest.getCode();
            var transforms = OpenLayers.Projection.transforms;
            if (transforms[sourceCode] && transforms[sourceCode][destCode]) {
                transforms[sourceCode][destCode](point);
            }
        }
    }
    return point;
};

/**
 * APIFunction: nullTransform
 * A null transformation - useful for defining projection aliases when
 * proj4js is not available:
 *
 * (code)
 * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913",
 *     OpenLayers.Projection.nullTransform);
 * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857",
 *     OpenLayers.Projection.nullTransform);
 * (end)
 */
OpenLayers.Projection.nullTransform = function(point) {
    return point;
};

/**
 * Note: Transforms for web mercator <-> geographic
 * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100.
 * OpenLayers originally started referring to EPSG:900913 as web mercator.
 * The EPSG has declared EPSG:3857 to be web mercator.
 * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as
 * equivalent.  See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084.
 * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and
 * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis
 * order for EPSG:4326. 
 */
(function() {

    var pole = 20037508.34;

    function inverseMercator(xy) {
        xy.x = 180 * xy.x / pole;
        xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2);
        return xy;
    }

    function forwardMercator(xy) {
        xy.x = xy.x * pole / 180;
        var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole;
        xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34));
        return xy;
    }

    function map(base, codes) {
        var add = OpenLayers.Projection.addTransform;
        var same = OpenLayers.Projection.nullTransform;
        var i, len, code, other, j;
        for (i=0, len=codes.length; i<len; ++i) {
            code = codes[i];
            add(base, code, forwardMercator);
            add(code, base, inverseMercator);
            for (j=i+1; j<len; ++j) {
                other = codes[j];
                add(code, other, same);
                add(other, code, same);
            }
        }
    }
    
    // list of equivalent codes for web mercator
    var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"],
        geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
        i;
    for (i=mercator.length-1; i>=0; --i) {
        map(mercator[i], geographic);
    }
    for (i=geographic.length-1; i>=0; --i) {
        map(geographic[i], mercator);
    }

})();
/* ======================================================================
    OpenLayers/Map.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/Util/vendorPrefix.js
 * @requires OpenLayers/Events.js
 * @requires OpenLayers/Tween.js
 * @requires OpenLayers/Projection.js
 */

/**
 * Class: OpenLayers.Map
 * Instances of OpenLayers.Map are interactive maps embedded in a web page.
 * Create a new map with the <OpenLayers.Map> constructor.
 * 
 * On their own maps do not provide much functionality.  To extend a map
 * it's necessary to add controls (<OpenLayers.Control>) and 
 * layers (<OpenLayers.Layer>) to the map. 
 */
OpenLayers.Map = OpenLayers.Class({
    
    /**
     * Constant: Z_INDEX_BASE
     * {Object} Base z-indexes for different classes of thing 
     */
    Z_INDEX_BASE: {
        BaseLayer: 100,
        Overlay: 325,
        Feature: 725,
        Popup: 750,
        Control: 1000
    },

    /**
     * APIProperty: events
     * {<OpenLayers.Events>}
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * map.events.register(type, obj, listener);
     * (end)
     *
     * Listeners will be called with a reference to an event object.  The
     *     properties of this event depends on exactly what happened.
     *
     * All event objects have at least the following properties:
     * object - {Object} A reference to map.events.object.
     * element - {DOMElement} A reference to map.events.element.
     *
     * Browser events have the following additional properties:
     * xy - {<OpenLayers.Pixel>} The pixel location of the event (relative
     *     to the the map viewport).
     *
     * Supported map event types:
     * preaddlayer - triggered before a layer has been added.  The event
     *     object will include a *layer* property that references the layer  
     *     to be added. When a listener returns "false" the adding will be 
     *     aborted.
     * addlayer - triggered after a layer has been added.  The event object
     *     will include a *layer* property that references the added layer.
     * preremovelayer - triggered before a layer has been removed. The event
     *     object will include a *layer* property that references the layer  
     *     to be removed. When a listener returns "false" the removal will be 
     *     aborted.
     * removelayer - triggered after a layer has been removed.  The event
     *     object will include a *layer* property that references the removed
     *     layer.
     * changelayer - triggered after a layer name change, order change,
     *     opacity change, params change, visibility change (actual visibility,
     *     not the layer's visibility property) or attribution change (due to
     *     extent change). Listeners will receive an event object with *layer*
     *     and *property* properties. The *layer* property will be a reference
     *     to the changed layer. The *property* property will be a key to the
     *     changed property (name, order, opacity, params, visibility or
     *     attribution).
     * movestart - triggered after the start of a drag, pan, or zoom. The event
     *     object may include a *zoomChanged* property that tells whether the
     *     zoom has changed.
     * move - triggered after each drag, pan, or zoom
     * moveend - triggered after a drag, pan, or zoom completes
     * zoomend - triggered after a zoom completes
     * mouseover - triggered after mouseover the map
     * mouseout - triggered after mouseout the map
     * mousemove - triggered after mousemove the map
     * changebaselayer - triggered after the base layer changes
     * updatesize - triggered after the <updateSize> method was executed
     */

    /**
     * Property: id
     * {String} Unique identifier for the map
     */
    id: null,
    
    /**
     * Property: fractionalZoom
     * {Boolean} For a base layer that supports it, allow the map resolution
     *     to be set to a value between one of the values in the resolutions
     *     array.  Default is false.
     *
     * When fractionalZoom is set to true, it is possible to zoom to
     *     an arbitrary extent.  This requires a base layer from a source
     *     that supports requests for arbitrary extents (i.e. not cached
     *     tiles on a regular lattice).  This means that fractionalZoom
     *     will not work with commercial layers (Google, Yahoo, VE), layers
     *     using TileCache, or any other pre-cached data sources.
     *
     * If you are using fractionalZoom, then you should also use
     *     <getResolutionForZoom> instead of layer.resolutions[zoom] as the
     *     former works for non-integer zoom levels.
     */
    fractionalZoom: false,
    
    /**
     * APIProperty: events
     * {<OpenLayers.Events>} An events object that handles all 
     *                       events on the map
     */
    events: null,
    
    /**
     * APIProperty: allOverlays
     * {Boolean} Allow the map to function with "overlays" only.  Defaults to
     *     false.  If true, the lowest layer in the draw order will act as
     *     the base layer.  In addition, if set to true, all layers will
     *     have isBaseLayer set to false when they are added to the map.
     *
     * Note:
     * If you set map.allOverlays to true, then you *cannot* use
     *     map.setBaseLayer or layer.setIsBaseLayer.  With allOverlays true,
     *     the lowest layer in the draw layer is the base layer.  So, to change
     *     the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
     *     index to 0.
     */
    allOverlays: false,

    /**
     * APIProperty: div
     * {DOMElement|String} The element that contains the map (or an id for
     *     that element).  If the <OpenLayers.Map> constructor is called
     *     with two arguments, this should be provided as the first argument.
     *     Alternatively, the map constructor can be called with the options
     *     object as the only argument.  In this case (one argument), a
     *     div property may or may not be provided.  If the div property
     *     is not provided, the map can be rendered to a container later
     *     using the <render> method.
     *     
     * Note:
     * If you are calling <render> after map construction, do not use
     *     <maxResolution>  auto.  Instead, divide your <maxExtent> by your
     *     maximum expected dimension.
     */
    div: null,
    
    /**
     * Property: dragging
     * {Boolean} The map is currently being dragged.
     */
    dragging: false,

    /**
     * Property: size
     * {<OpenLayers.Size>} Size of the main div (this.div)
     */
    size: null,
    
    /**
     * Property: viewPortDiv
     * {HTMLDivElement} The element that represents the map viewport
     */
    viewPortDiv: null,

    /**
     * Property: layerContainerOrigin
     * {<OpenLayers.LonLat>} The lonlat at which the later container was
     *                       re-initialized (on-zoom)
     */
    layerContainerOrigin: null,

    /**
     * Property: layerContainerDiv
     * {HTMLDivElement} The element that contains the layers.
     */
    layerContainerDiv: null,

    /**
     * APIProperty: layers
     * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
     */
    layers: null,

    /**
     * APIProperty: controls
     * {Array(<OpenLayers.Control>)} List of controls associated with the map.
     *
     * If not provided in the map options at construction, the map will
     *     by default be given the following controls if present in the build:
     *  - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
     *  - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
     *  - <OpenLayers.Control.ArgParser>
     *  - <OpenLayers.Control.Attribution>
     */
    controls: null,

    /**
     * Property: popups
     * {Array(<OpenLayers.Popup>)} List of popups associated with the map
     */
    popups: null,

    /**
     * APIProperty: baseLayer
     * {<OpenLayers.Layer>} The currently selected base layer.  This determines
     * min/max zoom level, projection, etc.
     */
    baseLayer: null,
    
    /**
     * Property: center
     * {<OpenLayers.LonLat>} The current center of the map
     */
    center: null,

    /**
     * Property: resolution
     * {Float} The resolution of the map.
     */
    resolution: null,

    /**
     * Property: zoom
     * {Integer} The current zoom level of the map
     */
    zoom: 0,    

    /**
     * Property: panRatio
     * {Float} The ratio of the current extent within
     *         which panning will tween.
     */
    panRatio: 1.5,    

    /**
     * APIProperty: options
     * {Object} The options object passed to the class constructor. Read-only.
     */
    options: null,

  // Options

    /**
     * APIProperty: tileSize
     * {<OpenLayers.Size>} Set in the map options to override the default tile
     *                     size for this map.
     */
    tileSize: null,

    /**
     * APIProperty: projection
     * {String} Set in the map options to specify the default projection 
     *          for layers added to this map. When using a projection other than EPSG:4326
     *          (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator),
     *          also set maxExtent, maxResolution or resolutions.  Default is "EPSG:4326".
     *          Note that the projection of the map is usually determined
     *          by that of the current baseLayer (see <baseLayer> and <getProjectionObject>).
     */
    projection: "EPSG:4326",    
        
    /**
     * APIProperty: units
     * {String} The map units.  Possible values are 'degrees' (or 'dd'), 'm', 
     *     'ft', 'km', 'mi', 'inches'.  Normally taken from the projection.
     *     Only required if both map and layers do not define a projection,
     *     or if they define a projection which does not define units
     */
    units: null,

    /**
     * APIProperty: resolutions
     * {Array(Float)} A list of map resolutions (map units per pixel) in 
     *     descending order.  If this is not set in the layer constructor, it 
     *     will be set based on other resolution related properties 
     *     (maxExtent, maxResolution, maxScale, etc.).
     */
    resolutions: null,

    /**
     * APIProperty: maxResolution
     * {Float} Required if you are not displaying the whole world on a tile
     * with the size specified in <tileSize>.
     */
    maxResolution: null,

    /**
     * APIProperty: minResolution
     * {Float}
     */
    minResolution: null,

    /**
     * APIProperty: maxScale
     * {Float}
     */
    maxScale: null,

    /**
     * APIProperty: minScale
     * {Float}
     */
    minScale: null,

    /**
     * APIProperty: maxExtent
     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
     *     should consist of four values (left, bottom, right, top).
     *     The maximum extent for the map.
     *     Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults
     *     (EPSG:4326 or web mercator), maxExtent will be set to the value defined there;
     *     else, defaults to null.
     *     To restrict user panning and zooming of the map, use <restrictedExtent> instead.
     *     The value for <maxExtent> will change calculations for tile URLs.
     */
    maxExtent: null,
    
    /**
     * APIProperty: minExtent
     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
     *     should consist of four values (left, bottom, right, top).
     *     The minimum extent for the map.  Defaults to null.
     */
    minExtent: null,
    
    /**
     * APIProperty: restrictedExtent
     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
     *     should consist of four values (left, bottom, right, top).
     *     Limit map navigation to this extent where possible.
     *     If a non-null restrictedExtent is set, panning will be restricted
     *     to the given bounds.  In addition, zooming to a resolution that
     *     displays more than the restricted extent will center the map
     *     on the restricted extent.  If you wish to limit the zoom level
     *     or resolution, use maxResolution.
     */
    restrictedExtent: null,

    /**
     * APIProperty: numZoomLevels
     * {Integer} Number of zoom levels for the map.  Defaults to 16.  Set a
     *           different value in the map options if needed.
     */
    numZoomLevels: 16,

    /**
     * APIProperty: theme
     * {String} Relative path to a CSS file from which to load theme styles.
     *          Specify null in the map options (e.g. {theme: null}) if you 
     *          want to get cascading style declarations - by putting links to 
     *          stylesheets or style declarations directly in your page.
     */
    theme: null,
    
    /** 
     * APIProperty: displayProjection
     * {<OpenLayers.Projection>} Requires proj4js support for projections other
     *     than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by
     *     several controls to display data to user. If this property is set,
     *     it will be set on any control which has a null displayProjection
     *     property at the time the control is added to the map. 
     */
    displayProjection: null,

    /**
     * APIProperty: tileManager
     * {<OpenLayers.TileManager>|Object} By default, and if the build contains
     * TileManager.js, the map will use the TileManager to queue image requests
     * and to cache tile image elements. To create a map without a TileManager
     * configure the map with tileManager: null. To create a TileManager with
     * non-default options, supply the options instead or alternatively supply
     * an instance of {<OpenLayers.TileManager>}.
     */

    /**
     * APIProperty: fallThrough
     * {Boolean} Should OpenLayers allow events on the map to fall through to
     *           other elements on the page, or should it swallow them? (#457)
     *           Default is to swallow.
     */
    fallThrough: false,

    /**
     * APIProperty: autoUpdateSize
     * {Boolean} Should OpenLayers automatically update the size of the map
     * when the resize event is fired. Default is true.
     */
    autoUpdateSize: true,
    
    /**
     * APIProperty: eventListeners
     * {Object} If set as an option at construction, the eventListeners
     *     object will be registered with <OpenLayers.Events.on>.  Object
     *     structure must be a listeners object as shown in the example for
     *     the events.on method.
     */
    eventListeners: null,

    /**
     * Property: panTween
     * {<OpenLayers.Tween>} Animated panning tween object, see panTo()
     */
    panTween: null,

    /**
     * APIProperty: panMethod
     * {Function} The Easing function to be used for tweening.  Default is
     * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
     * animated panning.
     */
    panMethod: OpenLayers.Easing.Expo.easeOut,
    
    /**
     * Property: panDuration
     * {Integer} The number of steps to be passed to the
     * OpenLayers.Tween.start() method when the map is
     * panned.
     * Default is 50.
     */
    panDuration: 50,
    
    /**
     * Property: zoomTween
     * {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
     */
    zoomTween: null,

    /**
     * APIProperty: zoomMethod
     * {Function} The Easing function to be used for tweening.  Default is
     * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
     * animated zooming.
     */
    zoomMethod: OpenLayers.Easing.Quad.easeOut,
    
    /**
     * Property: zoomDuration
     * {Integer} The number of steps to be passed to the
     * OpenLayers.Tween.start() method when the map is zoomed.
     * Default is 20.
     */
    zoomDuration: 20,
    
    /**
     * Property: paddingForPopups
     * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent 
     *     the popup from getting too close to the map border.
     */
    paddingForPopups : null,
    
    /**
     * Property: layerContainerOriginPx
     * {Object} Cached object representing the layer container origin (in pixels).
     */
    layerContainerOriginPx: null,
    
    /**
     * Property: minPx
     * {Object} An object with a 'x' and 'y' values that is the lower
     *     left of maxExtent in viewport pixel space.
     *     Used to verify in moveByPx that the new location we're moving to
     *     is valid. It is also used in the getLonLatFromViewPortPx function
     *     of Layer.
     */
    minPx: null,
    
    /**
     * Property: maxPx
     * {Object} An object with a 'x' and 'y' values that is the top
     *     right of maxExtent in viewport pixel space.
     *     Used to verify in moveByPx that the new location we're moving to
     *     is valid.
     */
    maxPx: null,
    
    /**
     * Constructor: OpenLayers.Map
     * Constructor for a new OpenLayers.Map instance.  There are two possible
     *     ways to call the map constructor.  See the examples below.
     *
     * Parameters:
     * div - {DOMElement|String}  The element or id of an element in your page
     *     that will contain the map.  May be omitted if the <div> option is
     *     provided or if you intend to call the <render> method later.
     * options - {Object} Optional object with properties to tag onto the map.
     *
     * Valid options (in addition to the listed API properties):
     * center - {<OpenLayers.LonLat>|Array} The default initial center of the map.
     *     If provided as array, the first value is the x coordinate,
     *     and the 2nd value is the y coordinate.
     *     Only specify if <layers> is provided.
     *     Note that if an ArgParser/Permalink control is present,
     *     and the querystring contains coordinates, center will be set
     *     by that, and this option will be ignored.
     * zoom - {Number} The initial zoom level for the map. Only specify if
     *     <layers> is provided.
     *     Note that if an ArgParser/Permalink control is present,
     *     and the querystring contains a zoom level, zoom will be set
     *     by that, and this option will be ignored.
     * extent - {<OpenLayers.Bounds>|Array} The initial extent of the map.
     *     If provided as an array, the array should consist of
     *     four values (left, bottom, right, top).
     *     Only specify if <center> and <zoom> are not provided.
     * 
     * Examples:
     * (code)
     * // create a map with default options in an element with the id "map1"
     * var map = new OpenLayers.Map("map1");
     *
     * // create a map with non-default options in an element with id "map2"
     * var options = {
     *     projection: "EPSG:3857",
     *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
     *     center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
     * };
     * var map = new OpenLayers.Map("map2", options);
     *
     * // map with non-default options - same as above but with a single argument,
     * // a restricted extent, and using arrays for bounds and center
     * var map = new OpenLayers.Map({
     *     div: "map_id",
     *     projection: "EPSG:3857",
     *     maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],
     *     restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],
     *     center: [-12356463.476333, 5621521.4854095]
     * });
     *
     * // create a map without a reference to a container - call render later
     * var map = new OpenLayers.Map({
     *     projection: "EPSG:3857",
     *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)
     * });
     * (end)
     */    
    initialize: function (div, options) {
        
        // If only one argument is provided, check if it is an object.
        if(arguments.length === 1 && typeof div === "object") {
            options = div;
            div = options && options.div;
        }

        // Simple-type defaults are set in class definition. 
        //  Now set complex-type defaults 
        this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
                                            OpenLayers.Map.TILE_HEIGHT);
        
        this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);

        this.theme = OpenLayers._getScriptLocation() + 
                             'theme/default/style.css'; 

        // backup original options
        this.options = OpenLayers.Util.extend({}, options);

        // now override default options 
        OpenLayers.Util.extend(this, options);
        
        var projCode = this.projection instanceof OpenLayers.Projection ?
            this.projection.projCode : this.projection;
        OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);
        
        // allow extents and center to be arrays
        if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {
            this.maxExtent = new OpenLayers.Bounds(this.maxExtent);
        }
        if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {
            this.minExtent = new OpenLayers.Bounds(this.minExtent);
        }
        if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {
            this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);
        }
        if (this.center && !(this.center instanceof OpenLayers.LonLat)) {
            this.center = new OpenLayers.LonLat(this.center);
        }

        // initialize layers array
        this.layers = [];

        this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");

        this.div = OpenLayers.Util.getElement(div);
        if(!this.div) {
            this.div = document.createElement("div");
            this.div.style.height = "1px";
            this.div.style.width = "1px";
        }
        
        OpenLayers.Element.addClass(this.div, 'olMap');

        // the viewPortDiv is the outermost div we modify
        var id = this.id + "_OpenLayers_ViewPort";
        this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
                                                     "relative", null,
                                                     "hidden");
        this.viewPortDiv.style.width = "100%";
        this.viewPortDiv.style.height = "100%";
        this.viewPortDiv.className = "olMapViewport";
        this.div.appendChild(this.viewPortDiv);

        this.events = new OpenLayers.Events(
            this, this.viewPortDiv, null, this.fallThrough, 
            {includeXY: true}
        );
        
        if (OpenLayers.TileManager && this.tileManager !== null) {
            if (!(this.tileManager instanceof OpenLayers.TileManager)) {
                this.tileManager = new OpenLayers.TileManager(this.tileManager);
            }
            this.tileManager.addMap(this);
        }

        // the layerContainerDiv is the one that holds all the layers
        id = this.id + "_OpenLayers_Container";
        this.layerContainerDiv = OpenLayers.Util.createDiv(id);
        this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
        this.layerContainerOriginPx = {x: 0, y: 0};
        this.applyTransform();
        
        this.viewPortDiv.appendChild(this.layerContainerDiv);

        this.updateSize();
        if(this.eventListeners instanceof Object) {
            this.events.on(this.eventListeners);
        }

        if (this.autoUpdateSize === true) {
            // updateSize on catching the window's resize
            // Note that this is ok, as updateSize() does nothing if the 
            // map's size has not actually changed.
            this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize, 
                this);
            OpenLayers.Event.observe(window, 'resize',
                            this.updateSizeDestroy);
        }
        
        // only append link stylesheet if the theme property is set
        if(this.theme) {
            // check existing links for equivalent url
            var addNode = true;
            var nodes = document.getElementsByTagName('link');
            for(var i=0, len=nodes.length; i<len; ++i) {
                if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
                                                   this.theme)) {
                    addNode = false;
                    break;
                }
            }
            // only add a new node if one with an equivalent url hasn't already
            // been added
            if(addNode) {
                var cssNode = document.createElement('link');
                cssNode.setAttribute('rel', 'stylesheet');
                cssNode.setAttribute('type', 'text/css');
                cssNode.setAttribute('href', this.theme);
                document.getElementsByTagName('head')[0].appendChild(cssNode);
            }
        }
        
        if (this.controls == null) { // default controls
            this.controls = [];
            if (OpenLayers.Control != null) { // running full or lite?
                // Navigation or TouchNavigation depending on what is in build
                if (OpenLayers.Control.Navigation) {
                    this.controls.push(new OpenLayers.Control.Navigation());
                } else if (OpenLayers.Control.TouchNavigation) {
                    this.controls.push(new OpenLayers.Control.TouchNavigation());
                }
                if (OpenLayers.Control.Zoom) {
                    this.controls.push(new OpenLayers.Control.Zoom());
                } else if (OpenLayers.Control.PanZoom) {
                    this.controls.push(new OpenLayers.Control.PanZoom());
                }

                if (OpenLayers.Control.ArgParser) {
                    this.controls.push(new OpenLayers.Control.ArgParser());
                }
                if (OpenLayers.Control.Attribution) {
                    this.controls.push(new OpenLayers.Control.Attribution());
                }
            }
        }

        for(var i=0, len=this.controls.length; i<len; i++) {
            this.addControlToMap(this.controls[i]);
        }

        this.popups = [];

        this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
        

        // always call map.destroy()
        OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
        
        // add any initial layers
        if (options && options.layers) {
            /** 
             * If you have set options.center, the map center property will be
             * set at this point.  However, since setCenter has not been called,
             * addLayers gets confused.  So we delete the map center in this 
             * case.  Because the check below uses options.center, it will
             * be properly set below.
             */
            delete this.center;
            delete this.zoom;
            this.addLayers(options.layers);
            // set center (and optionally zoom)
            if (options.center && !this.getCenter()) {
                // zoom can be undefined here
                this.setCenter(options.center, options.zoom);
            }
        }

        if (this.panMethod) {
            this.panTween = new OpenLayers.Tween(this.panMethod);
        }
        if (this.zoomMethod && this.applyTransform.transform) {
            this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
        }
    },

    /** 
     * APIMethod: getViewport
     * Get the DOMElement representing the view port.
     *
     * Returns:
     * {DOMElement}
     */
    getViewport: function() {
        return this.viewPortDiv;
    },
    
    /**
     * APIMethod: render
     * Render the map to a specified container.
     * 
     * Parameters:
     * div - {String|DOMElement} The container that the map should be rendered
     *     to. If different than the current container, the map viewport
     *     will be moved from the current to the new container.
     */
    render: function(div) {
        this.div = OpenLayers.Util.getElement(div);
        OpenLayers.Element.addClass(this.div, 'olMap');
        this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
        this.div.appendChild(this.viewPortDiv);
        this.updateSize();
    },

    /**
     * Method: unloadDestroy
     * Function that is called to destroy the map on page unload. stored here
     *     so that if map is manually destroyed, we can unregister this.
     */
    unloadDestroy: null,
    
    /**
     * Method: updateSizeDestroy
     * When the map is destroyed, we need to stop listening to updateSize
     *    events: this method stores the function we need to unregister in 
     *    non-IE browsers.
     */
    updateSizeDestroy: null,

    /**
     * APIMethod: destroy
     * Destroy this map.
     *    Note that if you are using an application which removes a container
     *    of the map from the DOM, you need to ensure that you destroy the
     *    map *before* this happens; otherwise, the page unload handler
     *    will fail because the DOM elements that map.destroy() wants
     *    to clean up will be gone. (See 
     *    http://trac.osgeo.org/openlayers/ticket/2277 for more information).
     *    This will apply to GeoExt and also to other applications which
     *    modify the DOM of the container of the OpenLayers Map.
     */
    destroy:function() {
        // if unloadDestroy is null, we've already been destroyed
        if (!this.unloadDestroy) {
            return false;
        }
        
        // make sure panning doesn't continue after destruction
        if(this.panTween) {
            this.panTween.stop();
            this.panTween = null;
        }
        // make sure zooming doesn't continue after destruction
        if(this.zoomTween) {
            this.zoomTween.stop();
            this.zoomTween = null;
        }

        // map has been destroyed. dont do it again!
        OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
        this.unloadDestroy = null;

        if (this.updateSizeDestroy) {
            OpenLayers.Event.stopObserving(window, 'resize', 
                                           this.updateSizeDestroy);
        }
        
        this.paddingForPopups = null;    

        if (this.controls != null) {
            for (var i = this.controls.length - 1; i>=0; --i) {
                this.controls[i].destroy();
            } 
            this.controls = null;
        }
        if (this.layers != null) {
            for (var i = this.layers.length - 1; i>=0; --i) {
                //pass 'false' to destroy so that map wont try to set a new 
                // baselayer after each baselayer is removed
                this.layers[i].destroy(false);
            } 
            this.layers = null;
        }
        if (this.viewPortDiv && this.viewPortDiv.parentNode) {
            this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
        }
        this.viewPortDiv = null;
        
        if (this.tileManager) {
            this.tileManager.removeMap(this);
            this.tileManager = null;
        }

        if(this.eventListeners) {
            this.events.un(this.eventListeners);
            this.eventListeners = null;
        }
        this.events.destroy();
        this.events = null;

        this.options = null;
    },

    /**
     * APIMethod: setOptions
     * Change the map options
     *
     * Parameters:
     * options - {Object} Hashtable of options to tag to the map
     */
    setOptions: function(options) {
        var updatePxExtent = this.minPx &&
            options.restrictedExtent != this.restrictedExtent;
        OpenLayers.Util.extend(this, options);
        // force recalculation of minPx and maxPx
        updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {
            forceZoomChange: true
        });
    },

    /**
     * APIMethod: getTileSize
     * Get the tile size for the map
     *
     * Returns:
     * {<OpenLayers.Size>}
     */
     getTileSize: function() {
         return this.tileSize;
     },


    /**
     * APIMethod: getBy
     * Get a list of objects given a property and a match item.
     *
     * Parameters:
     * array - {String} A property on the map whose value is an array.
     * property - {String} A property on each item of the given array.
     * match - {String | Object} A string to match.  Can also be a regular
     *     expression literal or object.  In addition, it can be any object
     *     with a method named test.  For reqular expressions or other, if
     *     match.test(map[array][i][property]) evaluates to true, the item will
     *     be included in the array returned.  If no items are found, an empty
     *     array is returned.
     *
     * Returns:
     * {Array} An array of items where the given property matches the given
     *     criteria.
     */
    getBy: function(array, property, match) {
        var test = (typeof match.test == "function");
        var found = OpenLayers.Array.filter(this[array], function(item) {
            return item[property] == match || (test && match.test(item[property]));
        });
        return found;
    },

    /**
     * APIMethod: getLayersBy
     * Get a list of layers with properties matching the given criteria.
     *
     * Parameters:
     * property - {String} A layer property to be matched.
     * match - {String | Object} A string to match.  Can also be a regular
     *     expression literal or object.  In addition, it can be any object
     *     with a method named test.  For reqular expressions or other, if
     *     match.test(layer[property]) evaluates to true, the layer will be
     *     included in the array returned.  If no layers are found, an empty
     *     array is returned.
     *
     * Returns:
     * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
     *     An empty array is returned if no matches are found.
     */
    getLayersBy: function(property, match) {
        return this.getBy("layers", property, match);
    },

    /**
     * APIMethod: getLayersByName
     * Get a list of layers with names matching the given name.
     *
     * Parameters:
     * match - {String | Object} A layer name.  The name can also be a regular
     *     expression literal or object.  In addition, it can be any object
     *     with a method named test.  For reqular expressions or other, if
     *     name.test(layer.name) evaluates to true, the layer will be included
     *     in the list of layers returned.  If no layers are found, an empty
     *     array is returned.
     *
     * Returns:
     * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
     *     An empty array is returned if no matches are found.
     */
    getLayersByName: function(match) {
        return this.getLayersBy("name", match);
    },

    /**
     * APIMethod: getLayersByClass
     * Get a list of layers of a given class (CLASS_NAME).
     *
     * Parameters:
     * match - {String | Object} A layer class name.  The match can also be a
     *     regular expression literal or object.  In addition, it can be any
     *     object with a method named test.  For reqular expressions or other,
     *     if type.test(layer.CLASS_NAME) evaluates to true, the layer will
     *     be included in the list of layers returned.  If no layers are
     *     found, an empty array is returned.
     *
     * Returns:
     * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
     *     An empty array is returned if no matches are found.
     */
    getLayersByClass: function(match) {
        return this.getLayersBy("CLASS_NAME", match);
    },

    /**
     * APIMethod: getControlsBy
     * Get a list of controls with properties matching the given criteria.
     *
     * Parameters:
     * property - {String} A control property to be matched.
     * match - {String | Object} A string to match.  Can also be a regular
     *     expression literal or object.  In addition, it can be any object
     *     with a method named test.  For reqular expressions or other, if
     *     match.test(layer[property]) evaluates to true, the layer will be
     *     included in the array returned.  If no layers are found, an empty
     *     array is returned.
     *
     * Returns:
     * {Array(<OpenLayers.Control>)} A list of controls matching the given
     *     criteria.  An empty array is returned if no matches are found.
     */
    getControlsBy: function(property, match) {
        return this.getBy("controls", property, match);
    },

    /**
     * APIMethod: getControlsByClass
     * Get a list of controls of a given class (CLASS_NAME).
     *
     * Parameters:
     * match - {String | Object} A control class name.  The match can also be a
     *     regular expression literal or object.  In addition, it can be any
     *     object with a method named test.  For reqular expressions or other,
     *     if type.test(control.CLASS_NAME) evaluates to true, the control will
     *     be included in the list of controls returned.  If no controls are
     *     found, an empty array is returned.
     *
     * Returns:
     * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
     *     An empty array is returned if no matches are found.
     */
    getControlsByClass: function(match) {
        return this.getControlsBy("CLASS_NAME", match);
    },

  /********************************************************/
  /*                                                      */
  /*                  Layer Functions                     */
  /*                                                      */
  /*     The following functions deal with adding and     */
  /*        removing Layers to and from the Map           */
  /*                                                      */
  /********************************************************/         

    /**
     * APIMethod: getLayer
     * Get a layer based on its id
     *
     * Parameters:
     * id - {String} A layer id
     *
     * Returns:
     * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's 
     *                      layer collection, or null if not found.
     */
    getLayer: function(id) {
        var foundLayer = null;
        for (var i=0, len=this.layers.length; i<len; i++) {
            var layer = this.layers[i];
            if (layer.id == id) {
                foundLayer = layer;
                break;
            }
        }
        return foundLayer;
    },

    /**
    * Method: setLayerZIndex
    * 
    * Parameters:
    * layer - {<OpenLayers.Layer>} 
    * zIdx - {int} 
    */    
    setLayerZIndex: function (layer, zIdx) {
        layer.setZIndex(
            this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
            + zIdx * 5 );
    },

    /**
     * Method: resetLayersZIndex
     * Reset each layer's z-index based on layer's array index
     */
    resetLayersZIndex: function() {
        for (var i=0, len=this.layers.length; i<len; i++) {
            var layer = this.layers[i];
            this.setLayerZIndex(layer, i);
        }
    },

    /**
    * APIMethod: addLayer
    *
    * Parameters:
    * layer - {<OpenLayers.Layer>} 
    *
    * Returns:
    * {Boolean} True if the layer has been added to the map.
    */    
    addLayer: function (layer) {
        for(var i = 0, len = this.layers.length; i < len; i++) {
            if (this.layers[i] == layer) {
                return false;
            }
        }
        if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) {
            return false;
        }
        if(this.allOverlays) {
            layer.isBaseLayer = false;
        }
        
        layer.div.className = "olLayerDiv";
        layer.div.style.overflow = "";
        this.setLayerZIndex(layer, this.layers.length);

        if (layer.isFixed) {
            this.viewPortDiv.appendChild(layer.div);
        } else {
            this.layerContainerDiv.appendChild(layer.div);
        }
        this.layers.push(layer);
        layer.setMap(this);

        if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer))  {
            if (this.baseLayer == null) {
                // set the first baselaye we add as the baselayer
                this.setBaseLayer(layer);
            } else {
                layer.setVisibility(false);
            }
        } else {
            layer.redraw();
        }

        this.events.triggerEvent("addlayer", {layer: layer});
        layer.events.triggerEvent("added", {map: this, layer: layer});
        layer.afterAdd();

        return true;
    },

    /**
    * APIMethod: addLayers 
    *
    * Parameters:
    * layers - {Array(<OpenLayers.Layer>)} 
    */    
    addLayers: function (layers) {
        for (var i=0, len=layers.length; i<len; i++) {
            this.addLayer(layers[i]);
        }
    },

    /** 
     * APIMethod: removeLayer
     * Removes a layer from the map by removing its visual element (the 
     *   layer.div property), then removing it from the map's internal list 
     *   of layers, setting the layer's map property to null. 
     * 
     *   a "removelayer" event is triggered.
     * 
     *   very worthy of mention is that simply removing a layer from a map
     *   will not cause the removal of any popups which may have been created
     *   by the layer. this is due to the fact that it was decided at some
     *   point that popups would not belong to layers. thus there is no way 
     *   for us to know here to which layer the popup belongs.
     *    
     *     A simple solution to this is simply to call destroy() on the layer.
     *     the default OpenLayers.Layer class's destroy() function
     *     automatically takes care to remove itself from whatever map it has
     *     been attached to. 
     * 
     *     The correct solution is for the layer itself to register an 
     *     event-handler on "removelayer" and when it is called, if it 
     *     recognizes itself as the layer being removed, then it cycles through
     *     its own personal list of popups, removing them from the map.
     * 
     * Parameters:
     * layer - {<OpenLayers.Layer>} 
     * setNewBaseLayer - {Boolean} Default is true
     */
    removeLayer: function(layer, setNewBaseLayer) {
        if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) {
            return;
        }
        if (setNewBaseLayer == null) {
            setNewBaseLayer = true;
        }

        if (layer.isFixed) {
            this.viewPortDiv.removeChild(layer.div);
        } else {
            this.layerContainerDiv.removeChild(layer.div);
        }
        OpenLayers.Util.removeItem(this.layers, layer);
        layer.removeMap(this);
        layer.map = null;

        // if we removed the base layer, need to set a new one
        if(this.baseLayer == layer) {
            this.baseLayer = null;
            if(setNewBaseLayer) {
                for(var i=0, len=this.layers.length; i<len; i++) {
                    var iLayer = this.layers[i];
                    if (iLayer.isBaseLayer || this.allOverlays) {
                        this.setBaseLayer(iLayer);
                        break;
                    }
                }
            }
        }

        this.resetLayersZIndex();

        this.events.triggerEvent("removelayer", {layer: layer});
        layer.events.triggerEvent("removed", {map: this, layer: layer});
    },

    /**
     * APIMethod: getNumLayers
     * 
     * Returns:
     * {Int} The number of layers attached to the map.
     */
    getNumLayers: function () {
        return this.layers.length;
    },

    /** 
     * APIMethod: getLayerIndex
     *
     * Parameters:
     * layer - {<OpenLayers.Layer>}
     *
     * Returns:
     * {Integer} The current (zero-based) index of the given layer in the map's
     *           layer stack. Returns -1 if the layer isn't on the map.
     */
    getLayerIndex: function (layer) {
        return OpenLayers.Util.indexOf(this.layers, layer);
    },
    
    /** 
     * APIMethod: setLayerIndex
     * Move the given layer to the specified (zero-based) index in the layer
     *     list, changing its z-index in the map display. Use
     *     map.getLayerIndex() to find out the current index of a layer. Note
     *     that this cannot (or at least should not) be effectively used to
     *     raise base layers above overlays.
     *
     * Parameters:
     * layer - {<OpenLayers.Layer>} 
     * idx - {int} 
     */
    setLayerIndex: function (layer, idx) {
        var base = this.getLayerIndex(layer);
        if (idx < 0) {
            idx = 0;
        } else if (idx > this.layers.length) {
            idx = this.layers.length;
        }
        if (base != idx) {
            this.layers.splice(base, 1);
            this.layers.splice(idx, 0, layer);
            for (var i=0, len=this.layers.length; i<len; i++) {
                this.setLayerZIndex(this.layers[i], i);
            }
            this.events.triggerEvent("changelayer", {
                layer: layer, property: "order"
            });
            if(this.allOverlays) {
                if(idx === 0) {
                    this.setBaseLayer(layer);
                } else if(this.baseLayer !== this.layers[0]) {
                    this.setBaseLayer(this.layers[0]);
                }
            }
        }
    },

    /** 
     * APIMethod: raiseLayer
     * Change the index of the given layer by delta. If delta is positive, 
     *     the layer is moved up the map's layer stack; if delta is negative,
     *     the layer is moved down.  Again, note that this cannot (or at least
     *     should not) be effectively used to raise base layers above overlays.
     *
     * Paremeters:
     * layer - {<OpenLayers.Layer>} 
     * delta - {int} 
     */
    raiseLayer: function (layer, delta) {
        var idx = this.getLayerIndex(layer) + delta;
        this.setLayerIndex(layer, idx);
    },
    
    /** 
     * APIMethod: setBaseLayer
     * Allows user to specify one of the currently-loaded layers as the Map's
     *     new base layer.
     * 
     * Parameters:
     * newBaseLayer - {<OpenLayers.Layer>}
     */
    setBaseLayer: function(newBaseLayer) {
        
        if (newBaseLayer != this.baseLayer) {
          
            // ensure newBaseLayer is already loaded
            if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {

                // preserve center and scale when changing base layers
                var center = this.getCachedCenter();
                var newResolution = OpenLayers.Util.getResolutionFromScale(
                    this.getScale(), newBaseLayer.units
                );

                // make the old base layer invisible 
                if (this.baseLayer != null && !this.allOverlays) {
                    this.baseLayer.setVisibility(false);
                }

                // set new baselayer
                this.baseLayer = newBaseLayer;
                
                if(!this.allOverlays || this.baseLayer.visibility) {
                    this.baseLayer.setVisibility(true);
                    // Layer may previously have been visible but not in range.
                    // In this case we need to redraw it to make it visible.
                    if (this.baseLayer.inRange === false) {
                        this.baseLayer.redraw();
                    }
                }

                // recenter the map
                if (center != null) {
                    // new zoom level derived from old scale
                    var newZoom = this.getZoomForResolution(
                        newResolution || this.resolution, true
                    );
                    // zoom and force zoom change
                    this.setCenter(center, newZoom, false, true);
                }

                this.events.triggerEvent("changebaselayer", {
                    layer: this.baseLayer
                });
            }        
        }
    },


  /********************************************************/
  /*                                                      */
  /*                 Control Functions                    */
  /*                                                      */
  /*     The following functions deal with adding and     */
  /*        removing Controls to and from the Map         */
  /*                                                      */
  /********************************************************/         

    /**
     * APIMethod: addControl
     * Add the passed over control to the map. Optionally 
     *     position the control at the given pixel.
     * 
     * Parameters:
     * control - {<OpenLayers.Control>}
     * px - {<OpenLayers.Pixel>}
     */    
    addControl: function (control, px) {
        this.controls.push(control);
        this.addControlToMap(control, px);
    },
    
    /**
     * APIMethod: addControls
     * Add all of the passed over controls to the map. 
     *     You can pass over an optional second array
     *     with pixel-objects to position the controls.
     *     The indices of the two arrays should match and
     *     you can add null as pixel for those controls 
     *     you want to be autopositioned.   
     *     
     * Parameters:
     * controls - {Array(<OpenLayers.Control>)}
     * pixels - {Array(<OpenLayers.Pixel>)}
     */    
    addControls: function (controls, pixels) {
        var pxs = (arguments.length === 1) ? [] : pixels;
        for (var i=0, len=controls.length; i<len; i++) {
            var ctrl = controls[i];
            var px = (pxs[i]) ? pxs[i] : null;
            this.addControl( ctrl, px );
        }
    },

    /**
     * Method: addControlToMap
     * 
     * Parameters:
     * 
     * control - {<OpenLayers.Control>}
     * px - {<OpenLayers.Pixel>}
     */    
    addControlToMap: function (control, px) {
        // If a control doesn't have a div at this point, it belongs in the
        // viewport.
        control.outsideViewport = (control.div != null);
        
        // If the map has a displayProjection, and the control doesn't, set 
        // the display projection.
        if (this.displayProjection && !control.displayProjection) {
            control.displayProjection = this.displayProjection;
        }    
        
        control.setMap(this);
        var div = control.draw(px);
        if (div) {
            if(!control.outsideViewport) {
                div.style.zIndex = this.Z_INDEX_BASE['Control'] +
                                    this.controls.length;
                this.viewPortDiv.appendChild( div );
            }
        }
        if(control.autoActivate) {
            control.activate();
        }
    },
    
    /**
     * APIMethod: getControl
     * 
     * Parameters:
     * id - {String} ID of the control to return.
     * 
     * Returns:
     * {<OpenLayers.Control>} The control from the map's list of controls 
     *                        which has a matching 'id'. If none found, 
     *                        returns null.
     */    
    getControl: function (id) {
        var returnControl = null;
        for(var i=0, len=this.controls.length; i<len; i++) {
            var control = this.controls[i];
            if (control.id == id) {
                returnControl = control;
                break;
            }
        }
        return returnControl;
    },
    
    /** 
     * APIMethod: removeControl
     * Remove a control from the map. Removes the control both from the map 
     *     object's internal array of controls, as well as from the map's 
     *     viewPort (assuming the control was not added outsideViewport)
     * 
     * Parameters:
     * control - {<OpenLayers.Control>} The control to remove.
     */    
    removeControl: function (control) {
        //make sure control is non-null and actually part of our map
        if ( (control) && (control == this.getControl(control.id)) ) {
            if (control.div && (control.div.parentNode == this.viewPortDiv)) {
                this.viewPortDiv.removeChild(control.div);
            }
            OpenLayers.Util.removeItem(this.controls, control);
        }
    },

  /********************************************************/
  /*                                                      */
  /*                  Popup Functions                     */
  /*                                                      */
  /*     The following functions deal with adding and     */
  /*        removing Popups to and from the Map           */
  /*                                                      */
  /********************************************************/         

    /** 
     * APIMethod: addPopup
     * 
     * Parameters:
     * popup - {<OpenLayers.Popup>}
     * exclusive - {Boolean} If true, closes all other popups first
     */
    addPopup: function(popup, exclusive) {

        if (exclusive) {
            //remove all other popups from screen
            for (var i = this.popups.length - 1; i >= 0; --i) {
                this.removePopup(this.popups[i]);
            }
        }

        popup.map = this;
        this.popups.push(popup);
        var popupDiv = popup.draw();
        if (popupDiv) {
            popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
                                    this.popups.length;
            this.layerContainerDiv.appendChild(popupDiv);
        }
    },
    
    /** 
    * APIMethod: removePopup
    * 
    * Parameters:
    * popup - {<OpenLayers.Popup>}
    */
    removePopup: function(popup) {
        OpenLayers.Util.removeItem(this.popups, popup);
        if (popup.div) {
            try { this.layerContainerDiv.removeChild(popup.div); }
            catch (e) { } // Popups sometimes apparently get disconnected
                      // from the layerContainerDiv, and cause complaints.
        }
        popup.map = null;
    },

  /********************************************************/
  /*                                                      */
  /*              Container Div Functions                 */
  /*                                                      */
  /*   The following functions deal with the access to    */
  /*    and maintenance of the size of the container div  */
  /*                                                      */
  /********************************************************/     

    /**
     * APIMethod: getSize
     * 
     * Returns:
     * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the 
     *                     size, in pixels, of the div into which OpenLayers 
     *                     has been loaded. 
     *                     Note - A clone() of this locally cached variable is
     *                     returned, so as not to allow users to modify it.
     */
    getSize: function () {
        var size = null;
        if (this.size != null) {
            size = this.size.clone();
        }
        return size;
    },

    /**
     * APIMethod: updateSize
     * This function should be called by any external code which dynamically
     *     changes the size of the map div (because mozilla wont let us catch 
     *     the "onresize" for an element)
     */
    updateSize: function() {
        // the div might have moved on the page, also
        var newSize = this.getCurrentSize();
        if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {
            this.events.clearMouseCache();
            var oldSize = this.getSize();
            if (oldSize == null) {
                this.size = oldSize = newSize;
            }
            if (!newSize.equals(oldSize)) {
                
                // store the new size
                this.size = newSize;
    
                //notify layers of mapresize
                for(var i=0, len=this.layers.length; i<len; i++) {
                    this.layers[i].onMapResize();                
                }
    
                var center = this.getCachedCenter();
    
                if (this.baseLayer != null && center != null) {
                    var zoom = this.getZoom();
                    this.zoom = null;
                    this.setCenter(center, zoom);
                }
    
            }
        }
        this.events.triggerEvent("updatesize");
    },
    
    /**
     * Method: getCurrentSize
     * 
     * Returns:
     * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions 
     *                     of the map div
     */
    getCurrentSize: function() {

        var size = new OpenLayers.Size(this.div.clientWidth, 
                                       this.div.clientHeight);

        if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
            size.w = this.div.offsetWidth;
            size.h = this.div.offsetHeight;
        }
        if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
            size.w = parseInt(this.div.style.width);
            size.h = parseInt(this.div.style.height);
        }
        return size;
    },

    /** 
     * Method: calculateBounds
     * 
     * Parameters:
     * center - {<OpenLayers.LonLat>} Default is this.getCenter()
     * resolution - {float} Default is this.getResolution() 
     * 
     * Returns:
     * {<OpenLayers.Bounds>} A bounds based on resolution, center, and 
     *                       current mapsize.
     */
    calculateBounds: function(center, resolution) {

        var extent = null;
        
        if (center == null) {
            center = this.getCachedCenter();
        }                
        if (resolution == null) {
            resolution = this.getResolution();
        }
    
        if ((center != null) && (resolution != null)) {
            var halfWDeg = (this.size.w * resolution) / 2;
            var halfHDeg = (this.size.h * resolution) / 2;
        
            extent = new OpenLayers.Bounds(center.lon - halfWDeg,
                                           center.lat - halfHDeg,
                                           center.lon + halfWDeg,
                                           center.lat + halfHDeg);
        }

        return extent;
    },


  /********************************************************/
  /*                                                      */
  /*            Zoom, Center, Pan Functions               */
  /*                                                      */
  /*    The following functions handle the validation,    */
  /*   getting and setting of the Zoom Level and Center   */
  /*       as well as the panning of the Map              */
  /*                                                      */
  /********************************************************/
    /**
     * APIMethod: getCenter
     * 
     * Returns:
     * {<OpenLayers.LonLat>}
     */
    getCenter: function () {
        var center = null;
        var cachedCenter = this.getCachedCenter();
        if (cachedCenter) {
            center = cachedCenter.clone();
        }
        return center;
    },

    /**
     * Method: getCachedCenter
     *
     * Returns:
     * {<OpenLayers.LonLat>}
     */
    getCachedCenter: function() {
        if (!this.center && this.size) {
            this.center = this.getLonLatFromViewPortPx({
                x: this.size.w / 2,
                y: this.size.h / 2
            });
        }
        return this.center;
    },

    /**
     * APIMethod: getZoom
     * 
     * Returns:
     * {Integer}
     */
    getZoom: function () {
        return this.zoom;
    },
    
    /** 
     * APIMethod: pan
     * Allows user to pan by a value of screen pixels
     * 
     * Parameters:
     * dx - {Integer}
     * dy - {Integer}
     * options - {Object} Options to configure panning:
     *  - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
     *  - *dragging* {Boolean} Call setCenter with dragging true.  Default is
     *    false.
     */
    pan: function(dx, dy, options) {
        options = OpenLayers.Util.applyDefaults(options, {
            animate: true,
            dragging: false
        });
        if (options.dragging) {
            if (dx != 0 || dy != 0) {
                this.moveByPx(dx, dy);
            }
        } else {
            // getCenter
            var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());

            // adjust
            var newCenterPx = centerPx.add(dx, dy);

            if (this.dragging || !newCenterPx.equals(centerPx)) {
                var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
                if (options.animate) {
                    this.panTo(newCenterLonLat);
                } else {
                    this.moveTo(newCenterLonLat);
                    if(this.dragging) {
                        this.dragging = false;
                        this.events.triggerEvent("moveend");
                    }
                }    
            }
        }        

   },
   
   /** 
     * APIMethod: panTo
     * Allows user to pan to a new lonlat
     * If the new lonlat is in the current extent the map will slide smoothly
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>}
     */
    panTo: function(lonlat) {
        if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
            var center = this.getCachedCenter();

            // center will not change, don't do nothing
            if (lonlat.equals(center)) {
                return;
            }

            var from = this.getPixelFromLonLat(center);
            var to = this.getPixelFromLonLat(lonlat);
            var vector = { x: to.x - from.x, y: to.y - from.y };
            var last = { x: 0, y: 0 };

            this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, {
                callbacks: {
                    eachStep: OpenLayers.Function.bind(function(px) {
                        var x = px.x - last.x,
                            y = px.y - last.y;
                        this.moveByPx(x, y);
                        last.x = Math.round(px.x);
                        last.y = Math.round(px.y);
                    }, this),
                    done: OpenLayers.Function.bind(function(px) {
                        this.moveTo(lonlat);
                        this.dragging = false;
                        this.events.triggerEvent("moveend");
                    }, this)
                }
            });
        } else {
            this.setCenter(lonlat);
        }
    },

    /**
     * APIMethod: setCenter
     * Set the map center (and optionally, the zoom level).
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>|Array} The new center location.
     *     If provided as array, the first value is the x coordinate,
     *     and the 2nd value is the y coordinate.
     * zoom - {Integer} Optional zoom level.
     * dragging - {Boolean} Specifies whether or not to trigger 
     *                      movestart/end events
     * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom 
     *                             change events (needed on baseLayer change)
     *
     * TBD: reconsider forceZoomChange in 3.0
     */
    setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
        if (this.panTween) {
            this.panTween.stop();
        }
        if (this.zoomTween) {
            this.zoomTween.stop();
        }            
        this.moveTo(lonlat, zoom, {
            'dragging': dragging,
            'forceZoomChange': forceZoomChange
        });
    },
    
    /** 
     * Method: moveByPx
     * Drag the map by pixels.
     *
     * Parameters:
     * dx - {Number}
     * dy - {Number}
     */
    moveByPx: function(dx, dy) {
        var hw = this.size.w / 2;
        var hh = this.size.h / 2;
        var x = hw + dx;
        var y = hh + dy;
        var wrapDateLine = this.baseLayer.wrapDateLine;
        var xRestriction = 0;
        var yRestriction = 0;
        if (this.restrictedExtent) {
            xRestriction = hw;
            yRestriction = hh;
            // wrapping the date line makes no sense for restricted extents
            wrapDateLine = false;
        }
        dx = wrapDateLine ||
                    x <= this.maxPx.x - xRestriction &&
                    x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;
        dy = y <= this.maxPx.y - yRestriction &&
                    y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;
        if (dx || dy) {
            if (!this.dragging) {
                this.dragging = true;
                this.events.triggerEvent("movestart");
            }
            this.center = null;
            if (dx) {
                this.layerContainerOriginPx.x -= dx;
                this.minPx.x -= dx;
                this.maxPx.x -= dx;
            }
            if (dy) {
                this.layerContainerOriginPx.y -= dy;
                this.minPx.y -= dy;
                this.maxPx.y -= dy;
            }
            this.applyTransform();
            var layer, i, len;
            for (i=0, len=this.layers.length; i<len; ++i) {
                layer = this.layers[i];
                if (layer.visibility &&
                    (layer === this.baseLayer || layer.inRange)) {
                    layer.moveByPx(dx, dy);
                    layer.events.triggerEvent("move");
                }
            }
            this.events.triggerEvent("move");
        }
    },
    
    /**
     * Method: adjustZoom
     *
     * Parameters:
     * zoom - {Number} The zoom level to adjust
     *
     * Returns:
     * {Integer} Adjusted zoom level that shows a map not wider than its
     * <baseLayer>'s maxExtent.
     */
    adjustZoom: function(zoom) {
        if (this.baseLayer && this.baseLayer.wrapDateLine) {
            var resolution, resolutions = this.baseLayer.resolutions,
                maxResolution = this.getMaxExtent().getWidth() / this.size.w;
            if (this.getResolutionForZoom(zoom) > maxResolution) {
                if (this.fractionalZoom) {
                    zoom = this.getZoomForResolution(maxResolution);
                } else {
                    for (var i=zoom|0, ii=resolutions.length; i<ii; ++i) {
                        if (resolutions[i] <= maxResolution) {
                            zoom = i;
                            break;
                        }
                    }
                } 
            }
        }
        return zoom;
    },
    
    /**
     * APIMethod: getMinZoom
     * Returns the minimum zoom level for the current map view. If the base
     * layer is configured with <wrapDateLine> set to true, this will be the
     * first zoom level that shows no more than one world width in the current
     * map viewport. Components that rely on this value (e.g. zoom sliders)
     * should also listen to the map's "updatesize" event and call this method
     * in the "updatesize" listener.
     *
     * Returns:
     * {Number} Minimum zoom level that shows a map not wider than its
     * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
     * configured with <fractionalZoom> set to true.
     */
    getMinZoom: function() {
        return this.adjustZoom(0);
    },

    /**
     * Method: moveTo
     *
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>}
     * zoom - {Integer}
     * options - {Object}
     */
    moveTo: function(lonlat, zoom, options) {
        if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {
            lonlat = new OpenLayers.LonLat(lonlat);
        }
        if (!options) { 
            options = {};
        }
        if (zoom != null) {
            zoom = parseFloat(zoom);
            if (!this.fractionalZoom) {
                zoom = Math.round(zoom);
            }
        }
        var requestedZoom = zoom;
        zoom = this.adjustZoom(zoom);
        if (zoom !== requestedZoom) {
            // zoom was adjusted, so keep old lonlat to avoid panning
            lonlat = this.getCenter();
        }
        // dragging is false by default
        var dragging = options.dragging || this.dragging;
        // forceZoomChange is false by default
        var forceZoomChange = options.forceZoomChange;

        if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {
            lonlat = this.maxExtent.getCenterLonLat();
            this.center = lonlat.clone();
        }

        if(this.restrictedExtent != null) {
            // In 3.0, decide if we want to change interpretation of maxExtent.
            if(lonlat == null) { 
                lonlat = this.center; 
            }
            if(zoom == null) { 
                zoom = this.getZoom(); 
            }
            var resolution = this.getResolutionForZoom(zoom);
            var extent = this.calculateBounds(lonlat, resolution); 
            if(!this.restrictedExtent.containsBounds(extent)) {
                var maxCenter = this.restrictedExtent.getCenterLonLat(); 
                if(extent.getWidth() > this.restrictedExtent.getWidth()) { 
                    lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat); 
                } else if(extent.left < this.restrictedExtent.left) {
                    lonlat = lonlat.add(this.restrictedExtent.left -
                                        extent.left, 0); 
                } else if(extent.right > this.restrictedExtent.right) { 
                    lonlat = lonlat.add(this.restrictedExtent.right -
                                        extent.right, 0); 
                } 
                if(extent.getHeight() > this.restrictedExtent.getHeight()) { 
                    lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat); 
                } else if(extent.bottom < this.restrictedExtent.bottom) { 
                    lonlat = lonlat.add(0, this.restrictedExtent.bottom -
                                        extent.bottom); 
                } 
                else if(extent.top > this.restrictedExtent.top) { 
                    lonlat = lonlat.add(0, this.restrictedExtent.top -
                                        extent.top); 
                } 
            }
        }
        
        var zoomChanged = forceZoomChange || (
                            (this.isValidZoomLevel(zoom)) && 
                            (zoom != this.getZoom()) );

        var centerChanged = (this.isValidLonLat(lonlat)) && 
                            (!lonlat.equals(this.center));

        // if neither center nor zoom will change, no need to do anything
        if (zoomChanged || centerChanged || dragging) {
            dragging || this.events.triggerEvent("movestart", {
                zoomChanged: zoomChanged
            });

            if (centerChanged) {
                if (!zoomChanged && this.center) { 
                    // if zoom hasnt changed, just slide layerContainer
                    //  (must be done before setting this.center to new value)
                    this.centerLayerContainer(lonlat);
                }
                this.center = lonlat.clone();
            }

            var res = zoomChanged ?
                this.getResolutionForZoom(zoom) : this.getResolution();
            // (re)set the layerContainerDiv's location
            if (zoomChanged || this.layerContainerOrigin == null) {
                this.layerContainerOrigin = this.getCachedCenter();
                this.layerContainerOriginPx.x = 0;
                this.layerContainerOriginPx.y = 0;
                this.applyTransform();
                var maxExtent = this.getMaxExtent({restricted: true});
                var maxExtentCenter = maxExtent.getCenterLonLat();
                var lonDelta = this.center.lon - maxExtentCenter.lon;
                var latDelta = maxExtentCenter.lat - this.center.lat;
                var extentWidth = Math.round(maxExtent.getWidth() / res);
                var extentHeight = Math.round(maxExtent.getHeight() / res);
                this.minPx = {
                    x: (this.size.w - extentWidth) / 2 - lonDelta / res,
                    y: (this.size.h - extentHeight) / 2 - latDelta / res
                };
                this.maxPx = {
                    x: this.minPx.x + Math.round(maxExtent.getWidth() / res),
                    y: this.minPx.y + Math.round(maxExtent.getHeight() / res)
                };
            }

            if (zoomChanged) {
                this.zoom = zoom;
                this.resolution = res;
            }    
            
            var bounds = this.getExtent();
            
            //send the move call to the baselayer and all the overlays
            if (zoom && this.baseLayer.CLASS_NAME != "OpenLayers.Layer.MapGuide") {
                if (zoom >= this.baseLayer.MAX_ZOOM_LEVEL)
                    this.baseLayer.setVisibility(false);
                else
                    this.baseLayer.setVisibility(true);
            }
            if(this.baseLayer.visibility) {
                this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);
                options.dragging || this.baseLayer.events.triggerEvent(
                    "moveend", {zoomChanged: zoomChanged}
                );
            }
            
            bounds = this.baseLayer.getExtent();
            
            for (var i=this.layers.length-1; i>=0; --i) {
                var layer = this.layers[i];
                if (layer !== this.baseLayer && !layer.isBaseLayer) {
                    var inRange = layer.calculateInRange();
                    if (layer.inRange != inRange) {
                        // the inRange property has changed. If the layer is
                        // no longer in range, we turn it off right away. If
                        // the layer is no longer out of range, the moveTo
                        // call below will turn on the layer.
                        layer.inRange = inRange;
                        if (!inRange) {
                            layer.display(false);
                        }
                        this.events.triggerEvent("changelayer", {
                            layer: layer, property: "visibility"
                        });
                    }
                    if (inRange && layer.visibility) {
                        layer.moveTo(bounds, zoomChanged, options.dragging);
                        options.dragging || layer.events.triggerEvent(
                            "moveend", {zoomChanged: zoomChanged}
                        );
                    }
                }                
            }
            
            this.events.triggerEvent("move");
            dragging || this.events.triggerEvent("moveend");

            if (zoomChanged) {
                //redraw popups
                for (var i=0, len=this.popups.length; i<len; i++) {
                    this.popups[i].updatePosition();
                }
                this.events.triggerEvent("zoomend");
            }
        }
    },

    /** 
     * Method: centerLayerContainer
     * This function takes care to recenter the layerContainerDiv.
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>}
     */
    centerLayerContainer: function (lonlat) {
        var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
        var newPx = this.getViewPortPxFromLonLat(lonlat);

        if ((originPx != null) && (newPx != null)) {
            var oldLeft = this.layerContainerOriginPx.x;
            var oldTop = this.layerContainerOriginPx.y;
            var newLeft = Math.round(originPx.x - newPx.x);
            var newTop = Math.round(originPx.y - newPx.y);
            this.applyTransform(
                (this.layerContainerOriginPx.x = newLeft),
                (this.layerContainerOriginPx.y = newTop));
            var dx = oldLeft - newLeft;
            var dy = oldTop - newTop;
            this.minPx.x -= dx;
            this.maxPx.x -= dx;
            this.minPx.y -= dy;
            this.maxPx.y -= dy;
        }        
    },

    /**
     * Method: isValidZoomLevel
     * 
     * Parameters:
     * zoomLevel - {Integer}
     * 
     * Returns:
     * {Boolean} Whether or not the zoom level passed in is non-null and 
     *           within the min/max range of zoom levels.
     */
    isValidZoomLevel: function(zoomLevel) {
        return ( (zoomLevel != null) &&
                 (zoomLevel >= 0) && 
                 (zoomLevel < this.getNumZoomLevels()) );
    },
    
    /**
     * Method: isValidLonLat
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>}
     * 
     * Returns:
     * {Boolean} Whether or not the lonlat passed in is non-null and within
     *           the maxExtent bounds
     */
    isValidLonLat: function(lonlat) {
        var valid = false;
        if (lonlat != null) {
            var maxExtent = this.getMaxExtent();
            var worldBounds = this.baseLayer.wrapDateLine && maxExtent;
            valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds});
        }
        return valid;
    },

  /********************************************************/
  /*                                                      */
  /*                 Layer Options                        */
  /*                                                      */
  /*    Accessor functions to Layer Options parameters    */
  /*                                                      */
  /********************************************************/
    
    /**
     * APIMethod: getProjection
     * This method returns a string representing the projection. In 
     *     the case of projection support, this will be the srsCode which
     *     is loaded -- otherwise it will simply be the string value that
     *     was passed to the projection at startup.
     *
     * FIXME: In 3.0, we will remove getProjectionObject, and instead
     *     return a Projection object from this function. 
     * 
     * Returns:
     * {String} The Projection string from the base layer or null. 
     */
    getProjection: function() {
        var projection = this.getProjectionObject();
        return projection ? projection.getCode() : null;
    },
    
    /**
     * APIMethod: getProjectionObject
     * Returns the projection obect from the baselayer.
     *
     * Returns:
     * {<OpenLayers.Projection>} The Projection of the base layer.
     */
    getProjectionObject: function() {
        var projection = null;
        if (this.baseLayer != null) {
            projection = this.baseLayer.projection;
        }
        return projection;
    },
    
    /**
     * APIMethod: getMaxResolution
     * 
     * Returns:
     * {String} The Map's Maximum Resolution
     */
    getMaxResolution: function() {
        var maxResolution = null;
        if (this.baseLayer != null) {
            maxResolution = this.baseLayer.maxResolution;
        }
        return maxResolution;
    },
        
    /**
     * APIMethod: getMaxExtent
     *
     * Parameters:
     * options - {Object} 
     * 
     * Allowed Options:
     * restricted - {Boolean} If true, returns restricted extent (if it is 
     *     available.)
     *
     * Returns:
     * {<OpenLayers.Bounds>} The maxExtent property as set on the current 
     *     baselayer, unless the 'restricted' option is set, in which case
     *     the 'restrictedExtent' option from the map is returned (if it
     *     is set).
     */
    getMaxExtent: function (options) {
        var maxExtent = null;
        if(options && options.restricted && this.restrictedExtent){
            maxExtent = this.restrictedExtent;
        } else if (this.baseLayer != null) {
            maxExtent = this.baseLayer.maxExtent;
        }        
        return maxExtent;
    },
    
    /**
     * APIMethod: getNumZoomLevels
     * 
     * Returns:
     * {Integer} The total number of zoom levels that can be displayed by the 
     *           current baseLayer.
     */
    getNumZoomLevels: function() {
        var numZoomLevels = null;
        if (this.baseLayer != null) {
            numZoomLevels = this.baseLayer.numZoomLevels;
        }
        return numZoomLevels;
    },

  /********************************************************/
  /*                                                      */
  /*                 Baselayer Functions                  */
  /*                                                      */
  /*    The following functions, all publicly exposed     */
  /*       in the API?, are all merely wrappers to the    */
  /*       the same calls on whatever layer is set as     */
  /*                the current base layer                */
  /*                                                      */
  /********************************************************/

    /**
     * APIMethod: getExtent
     * 
     * Returns:
     * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
     *                       bounds of the current viewPort. 
     *                       If no baselayer is set, returns null.
     */
    getExtent: function () {
        var extent = null;
        if (this.baseLayer != null) {
            extent = this.baseLayer.getExtent();
        }
        return extent;
    },

    /**
     * APIMethod: getResolution
     * 
     * Returns:
     * {Float} The current resolution of the map. 
     *         If no baselayer is set, returns null.
     */
    getResolution: function () {
        var resolution = null;
        if (this.baseLayer != null) {
            resolution = this.baseLayer.getResolution();
        } else if(this.allOverlays === true && this.layers.length > 0) {
            // while adding the 1st layer to the map in allOverlays mode,
            // this.baseLayer is not set yet when we need the resolution
            // for calculateInRange.
            resolution = this.layers[0].getResolution();
        }
        return resolution;
    },

    /**
     * APIMethod: getUnits
     * 
     * Returns:
     * {Float} The current units of the map. 
     *         If no baselayer is set, returns null.
     */
    getUnits: function () {
        var units = null;
        if (this.baseLayer != null) {
            units = this.baseLayer.units;
        }
        return units;
    },

     /**
      * APIMethod: getScale
      * 
      * Returns:
      * {Float} The current scale denominator of the map. 
      *         If no baselayer is set, returns null.
      */
    getScale: function () {
        var scale = null;
        if (this.baseLayer != null) {
            var res = this.getResolution();
            var units = this.baseLayer.units;
            scale = OpenLayers.Util.getScaleFromResolution(res, units);
        }
        return scale;
    },


    /**
     * APIMethod: getZoomForExtent
     * 
     * Parameters: 
     * bounds - {<OpenLayers.Bounds>}
     * closest - {Boolean} Find the zoom level that most closely fits the 
     *     specified bounds. Note that this may result in a zoom that does 
     *     not exactly contain the entire extent.
     *     Default is false.
     * 
     * Returns:
     * {Integer} A suitable zoom level for the specified bounds.
     *           If no baselayer is set, returns null.
     */
    getZoomForExtent: function (bounds, closest) {
        var zoom = null;
        if (this.baseLayer != null) {
            zoom = this.baseLayer.getZoomForExtent(bounds, closest);
        }
        return zoom;
    },

    /**
     * APIMethod: getResolutionForZoom
     * 
     * Parameters:
     * zoom - {Float}
     * 
     * Returns:
     * {Float} A suitable resolution for the specified zoom.  If no baselayer
     *     is set, returns null.
     */
    getResolutionForZoom: function(zoom) {
        var resolution = null;
        if(this.baseLayer) {
            resolution = this.baseLayer.getResolutionForZoom(zoom);
        }
        return resolution;
    },

    /**
     * APIMethod: getZoomForResolution
     * 
     * Parameters:
     * resolution - {Float}
     * closest - {Boolean} Find the zoom level that corresponds to the absolute 
     *     closest resolution, which may result in a zoom whose corresponding
     *     resolution is actually smaller than we would have desired (if this
     *     is being called from a getZoomForExtent() call, then this means that
     *     the returned zoom index might not actually contain the entire 
     *     extent specified... but it'll be close).
     *     Default is false.
     * 
     * Returns:
     * {Integer} A suitable zoom level for the specified resolution.
     *           If no baselayer is set, returns null.
     */
    getZoomForResolution: function(resolution, closest) {
        var zoom = null;
        if (this.baseLayer != null) {
            zoom = this.baseLayer.getZoomForResolution(resolution, closest);
        }
        return zoom;
    },

  /********************************************************/
  /*                                                      */
  /*                  Zooming Functions                   */
  /*                                                      */
  /*    The following functions, all publicly exposed     */
  /*       in the API, are all merely wrappers to the     */
  /*               the setCenter() function               */
  /*                                                      */
  /********************************************************/
  
    /** 
     * APIMethod: zoomTo
     * Zoom to a specific zoom level. Zooming will be animated unless the map
     * is configured with {zoomMethod: null}. To zoom without animation, use
     * <setCenter> without a lonlat argument.
     * 
     * Parameters:
     * zoom - {Integer}
     */
    zoomTo: function(zoom, xy) {
        // non-API arguments:
        // xy - {<OpenLayers.Pixel>} optional zoom origin
        
        var map = this;
        if (map.isValidZoomLevel(zoom)) {
            if (map.baseLayer.wrapDateLine) {
                zoom = map.adjustZoom(zoom);
            }
            if (zoom && map.baseLayer.CLASS_NAME != "OpenLayers.Layer.MapGuide") {
                if (zoom >= map.baseLayer.MAX_ZOOM_LEVEL)
                    map.baseLayer.setVisibility(false);
                else
                    map.baseLayer.setVisibility(true);
            }
            if (map.zoomTween) {
                var currentRes = map.getResolution(),
                    targetRes = map.getResolutionForZoom(zoom),
                    start = {scale: 1},
                    end = {scale: currentRes / targetRes};
                if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {
                    // update the end scale, and reuse the running zoomTween
                    map.zoomTween.finish = {
                        scale: map.zoomTween.finish.scale * end.scale
                    };
                } else {
                    if (!xy) {
                        var size = map.getSize();
                        xy = {x: size.w / 2, y: size.h / 2};
                    }
                    map.zoomTween.start(start, end, map.zoomDuration, {
                        minFrameRate: 50, // don't spend much time zooming
                        callbacks: {
                            eachStep: function(data) {
                                var containerOrigin = map.layerContainerOriginPx,
                                    scale = data.scale,
                                    dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
                                    dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
                                map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
                            },
                            done: function(data) {
                                map.applyTransform();
                                var resolution = map.getResolution() / data.scale,
                                    zoom = map.getZoomForResolution(resolution, true)
                                map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);
                            }
                        }
                    });
                }
            } else {
                var center = xy ?
                    map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
                    null;
                map.setCenter(center, zoom);
            }
        }
    },
        
    /**
     * APIMethod: zoomIn
     * 
     */
    zoomIn: function() {
        this.zoomTo(this.getZoom() + 1);
    },
    
    /**
     * APIMethod: zoomOut
     * 
     */
    zoomOut: function() {
        this.zoomTo(this.getZoom() - 1);
    },

    /**
     * APIMethod: zoomToExtent
     * Zoom to the passed in bounds, recenter
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>|Array} If provided as an array, the array
     *     should consist of four values (left, bottom, right, top).
     * closest - {Boolean} Find the zoom level that most closely fits the 
     *     specified bounds. Note that this may result in a zoom that does 
     *     not exactly contain the entire extent.
     *     Default is false.
     * 
     */
    zoomToExtent: function(bounds, closest) {
        if (!(bounds instanceof OpenLayers.Bounds)) {
            bounds = new OpenLayers.Bounds(bounds);
        }
        var center = bounds.getCenterLonLat();
        if (this.baseLayer.wrapDateLine) {
            var maxExtent = this.getMaxExtent();

            //fix straddling bounds (in the case of a bbox that straddles the 
            // dateline, it's left and right boundaries will appear backwards. 
            // we fix this by allowing a right value that is greater than the
            // max value at the dateline -- this allows us to pass a valid 
            // bounds to calculate zoom)
            //
            bounds = bounds.clone();
            while (bounds.right < bounds.left) {
                bounds.right += maxExtent.getWidth();
            }
            //if the bounds was straddling (see above), then the center point 
            // we got from it was wrong. So we take our new bounds and ask it
            // for the center.
            //
            center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
        }
        this.setCenter(center, this.getZoomForExtent(bounds, closest));
    },

    /** 
     * APIMethod: zoomToMaxExtent
     * Zoom to the full extent and recenter.
     *
     * Parameters:
     * options - {Object}
     * 
     * Allowed Options:
     * restricted - {Boolean} True to zoom to restricted extent if it is 
     *     set. Defaults to true.
     */
    zoomToMaxExtent: function(options) {
        //restricted is true by default
        var restricted = (options) ? options.restricted : true;

        var maxExtent = this.getMaxExtent({
            'restricted': restricted 
        });
        this.zoomToExtent(maxExtent);
    },

    /** 
     * APIMethod: zoomToScale
     * Zoom to a specified scale 
     * 
     * Parameters:
     * scale - {float}
     * closest - {Boolean} Find the zoom level that most closely fits the 
     *     specified scale. Note that this may result in a zoom that does 
     *     not exactly contain the entire extent.
     *     Default is false.
     * 
     */
    zoomToScale: function(scale, closest) {
        var res = OpenLayers.Util.getResolutionFromScale(scale, 
                                                         this.baseLayer.units);

        var halfWDeg = (this.size.w * res) / 2;
        var halfHDeg = (this.size.h * res) / 2;
        var center = this.getCachedCenter();

        var extent = new OpenLayers.Bounds(center.lon - halfWDeg,
                                           center.lat - halfHDeg,
                                           center.lon + halfWDeg,
                                           center.lat + halfHDeg);
        this.zoomToExtent(extent, closest);
    },
    
  /********************************************************/
  /*                                                      */
  /*             Translation Functions                    */
  /*                                                      */
  /*      The following functions translate between       */
  /*           LonLat, LayerPx, and ViewPortPx            */
  /*                                                      */
  /********************************************************/
      
  //
  // TRANSLATION: LonLat <-> ViewPortPx
  //

    /**
     * Method: getLonLatFromViewPortPx
     * 
     * Parameters:
     * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
     *                                          an object with a 'x'
     *                                          and 'y' properties.
     * 
     * Returns:
     * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view 
     *                       port <OpenLayers.Pixel>, translated into lon/lat
     *                       by the current base layer.
     */
    getLonLatFromViewPortPx: function (viewPortPx) {
        var lonlat = null; 
        if (this.baseLayer != null) {
            lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
        }
        return lonlat;
    },

    /**
     * APIMethod: getViewPortPxFromLonLat
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>}
     * 
     * Returns:
     * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in 
     *                      <OpenLayers.LonLat>, translated into view port 
     *                      pixels by the current base layer.
     */
    getViewPortPxFromLonLat: function (lonlat) {
        var px = null; 
        if (this.baseLayer != null) {
            px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
        }
        return px;
    },

    /**
     * Method: getZoomTargetCenter
     *
     * Parameters:
     * xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
     * resolution - {Float} The resolution we want to get the center for
     *
     * Returns:
     * {<OpenLayers.LonLat>} The location of the map center after the
     *     transformation described by the origin xy and the target resolution.
     */
    getZoomTargetCenter: function (xy, resolution) {
        var lonlat = null,
            size = this.getSize(),
            deltaX  = size.w/2 - xy.x,
            deltaY  = xy.y - size.h/2,
            zoomPoint = this.getLonLatFromPixel(xy);
        if (zoomPoint) {
            lonlat = new OpenLayers.LonLat(
                zoomPoint.lon + deltaX * resolution,
                zoomPoint.lat + deltaY * resolution
            );
        }
        return lonlat;
    },
        
  //
  // CONVENIENCE TRANSLATION FUNCTIONS FOR API
  //

    /**
     * APIMethod: getLonLatFromPixel
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
     *                                  a 'x' and 'y' properties.
     *
     * Returns:
     * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
     *                       OpenLayers.Pixel, translated into lon/lat by the 
     *                       current base layer
     */
    getLonLatFromPixel: function (px) {
        return this.getLonLatFromViewPortPx(px);
    },

    /**
     * APIMethod: getPixelFromLonLat
     * Returns a pixel location given a map location.  The map location is
     *     translated to an integer pixel location (in viewport pixel
     *     coordinates) by the current base layer.
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>} A map location.
     * 
     * Returns: 
     * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the 
     *     <OpenLayers.LonLat> translated into view port pixels by the current
     *     base layer.
     */
    getPixelFromLonLat: function (lonlat) {
        var px = this.getViewPortPxFromLonLat(lonlat);
        px.x = Math.round(px.x);
        px.y = Math.round(px.y);
        return px;
    },
    
    /**
     * Method: getGeodesicPixelSize
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>} The pixel to get the geodesic length for. If
     *     not provided, the center pixel of the map viewport will be used.
     * 
     * Returns:
     * {<OpenLayers.Size>} The geodesic size of the pixel in kilometers.
     */
    getGeodesicPixelSize: function(px) {
        var lonlat = px ? this.getLonLatFromPixel(px) : (
            this.getCachedCenter() || new OpenLayers.LonLat(0, 0));
        var res = this.getResolution();
        var left = lonlat.add(-res / 2, 0);
        var right = lonlat.add(res / 2, 0);
        var bottom = lonlat.add(0, -res / 2);
        var top = lonlat.add(0, res / 2);
        var dest = new OpenLayers.Projection("EPSG:4326");
        var source = this.getProjectionObject() || dest;
        if(!source.equals(dest)) {
            left.transform(source, dest);
            right.transform(source, dest);
            bottom.transform(source, dest);
            top.transform(source, dest);
        }
        
        return new OpenLayers.Size(
            OpenLayers.Util.distVincenty(left, right),
            OpenLayers.Util.distVincenty(bottom, top)
        );
    },



  //
  // TRANSLATION: ViewPortPx <-> LayerPx
  //

    /**
     * APIMethod: getViewPortPxFromLayerPx
     * 
     * Parameters:
     * layerPx - {<OpenLayers.Pixel>}
     * 
     * Returns:
     * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel 
     *                      coordinates
     */
    getViewPortPxFromLayerPx:function(layerPx) {
        var viewPortPx = null;
        if (layerPx != null) {
            var dX = this.layerContainerOriginPx.x;
            var dY = this.layerContainerOriginPx.y;
            viewPortPx = layerPx.add(dX, dY);            
        }
        return viewPortPx;
    },
    
    /**
     * APIMethod: getLayerPxFromViewPortPx
     * 
     * Parameters:
     * viewPortPx - {<OpenLayers.Pixel>}
     * 
     * Returns:
     * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel 
     *                      coordinates
     */
    getLayerPxFromViewPortPx:function(viewPortPx) {
        var layerPx = null;
        if (viewPortPx != null) {
            var dX = -this.layerContainerOriginPx.x;
            var dY = -this.layerContainerOriginPx.y;
            layerPx = viewPortPx.add(dX, dY);
            if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
                layerPx = null;
            }
        }
        return layerPx;
    },
    
  //
  // TRANSLATION: LonLat <-> LayerPx
  //

    /**
     * Method: getLonLatFromLayerPx
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     *
     * Returns:
     * {<OpenLayers.LonLat>}
     */
    getLonLatFromLayerPx: function (px) {
       //adjust for displacement of layerContainerDiv
       px = this.getViewPortPxFromLayerPx(px);
       return this.getLonLatFromViewPortPx(px);         
    },
    
    /**
     * APIMethod: getLayerPxFromLonLat
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>} lonlat
     *
     * Returns:
     * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in 
     *                      <OpenLayers.LonLat>, translated into layer pixels 
     *                      by the current base layer
     */
    getLayerPxFromLonLat: function (lonlat) {
       //adjust for displacement of layerContainerDiv
       var px = this.getPixelFromLonLat(lonlat);
       return this.getLayerPxFromViewPortPx(px);         
    },

    /**
     * Method: applyTransform
     * Applies the given transform to the <layerContainerDiv>. This method has
     * a 2-stage fallback from translate3d/scale3d via translate/scale to plain
     * style.left/style.top, in which case no scaling is supported.
     *
     * Parameters:
     * x - {Number} x parameter for the translation. Defaults to the x value of
     *     the map's <layerContainerOriginPx>
     * y - {Number} y parameter for the translation. Defaults to the y value of
     *     the map's <layerContainerOriginPx>
     * scale - {Number} scale. Defaults to 1 if not provided.
     */
     applyTransform: function(x, y, scale) {
         scale = scale || 1;
         var origin = this.layerContainerOriginPx,
             needTransform = scale !== 1;
         x = x || origin.x;
         y = y || origin.y;
            
         var style = this.layerContainerDiv.style,
             transform = this.applyTransform.transform,
             template = this.applyTransform.template;
        
         if (transform === undefined) {
             transform = OpenLayers.Util.vendorPrefix.style('transform');
             this.applyTransform.transform = transform;
             if (transform) {
                 // Try translate3d, but only if the viewPortDiv has a transform
                 // defined in a stylesheet
                 var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
                     OpenLayers.Util.vendorPrefix.css('transform'));
                 if (!computedStyle || computedStyle !== 'none') {
                     template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
                     style[transform] = [template[0], '0,0', template[1]].join('');
                 }
                 // If no transform is defined in the stylesheet or translate3d
                 // does not stick, use translate and scale
                 if (!template || !~style[transform].indexOf(template[0])) {
                     template = ['translate(', ') ', 'scale(', ')'];
                 }
                 this.applyTransform.template = template;
             }
         }
         
         // If we do 3d transforms, we always want to use them. If we do 2d
         // transforms, we only use them when we need to.
         if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
             // Our 2d transforms are combined with style.left and style.top, so
             // adjust x and y values and set the origin as left and top
             if (needTransform === true && template[0] === 'translate(') {
                 x -= origin.x;
                 y -= origin.y;
                 style.left = origin.x + 'px';
                 style.top = origin.y + 'px';
             }
             style[transform] = [
                 template[0], x, 'px,', y, 'px', template[1],
                 template[2], scale, ',', scale, template[3]
             ].join('');
         } else {
             style.left = x + 'px';
             style.top = y + 'px';
             // We previously might have had needTransform, so remove transform
             if (transform !== null) {
                 style[transform] = '';
             }
         }
     },
    
    CLASS_NAME: "OpenLayers.Map"
});

/**
 * Constant: TILE_WIDTH
 * {Integer} 256 Default tile width (unless otherwise specified)
 */
OpenLayers.Map.TILE_WIDTH = 256;
/**
 * Constant: TILE_HEIGHT
 * {Integer} 256 Default tile height (unless otherwise specified)
 */
OpenLayers.Map.TILE_HEIGHT = 256;
/* ======================================================================
    OpenLayers/Layer.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Map.js
 * @requires OpenLayers/Projection.js
 */

/**
 * Class: OpenLayers.Layer
 */
OpenLayers.Layer = OpenLayers.Class({

    /**
     * APIProperty: id
     * {String}
     */
    id: null,

    /** 
     * APIProperty: name
     * {String}
     */
    name: null,

    /** 
     * APIProperty: div
     * {DOMElement}
     */
    div: null,

    /**
     * APIProperty: opacity
     * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default
     * is 1.
     */
    opacity: 1,

    /**
     * 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: RESOLUTION_PROPERTIES
     * {Array} The properties that are used for calculating resolutions
     *     information.
     */
    RESOLUTION_PROPERTIES: [
        'scales', 'resolutions',
        'maxScale', 'minScale',
        'maxResolution', 'minResolution',
        'numZoomLevels', 'maxZoomLevel'
    ],

    /**
     * APIProperty: events
     * {<OpenLayers.Events>}
     *
     * 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:
     * loadstart - Triggered when layer loading starts.  When using a Vector 
     *     layer with a Fixed or BBOX strategy, the event object includes 
     *     a *filter* property holding the OpenLayers.Filter used when 
     *     calling read on the protocol.
     * loadend - Triggered when layer loading ends.  When using a Vector layer
     *     with a Fixed or BBOX strategy, the event object includes a 
     *     *response* property holding an OpenLayers.Protocol.Response object.
     * visibilitychanged - Triggered when the layer's visibility property is
     *     changed, e.g. by turning the layer on or off in the layer switcher.
     *     Note that the actual visibility of the layer can also change if it
     *     gets out of range (see <calculateInRange>). If you also want to catch
     *     these cases, register for the map's 'changelayer' event instead.
     * move - Triggered when layer moves (triggered with every mousemove
     *     during a drag).
     * moveend - Triggered when layer is done moving, object passed as
     *     argument has a zoomChanged boolean property which tells that the
     *     zoom has changed.
     * added - Triggered after the layer is added to a map.  Listeners will
     *     receive an object with a *map* property referencing the map and a
     *     *layer* property referencing the layer.
     * removed - Triggered after the layer is removed from the map.  Listeners
     *     will receive an object with a *map* property referencing the map and
     *     a *layer* property referencing the layer.
     */
    events: null,

    /**
     * APIProperty: map
     * {<OpenLayers.Map>} This variable is set when the layer is added to 
     *     the map, via the accessor function setMap().
     */
    map: null,
    
    /**
     * APIProperty: isBaseLayer
     * {Boolean} Whether or not the layer is a base layer. This should be set 
     *     individually by all subclasses. Default is false
     */
    isBaseLayer: false,
 
    /**
     * Property: alpha
     * {Boolean} The layer's images have an alpha channel.  Default is false.
     */
    alpha: false,

    /** 
     * APIProperty: displayInLayerSwitcher
     * {Boolean} Display the layer's name in the layer switcher.  Default is
     *     true.
     */
    displayInLayerSwitcher: true,

    /**
     * APIProperty: visibility
     * {Boolean} The layer should be displayed in the map.  Default is true.
     */
    visibility: true,

    /**
     * APIProperty: attribution
     * {String} Attribution string, displayed when an 
     *     <OpenLayers.Control.Attribution> has been added to the map.
     */
    attribution: null, 

    /** 
     * Property: inRange
     * {Boolean} The current map resolution is within the layer's min/max 
     *     range. This is set in <OpenLayers.Map.setCenter> whenever the zoom 
     *     changes.
     */
    inRange: false,
    
    /**
     * Propery: imageSize
     * {<OpenLayers.Size>} For layers with a gutter, the image is larger than 
     *     the tile by twice the gutter in each dimension.
     */
    imageSize: null,
    
  // OPTIONS

    /** 
     * Property: options
     * {Object} An optional object whose properties will be set on the layer.
     *     Any of the layer properties can be set as a property of the options
     *     object and sent to the constructor when the layer is created.
     */
    options: null,

    /**
     * APIProperty: eventListeners
     * {Object} If set as an option at construction, the eventListeners
     *     object will be registered with <OpenLayers.Events.on>.  Object
     *     structure must be a listeners object as shown in the example for
     *     the events.on method.
     */
    eventListeners: null,

    /**
     * APIProperty: gutter
     * {Integer} Determines the width (in pixels) of the gutter around image
     *     tiles to ignore.  By setting this property to a non-zero value,
     *     images will be requested that are wider and taller than the tile
     *     size by a value of 2 x gutter.  This allows artifacts of rendering
     *     at tile edges to be ignored.  Set a gutter value that is equal to
     *     half the size of the widest symbol that needs to be displayed.
     *     Defaults to zero.  Non-tiled layers always have zero gutter.
     */ 
    gutter: 0, 

    /**
     * APIProperty: projection
     * {<OpenLayers.Projection>} or {<String>} Specifies the projection of the layer.
     *     Can be set in the layer options. If not specified in the layer options,
     *     it is set to the default projection specified in the map,
     *     when the layer is added to the map.
     *     Projection along with default maxExtent and resolutions
     *     are set automatically with commercial baselayers in EPSG:3857,
     *     such as Google, Bing and OpenStreetMap, and do not need to be specified.
     *     Otherwise, if specifying projection, also set maxExtent,
     *     maxResolution or resolutions as appropriate.
     *     When using vector layers with strategies, layer projection should be set
     *     to the projection of the source data if that is different from the map default.
     * 
     *     Can be either a string or an <OpenLayers.Projection> object;
     *     if a string is passed, will be converted to an object when
     *     the layer is added to the map.
     * 
     */
    projection: null,    
    
    /**
     * APIProperty: units
     * {String} The layer map units.  Defaults to null.  Possible values
     *     are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
     *     Normally taken from the projection.
     *     Only required if both map and layers do not define a projection,
     *     or if they define a projection which does not define units.
     */
    units: null,

    /**
     * APIProperty: scales
     * {Array}  An array of map scales in descending order.  The values in the
     *     array correspond to the map scale denominator.  Note that these
     *     values only make sense if the display (monitor) resolution of the
     *     client is correctly guessed by whomever is configuring the
     *     application.  In addition, the units property must also be set.
     *     Use <resolutions> instead wherever possible.
     */
    scales: null,

    /**
     * APIProperty: resolutions
     * {Array} A list of map resolutions (map units per pixel) in descending
     *     order.  If this is not set in the layer constructor, it will be set
     *     based on other resolution related properties (maxExtent,
     *     maxResolution, maxScale, etc.).
     */
    resolutions: null,
    
    /**
     * APIProperty: maxExtent
     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
     *     should consist of four values (left, bottom, right, top).
     *     The maximum extent for the layer.  Defaults to null.
     * 
     *     The center of these bounds will not stray outside
     *     of the viewport extent during panning.  In addition, if
     *     <displayOutsideMaxExtent> is set to false, data will not be
     *     requested that falls completely outside of these bounds.
     */
    maxExtent: null,
    
    /**
     * APIProperty: minExtent
     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
     *     should consist of four values (left, bottom, right, top).
     *     The minimum extent for the layer.  Defaults to null.
     */
    minExtent: null,
    
    /**
     * APIProperty: maxResolution
     * {Float} Default max is 360 deg / 256 px, which corresponds to
     *     zoom level 0 on gmaps.  Specify a different value in the layer 
     *     options if you are not using the default <OpenLayers.Map.tileSize>
     *     and displaying the whole world.
     */
    maxResolution: null,

    /**
     * APIProperty: minResolution
     * {Float}
     */
    minResolution: null,

    /**
     * APIProperty: numZoomLevels
     * {Integer}
     */
    numZoomLevels: null,
    
    /**
     * APIProperty: minScale
     * {Float}
     */
    minScale: null,
    
    /**
     * APIProperty: maxScale
     * {Float}
     */
    maxScale: null,

    /**
     * APIProperty: displayOutsideMaxExtent
     * {Boolean} Request map tiles that are completely outside of the max 
     *     extent for this layer. Defaults to false.
     */
    displayOutsideMaxExtent: false,

    /**
     * APIProperty: wrapDateLine
     * {Boolean} Wraps the world at the international dateline, so the map can
     * be panned infinitely in longitudinal direction. Only use this on the
     * base layer, and only if the layer's maxExtent equals the world bounds.
     * #487 for more info.   
     */
    wrapDateLine: false,
    
    /**
     * Property: metadata
     * {Object} This object can be used to store additional information on a
     *     layer object.
     */
    metadata: null,
    
    /**
     * Constructor: OpenLayers.Layer
     *
     * Parameters:
     * name - {String} The layer name
     * options - {Object} Hashtable of extra options to tag onto the layer
     */
    initialize: function(name, options) {

        this.metadata = {};
        
        options = OpenLayers.Util.extend({}, options);
        // make sure we respect alwaysInRange if set on the prototype
        if (this.alwaysInRange != null) {
            options.alwaysInRange = this.alwaysInRange;
        }
        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.div.dir = "ltr";

            this.events = new OpenLayers.Events(this, this.div);
            if(this.eventListeners instanceof Object) {
                this.events.on(this.eventListeners);
            }

        }
    },
    
    /**
     * Method: destroy
     * Destroy is a destructor: this is to alleviate cyclic references which
     *     the Javascript garbage cleaner can not take care of on its own.
     *
     * Parameters:
     * setNewBaseLayer - {Boolean} Set a new base layer when this layer has
     *     been destroyed.  Default is 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);
            }
            this.events.destroy();
        }
        this.eventListeners = null;
        this.events = null;
    },
    
   /**
    * Method: clone
    *
    * Parameters:
    * obj - {<OpenLayers.Layer>} The layer to be cloned
    *
    * Returns:
    * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
    */
    clone: function (obj) {
        
        if (obj == null) {
            obj = new OpenLayers.Layer(this.name, this.getOptions());
        }
        
        // catch any randomly tagged-on properties
        OpenLayers.Util.applyDefaults(obj, this);
        
        // a cloned layer should never have its map property set
        //  because it has not been added to a map yet. 
        obj.map = null;
        
        return obj;
    },
    
    /**
     * Method: getOptions
     * Extracts an object from the layer with the properties that were set as
     *     options, but updates them with the values currently set on the
     *     instance.
     * 
     * Returns:
     * {Object} the <options> of the layer, representing the current state.
     */
    getOptions: function() {
        var options = {};
        for(var o in this.options) {
            options[o] = this[o];
        }
        return options;
    },
    
    /** 
     * APIMethod: setName
     * Sets the new layer name for this layer.  Can trigger a changelayer event
     *     on the map.
     *
     * Parameters:
     * newName - {String} The new name.
     */
    setName: function(newName) {
        if (newName != this.name) {
            this.name = newName;
            if (this.map != null) {
                this.map.events.triggerEvent("changelayer", {
                    layer: this,
                    property: "name"
                });
            }
        }
    },    
    
   /**
    * APIMethod: addOptions
    * 
    * Parameters:
    * newOptions - {Object}
    * reinitialize - {Boolean} If set to true, and if resolution options of the
    *     current baseLayer were changed, the map will be recentered to make
    *     sure that it is displayed with a valid resolution, and a
    *     changebaselayer event will be triggered.
    */
    addOptions: function (newOptions, reinitialize) {

        if (this.options == null) {
            this.options = {};
        }
        
        if (newOptions) {
            // make sure this.projection references a projection object
            if(typeof newOptions.projection == "string") {
                newOptions.projection = new OpenLayers.Projection(newOptions.projection);
            }
            if (newOptions.projection) {
                // get maxResolution, units and maxExtent from projection defaults if
                // they are not defined already
                OpenLayers.Util.applyDefaults(newOptions,
                    OpenLayers.Projection.defaults[newOptions.projection.getCode()]);
            }
            // allow array for extents
            if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) {
                newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent);
            }
            if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) {
                newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent);
            }
        }

        // update our copy for clone
        OpenLayers.Util.extend(this.options, newOptions);

        // add new options to this
        OpenLayers.Util.extend(this, newOptions);
        
        // get the units from the projection, if we have a projection
        // and it it has units
        if(this.projection && this.projection.getUnits()) {
            this.units = this.projection.getUnits();
        }

        // re-initialize resolutions if necessary, i.e. if any of the
        // properties of the "properties" array defined below is set
        // in the new options
        if(this.map) {
            // store current resolution so we can try to restore it later
            var resolution = this.map.getResolution();
            var properties = this.RESOLUTION_PROPERTIES.concat(
                ["projection", "units", "minExtent", "maxExtent"]
            );
            for(var o in newOptions) {
                if(newOptions.hasOwnProperty(o) &&
                   OpenLayers.Util.indexOf(properties, o) >= 0) {

                    this.initResolutions();
                    if (reinitialize && this.map.baseLayer === this) {
                        // update map position, and restore previous resolution
                        this.map.setCenter(this.map.getCenter(),
                            this.map.getZoomForResolution(resolution),
                            false, true
                        );
                        // trigger a changebaselayer event to make sure that
                        // all controls (especially
                        // OpenLayers.Control.PanZoomBar) get notified of the
                        // new options
                        this.map.events.triggerEvent("changebaselayer", {
                            layer: this
                        });
                    }
                    break;
                }
            }
        }
    },

    /**
     * APIMethod: onMapResize
     * This function can be implemented by subclasses
     */
    onMapResize: function() {
        //this function can be implemented by subclasses  
    },

    /**
     * APIMethod: redraw
     * Redraws the layer.  Returns true if the layer was redrawn, false if not.
     *
     * Returns:
     * {Boolean} The layer was redrawn.
     */
    redraw: function() {
        var redrawn = false;
        if (this.map) {

            // min/max Range may have changed
            this.inRange = this.calculateInRange();

            // map's center might not yet be set
            var extent = this.getExtent();

            if (extent && this.inRange && this.visibility) {
                var zoomChanged = true;
                this.moveTo(extent, zoomChanged, false);
                this.events.triggerEvent("moveend",
                    {"zoomChanged": zoomChanged});
                redrawn = true;
            }
        }
        return redrawn;
    },

    /**
     * Method: moveTo
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
     *     do some init work in that case.
     * dragging - {Boolean}
     */
    moveTo:function(bounds, zoomChanged, dragging) {
        var display = this.visibility;
        if (!this.isBaseLayer) {
            display = display && this.inRange;
        }
        this.display(display);
    },

    /**
     * Method: moveByPx
     * Move the layer based on pixel vector. To be implemented by subclasses.
     *
     * Parameters:
     * dx - {Number} The x coord of the displacement vector.
     * dy - {Number} The y coord of the displacement vector.
     */
    moveByPx: function(dx, dy) {
    },

    /**
     * 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. 
     * 
     *     Here we take care to bring over any of the necessary default 
     *     properties from the map. 
     * 
     * Parameters:
     * map - {<OpenLayers.Map>}
     */
    setMap: function(map) {
        if (this.map == null) {
        
            this.map = map;
            
            // grab some essential layer data from the map if it hasn't already
            //  been set
            this.maxExtent = this.maxExtent || this.map.maxExtent;
            this.minExtent = this.minExtent || this.map.minExtent;

            this.projection = this.projection || this.map.projection;
            if (typeof this.projection == "string") {
                this.projection = new OpenLayers.Projection(this.projection);
            }

            // Check the projection to see if we can get units -- if not, refer
            // to properties.
            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";
            }
            
            // deal with gutters
            this.setTileSize();
        }
    },
    
    /**
     * Method: afterAdd
     * Called at the end of the map.addLayer sequence.  At this point, the map
     *     will have a base layer.  To be overridden by subclasses.
     */
    afterAdd: function() {
    },
    
    /**
     * APIMethod: removeMap
     * Just as setMap() allows each layer the possibility to take a 
     *     personalized action on being added to the map, removeMap() allows
     *     each layer to take a personalized action on being removed from it. 
     *     For now, this will be mostly unused, except for the EventPane layer,
     *     which needs this hook so that it can remove the special invisible
     *     pane. 
     * 
     * Parameters:
     * map - {<OpenLayers.Map>}
     */
    removeMap: function(map) {
        //to be overridden by subclasses
    },
    
    /**
     * APIMethod: getImageSize
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>} optional tile bounds, can be used
     *     by subclasses that have to deal with different tile sizes at the
     *     layer extent edges (e.g. Zoomify)
     * 
     * Returns:
     * {<OpenLayers.Size>} The size that the image should be, taking into 
     *     account gutters.
     */ 
    getImageSize: function(bounds) { 
        return (this.imageSize || this.tileSize); 
    },    
  
    /**
     * APIMethod: setTileSize
     * Set the tile size based on the map size.  This also sets layer.imageSize
     *     or use by Tile.Image.
     * 
     * Parameters:
     * size - {<OpenLayers.Size>}
     */
    setTileSize: function(size) {
        var tileSize = (size) ? size :
                                ((this.tileSize) ? this.tileSize :
                                                   this.map.getTileSize());
        this.tileSize = tileSize;
        if(this.gutter) {
          // layers with gutters need non-null tile sizes
          //if(tileSize == null) {
          //    OpenLayers.console.error("Error in layer.setMap() for " +
          //                              this.name + ": layers with " +
          //                              "gutters need non-null tile sizes");
          //}
            this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter), 
                                                 tileSize.h + (2*this.gutter)); 
        }
    },

    /**
     * APIMethod: getVisibility
     * 
     * Returns:
     * {Boolean} The layer should be displayed (if in range).
     */
    getVisibility: function() {
        return this.visibility;
    },

    /** 
     * APIMethod: setVisibility
     * Set the visibility flag for the layer and hide/show & redraw 
     *     accordingly. Fire event unless otherwise specified
     * 
     * Note that visibility is no longer simply whether or not the layer's
     *     style.display is set to "block". Now we store a 'visibility' state 
     *     property on the layer class, this allows us to remember whether or 
     *     not we *desire* for a layer to be visible. In the case where the 
     *     map's resolution is out of the layer's range, this desire may be 
     *     subverted.
     * 
     * Parameters:
     * visibility - {Boolean} Whether or not to display the layer (if in range)
     */
    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");
        }
    },

    /** 
     * APIMethod: display
     * Hide or show the Layer. This is designed to be used internally, and 
     *     is not generally the way to enable or disable the layer. For that,
     *     use the setVisibility function instead..
     * 
     * Parameters:
     * display - {Boolean}
     */
    display: function(display) {
        if (display != (this.div.style.display != "none")) {
            this.div.style.display = (display && this.calculateInRange()) ? "block" : "none";
        }
    },

    /**
     * APIMethod: calculateInRange
     * 
     * Returns:
     * {Boolean} The layer is displayable at the current map's current
     *     resolution. Note that if 'alwaysInRange' is true for the layer, 
     *     this function will always return true.
     */
    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;
    },

    /** 
     * APIMethod: setIsBaseLayer
     * 
     * Parameters:
     * isBaseLayer - {Boolean}
     */
    setIsBaseLayer: function(isBaseLayer) {
        if (isBaseLayer != this.isBaseLayer) {
            this.isBaseLayer = isBaseLayer;
            if (this.map != null) {
                this.map.events.triggerEvent("changebaselayer", {
                    layer: this
                });
            }
        }
    },

  /********************************************************/
  /*                                                      */
  /*                 Baselayer Functions                  */
  /*                                                      */
  /********************************************************/
  
    /** 
     * Method: initResolutions
     * This method's responsibility is to set up the 'resolutions' array 
     *     for the layer -- this array is what the layer will use to interface
     *     between the zoom levels of the map and the resolution display 
     *     of the layer.
     * 
     * The user has several options that determine how the array is set up.
     *  
     * For a detailed explanation, see the following wiki from the 
     *     openlayers.org homepage:
     *     http://trac.openlayers.org/wiki/SettingZoomLevels
     */
    initResolutions: function() {

        // ok we want resolutions, here's our strategy:
        //
        // 1. if resolutions are defined in the layer config, use them
        // 2. else, if scales are defined in the layer config then derive
        //    resolutions from these scales
        // 3. else, attempt to calculate resolutions from maxResolution,
        //    minResolution, numZoomLevels, maxZoomLevel set in the
        //    layer config
        // 4. if we still don't have resolutions, and if resolutions
        //    are defined in the same, use them
        // 5. else, if scales are defined in the map then derive
        //    resolutions from these scales
        // 6. else, attempt to calculate resolutions from maxResolution,
        //    minResolution, numZoomLevels, maxZoomLevel set in the
        //    map
        // 7. hope for the best!

        var i, len, p;
        var props = {}, alwaysInRange = true;

        // get resolution data from layer config
        // (we also set alwaysInRange in the layer as appropriate)
        for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
            p = this.RESOLUTION_PROPERTIES[i];
            props[p] = this.options[p];
            if(alwaysInRange && this.options[p]) {
                alwaysInRange = false;
            }
        }
        if(this.options.alwaysInRange == null) {
            this.alwaysInRange = alwaysInRange;
        }

        // if we don't have resolutions then attempt to derive them from scales
        if(props.resolutions == null) {
            props.resolutions = this.resolutionsFromScales(props.scales);
        }

        // if we still don't have resolutions then attempt to calculate them
        if(props.resolutions == null) {
            props.resolutions = this.calculateResolutions(props);
        }

        // if we couldn't calculate resolutions then we look at we have
        // in the map
        if(props.resolutions == null) {
            for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
                p = this.RESOLUTION_PROPERTIES[i];
                props[p] = this.options[p] != null ?
                    this.options[p] : this.map[p];
            }
            if(props.resolutions == null) {
                props.resolutions = this.resolutionsFromScales(props.scales);
            }
            if(props.resolutions == null) {
                props.resolutions = this.calculateResolutions(props);
            }
        }

        // ok, we new need to set properties in the instance

        // get maxResolution from the config if it's defined there
        var maxResolution;
        if(this.options.maxResolution &&
           this.options.maxResolution !== "auto") {
            maxResolution = this.options.maxResolution;
        }
        if(this.options.minScale) {
            maxResolution = OpenLayers.Util.getResolutionFromScale(
                this.options.minScale, this.units);
        }

        // get minResolution from the config if it's defined there
        var minResolution;
        if(this.options.minResolution &&
           this.options.minResolution !== "auto") {
            minResolution = this.options.minResolution;
        }
        if(this.options.maxScale) {
            minResolution = OpenLayers.Util.getResolutionFromScale(
                this.options.maxScale, this.units);
        }

        if(props.resolutions) {

            //sort resolutions array descendingly
            props.resolutions.sort(function(a, b) {
                return (b - a);
            });

            // if we still don't have a maxResolution get it from the
            // resolutions array
            if(!maxResolution) {
                maxResolution = props.resolutions[0];
            }

            // if we still don't have a minResolution get it from the
            // resolutions array
            if(!minResolution) {
                var lastIdx = props.resolutions.length - 1;
                minResolution = props.resolutions[lastIdx];
            }
        }

        this.resolutions = props.resolutions;
        if(this.resolutions) {
            len = this.resolutions.length;
            this.scales = new Array(len);
            for(i=0; i<len; i++) {
                this.scales[i] = OpenLayers.Util.getScaleFromResolution(
                    this.resolutions[i], this.units);
            }
            this.numZoomLevels = len;
        }
        this.minResolution = minResolution;
        if(minResolution) {
            this.maxScale = OpenLayers.Util.getScaleFromResolution(
                minResolution, this.units);
        }
        this.maxResolution = maxResolution;
        if(maxResolution) {
            this.minScale = OpenLayers.Util.getScaleFromResolution(
                maxResolution, this.units);
        }
    },

    /**
     * Method: resolutionsFromScales
     * Derive resolutions from scales.
     *
     * Parameters:
     * scales - {Array(Number)} Scales
     *
     * Returns
     * {Array(Number)} Resolutions
     */
    resolutionsFromScales: function(scales) {
        if(scales == null) {
            return;
        }
        var resolutions, i, len;
        len = scales.length;
        resolutions = new Array(len);
        for(i=0; i<len; i++) {
            resolutions[i] = OpenLayers.Util.getResolutionFromScale(
                scales[i], this.units);
        }
        return resolutions;
    },

    /**
     * Method: calculateResolutions
     * Calculate resolutions based on the provided properties.
     *
     * Parameters:
     * props - {Object} Properties
     *
     * Returns:
     * {Array({Number})} Array of resolutions.
     */
    calculateResolutions: function(props) {

        var viewSize, wRes, hRes;

        // determine maxResolution
        var maxResolution = props.maxResolution;
        if(props.minScale != null) {
            maxResolution =
                OpenLayers.Util.getResolutionFromScale(props.minScale,
                                                       this.units);
        } else if(maxResolution == "auto" && this.maxExtent != null) {
            viewSize = this.map.getSize();
            wRes = this.maxExtent.getWidth() / viewSize.w;
            hRes = this.maxExtent.getHeight() / viewSize.h;
            maxResolution = Math.max(wRes, hRes);
        }

        // determine minResolution
        var minResolution = props.minResolution;
        if(props.maxScale != null) {
            minResolution =
                OpenLayers.Util.getResolutionFromScale(props.maxScale,
                                                       this.units);
        } else if(props.minResolution == "auto" && this.minExtent != null) {
            viewSize = this.map.getSize();
            wRes = this.minExtent.getWidth() / viewSize.w;
            hRes = this.minExtent.getHeight()/ viewSize.h;
            minResolution = Math.max(wRes, hRes);
        }

        if(typeof maxResolution !== "number" &&
           typeof minResolution !== "number" &&
           this.maxExtent != null) {
            // maxResolution for default grid sets assumes that at zoom
            // level zero, the whole world fits on one tile.
            var tileSize = this.map.getTileSize();
            maxResolution = Math.max(
                this.maxExtent.getWidth() / tileSize.w,
                this.maxExtent.getHeight() / tileSize.h
            );
        }

        // determine numZoomLevels
        var maxZoomLevel = props.maxZoomLevel;
        var numZoomLevels = props.numZoomLevels;
        if(typeof minResolution === "number" &&
           typeof maxResolution === "number" && numZoomLevels === undefined) {
            var ratio = maxResolution / minResolution;
            numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
        } else if(numZoomLevels === undefined && maxZoomLevel != null) {
            numZoomLevels = maxZoomLevel + 1;
        }

        // are we able to calculate resolutions?
        if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
           (typeof maxResolution !== "number" &&
                typeof minResolution !== "number")) {
            return;
        }

        // now we have numZoomLevels and at least one of maxResolution
        // or minResolution, we can populate the resolutions array

        var resolutions = new Array(numZoomLevels);
        var base = 2;
        if(typeof minResolution == "number" &&
           typeof maxResolution == "number") {
            // if maxResolution and minResolution are set, we calculate
            // the base for exponential scaling that starts at
            // maxResolution and ends at minResolution in numZoomLevels
            // steps.
            base = Math.pow(
                    (maxResolution / minResolution),
                (1 / (numZoomLevels - 1))
            );
        }

        var i;
        if(typeof maxResolution === "number") {
            for(i=0; i<numZoomLevels; i++) {
                resolutions[i] = maxResolution / Math.pow(base, i);
            }
        } else {
            for(i=0; i<numZoomLevels; i++) {
                resolutions[numZoomLevels - 1 - i] =
                    minResolution * Math.pow(base, i);
            }
        }

        return resolutions;
    },

    /**
     * APIMethod: getResolution
     * 
     * Returns:
     * {Float} The currently selected resolution of the map, taken from the
     *     resolutions array, indexed by current zoom level.
     */
    getResolution: function() {
        var zoom = this.map.getZoom();
        return this.getResolutionForZoom(zoom);
    },

    /** 
     * APIMethod: getExtent
     * 
     * Returns:
     * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
     *     bounds of the current viewPort.
     */
    getExtent: function() {
        // just use stock map calculateBounds function -- passing no arguments
        //  means it will user map's current center & resolution
        //
        return this.map.calculateBounds();
    },

    /**
     * APIMethod: getZoomForExtent
     * 
     * Parameters:
     * extent - {<OpenLayers.Bounds>}
     * closest - {Boolean} Find the zoom level that most closely fits the 
     *     specified bounds. Note that this may result in a zoom that does 
     *     not exactly contain the entire extent.
     *     Default is false.
     *
     * Returns:
     * {Integer} The index of the zoomLevel (entry in the resolutions array) 
     *     for the passed-in extent. We do this by calculating the ideal 
     *     resolution for the given extent (based on the map size) and then 
     *     calling getZoomForResolution(), passing along the 'closest'
     *     parameter.
     */
    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);
    },
    
    /** 
     * Method: getDataExtent
     * Calculates the max extent which includes all of the data for the layer.
     *     This function is to be implemented by subclasses.
     * 
     * Returns:
     * {<OpenLayers.Bounds>}
     */
    getDataExtent: function () {
        //to be implemented by subclasses
    },

    /**
     * APIMethod: getResolutionForZoom
     * 
     * Parameters:
     * zoom - {Float}
     * 
     * Returns:
     * {Float} A suitable resolution for the specified zoom.
     */
    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[low] -
                ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
        } else {
            resolution = this.resolutions[Math.round(zoom)];
        }
        return resolution;
    },

    /**
     * APIMethod: getZoomForResolution
     * 
     * Parameters:
     * resolution - {Float}
     * closest - {Boolean} Find the zoom level that corresponds to the absolute 
     *     closest resolution, which may result in a zoom whose corresponding
     *     resolution is actually smaller than we would have desired (if this
     *     is being called from a getZoomForExtent() call, then this means that
     *     the returned zoom index might not actually contain the entire 
     *     extent specified... but it'll be close).
     *     Default is false.
     * 
     * Returns:
     * {Integer} The index of the zoomLevel (entry in the resolutions array) 
     *     that corresponds to the best fit resolution given the passed in 
     *     value and the 'closest' specification.
     */
    getZoomForResolution: function(resolution, closest) {
        var zoom, i, len;
        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(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 + ((highRes - resolution) / dRes);
            } else {
                zoom = lowZoom;
            }
        } else {
            var diff;
            var minDiff = Number.POSITIVE_INFINITY;
            for(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;
    },
    
    /**
     * APIMethod: getLonLatFromViewPortPx
     * 
     * Parameters:
     * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
     *                                          an object with a 'x'
     *                                          and 'y' properties.
     *
     * Returns:
     * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in 
     *     view port <OpenLayers.Pixel>, translated into lon/lat by the layer.
     */
    getLonLatFromViewPortPx: function (viewPortPx) {
        var lonlat = null;
        var map = this.map;
        if (viewPortPx != null && map.minPx) {
            var res = map.getResolution();
            var maxExtent = map.getMaxExtent({restricted: true});
            var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left;
            var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top;
            lonlat = new OpenLayers.LonLat(lon, lat);

            if (this.wrapDateLine) {
                lonlat = lonlat.wrapDateLine(this.maxExtent);
            }
        }
        return lonlat;
    },

    /**
     * APIMethod: getViewPortPxFromLonLat
     * Returns a pixel location given a map location.  This method will return
     *     fractional pixel values.
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>|Object} An OpenLayers.LonLat or
     *                                       an object with a 'lon'
     *                                       and 'lat' properties.
     *
     * Returns: 
     * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in 
     *     lonlat translated into view port pixels.
     */
    getViewPortPxFromLonLat: function (lonlat, resolution) {
        var px = null; 
        if (lonlat != null) {
            resolution = resolution || this.map.getResolution();
            var extent = this.map.calculateBounds(null, resolution);
            px = new OpenLayers.Pixel(
                (1/resolution * (lonlat.lon - extent.left)),
                (1/resolution * (extent.top - lonlat.lat))
            );    
        }
        return px;
    },
    
    /**
     * APIMethod: setOpacity
     * Sets the opacity for the entire layer (all images)
     * 
     * Parameters:
     * opacity - {Float}
     */
    setOpacity: function(opacity) {
        if (opacity != this.opacity) {
            this.opacity = opacity;
            var childNodes = this.div.childNodes;
            for(var i = 0, len = childNodes.length; i < len; ++i) {
                var element = childNodes[i].firstChild || childNodes[i];
                var lastChild = childNodes[i].lastChild;
                //TODO de-uglify this
                if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") {
                    element = lastChild.parentNode;
                }
                OpenLayers.Util.modifyDOMElement(element, null, null, null, 
                                                 null, null, null, opacity);
            }
            if (this.map != null) {
                this.map.events.triggerEvent("changelayer", {
                    layer: this,
                    property: "opacity"
                });
            }
        }
    },

    /**
     * Method: getZIndex
     * 
     * Returns: 
     * {Integer} the z-index of this layer
     */    
    getZIndex: function () {
        return this.div.style.zIndex;
    },

    /**
     * Method: setZIndex
     * 
     * Parameters: 
     * zIndex - {Integer}
     */    
    setZIndex: function (zIndex) {
        this.div.style.zIndex = zIndex;
    },

    /**
     * Method: adjustBounds
     * This function will take a bounds, and if wrapDateLine option is set
     *     on the layer, it will return a bounds which is wrapped around the 
     *     world. We do not wrap for bounds which *cross* the 
     *     maxExtent.left/right, only bounds which are entirely to the left 
     *     or entirely to the right.
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     */
    adjustBounds: function (bounds) {

        if (this.gutter) {
            // Adjust the extent of a bounds in map units by the 
            // layer's gutter in pixels.
            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) {
            // wrap around the date line, within the limits of rounding error
            var wrappingOptions = { 
                'rightTolerance':this.getResolution(),
                'leftTolerance':this.getResolution()
            };    
            bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
                              
        }
        return bounds;
    },

    CLASS_NAME: "OpenLayers.Layer"
});
/* ======================================================================
    OpenLayers/Layer/SphericalMercator.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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: 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) {
        return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments);
    },
    
    /**
     * 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) {
        return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments);
    },

    /** 
     * 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.03390625;
        for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) {
            this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom);
        }
        this.units = "m";
        this.projection = 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() {
        var gg = new OpenLayers.Projection("EPSG:4326");
        var sm = new OpenLayers.Projection("EPSG:900913");
        return function(lon, lat) {
            var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm);
            return new OpenLayers.LonLat(point.x, point.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() {
        var gg = new OpenLayers.Projection("EPSG:4326");
        var sm = new OpenLayers.Projection("EPSG:900913");
        return function(x, y) {
            var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg);
            return new OpenLayers.LonLat(point.x, point.y);
        };
    })()

};
/* ======================================================================
    OpenLayers/Layer/EventPane.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Layer.js
 * @requires OpenLayers/Util.js
 */

/**
 * Class: OpenLayers.Layer.EventPane
 * Base class for 3rd party layers, providing a DOM element which isolates
 * the 3rd-party layer from mouse events.
 * Only used by Google layers.
 *
 * Automatically instantiated by the Google constructor, and not usually instantiated directly.
 *
 * 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;
        this.pane = 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.BROWSER_NAME == "msie") {
            this.pane.style.background = 
                "url(" + OpenLayers.Util.getImageLocation("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);
        }
        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: moveByPx
     * Move the layer based on pixel vector. To be implemented by subclasses.
     *
     * Parameters:
     * dx - {Number} The x coord of the displacement vector.
     * dy - {Number} The y coord of the displacement vector.
     */
    moveByPx: function(dx, dy) {
        OpenLayers.Layer.prototype.moveByPx.apply(this, arguments);
        
        if (this.dragPanMapObject) {
            this.dragPanMapObject(dx, -dy);
        } else {
            this.moveTo(this.map.getCachedCenter());
        }
    },

    /**
     * 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 (!zoomChanged && oldCenter && 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-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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 = ['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;
        }        

        //
        // At this point, we know what the minimum desired zoom level is, and
        //  we must calculate the total number of zoom levels. 
        //  
        //  Because we allow for the setting of either the 'numZoomLevels'
        //   or the 'maxZoomLevel' properties... on either the layer or the  
        //   map, we have to define some rules to see which we take into
        //   account first in this calculation. 
        //
        // The following is the precedence list for these properties:
        // 
        // (1) numZoomLevels set on layer
        // (2) maxZoomLevel set on layer
        // (3) numZoomLevels set on map
        // (4) maxZoomLevel set on map*
        // (5) none of the above*
        //
        // *Note that options (4) and (5) are only possible if the user 
        //  _explicitly_ sets the 'numZoomLevels' property on the map to 
        //  null, since it is set by default to 16. 
        //

        //
        // Note to future: In 3.0, I think we should remove the default 
        // value of 16 for map.numZoomLevels. Rather, I think that value 
        // should be set as a default on the Layer.WMS class. If someone
        // creates a 3rd party layer and does not specify any 'minZoomLevel', 
        // 'maxZoomLevel', or 'numZoomLevels', and has not explicitly 
        // specified any of those on the map object either.. then I think
        // it is fair to say that s/he wants all the zoom levels available.
        // 
        // By making map.numZoomLevels *null* by default, that will be the 
        // case. As it is, I don't feel comfortable changing that right now
        // as it would be a glaring API change and actually would probably
        // break many peoples' codes. 
        //

        //the number of zoom levels we'd like to have.
        var desiredZoomLevels;

        //this is the maximum number of zoom levels the layer will allow, 
        // given the specified starting minimum zoom level.
        var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1;

        if ( ((this.options.numZoomLevels == null) && 
              (this.options.maxZoomLevel != null)) // (2)
              ||
             ((this.numZoomLevels == null) &&
              (this.maxZoomLevel != null)) // (4)
           ) {
            //calculate based on specified maxZoomLevel (on layer or map)
            desiredZoomLevels = this.maxZoomLevel - this.minZoomLevel + 1;
        } else {
            //calculate based on specified numZoomLevels (on layer or map)
            // this covers cases (1) and (3)
            desiredZoomLevels = this.numZoomLevels;
        }

        if (desiredZoomLevels != null) {
            //Now that we know what we would *like* the number of zoom levels
            // to be, based on layer or map options, we have to make sure that
            // it does not conflict with the actual limit, as specified by 
            // the constants on the layer itself (and calculated into the
            // 'limitZoomLevels' variable). 
            this.numZoomLevels = Math.min(desiredZoomLevels, limitZoomLevels);
        } else {
            // case (5) -- neither 'numZoomLevels' not 'maxZoomLevel' was 
            // set on either the layer or the map. So we just use the 
            // maximum limit as calculated by the layer's constants.
            this.numZoomLevels = limitZoomLevels;
        }

        //now that the 'numZoomLevels' is appropriately, safely set, 
        // we go back and re-calculate the 'maxZoomLevel'.
        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 size = this.map.getSize();
        var tl = this.getLonLatFromViewPortPx({
            x: 0, y: 0
        });
        var br = this.getLonLatFromViewPortPx({
            x: size.w, y: size.h
        });
        
        if ((tl != null) && (br != null)) {
            return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat);
        } else {
            return null;
        }
    },

    /**
     * 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;
            if (this.map.baseLayer !== this) {
                zoom = this.map.baseLayer.getZoomForResolution(
                    this.getResolutionForZoom(zoom)
                );
            }
        }
        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;
            if (this.map.baseLayer !== this) {
                zoom = this.getZoomForResolution(
                    this.map.baseLayer.getResolutionForZoom(zoom)
                );
            }
        }
        return zoom;
    },

    CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels"
});

/* ======================================================================
    OpenLayers/Layer/Google.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Layer/SphericalMercator.js
 * @requires OpenLayers/Layer/EventPane.js
 * @requires OpenLayers/Layer/FixedZoomLevels.js
 * @requires OpenLayers/Lang.js
 */

/**
 * Class: OpenLayers.Layer.Google
 * 
 * Provides a wrapper for Google's Maps API
 * Normally the Terms of Use for this API do not allow wrapping, but Google
 * have provided written consent to OpenLayers for this - see email in 
 * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html
 * 
 * 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} 21
     */
    MAX_ZOOM_LEVEL: 21,

    /** 
     * 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,
        0.0000013411045074462891,
        0.00000067055225372314453
    ],

    /**
     * APIProperty: type
     * {GMapType}
     */
    type: null,

    /**
     * APIProperty: wrapDateLine
     * {Boolean} Allow user to pan forever east/west.  Default is true.  
     *     Setting this to false only restricts panning if 
     *     <sphericalMercator> is true. 
     */
    wrapDateLine: true,

    /**
     * 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: version
     * {Number} The version of the Google Maps API
     */
    version: 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) {
        options = options || {};
        if(!options.version) {
            options.version = typeof GMap2 === "function" ? "2" : "3";
        }
        var mixin = OpenLayers.Layer.Google["v" +
            options.version.replace(/\./g, "_")];
        if (mixin) {
            OpenLayers.Util.applyDefaults(options, mixin);
        } else {
            throw "Unsupported Google Maps API version: " + options.version;
        }

        OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS);
        if (options.maxExtent) {
            options.maxExtent = options.maxExtent.clone();
        }

        OpenLayers.Layer.EventPane.prototype.initialize.apply(this,
            [name, options]);
        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
            [name, options]);

        if (this.sphericalMercator) {
            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
            this.initMercatorParameters();
        }    
    },

    /**
     * Method: clone
     * Create a clone of this layer
     *
     * Returns:
     * {<OpenLayers.Layer.Google>} An exact clone of this layer
     */
    clone: function() {
        /**
         * This method isn't intended to be called by a subclass and it
         * doesn't call the same method on the superclass.  We don't call
         * the super's clone because we don't want properties that are set
         * on this layer after initialize (i.e. this.mapObject etc.).
         */
        return new OpenLayers.Layer.Google(
            this.name, this.getOptions()
        );
    },

    /**
     * APIMethod: setVisibility
     * Set the visibility flag for the layer and hide/show & redraw 
     *     accordingly. Fire event unless otherwise specified
     * 
     * Note that visibility is no longer simply whether or not the layer's
     *     style.display is set to "block". Now we store a 'visibility' state 
     *     property on the layer class, this allows us to remember whether or 
     *     not we *desire* for a layer to be visible. In the case where the 
     *     map's resolution is out of the layer's range, this desire may be 
     *     subverted.
     * 
     * Parameters:
     * visible - {Boolean} Display the layer (if in range)
     */
    setVisibility: function(visible) {
        // sharing a map container, opacity has to be set per layer
        var opacity = this.opacity == null ? 1 : this.opacity;
        OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments);
        this.setOpacity(opacity);
    },
    
    /** 
     * APIMethod: display
     * Hide or show the Layer
     * 
     * Parameters:
     * visible - {Boolean}
     */
    display: function(visible) {
        if (!this._dragging) {
            this.setGMapVisibility(visible);
        }
        OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments);
    },
    
    /**
     * Method: moveTo
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
     *     do some init work in that case.
     * dragging - {Boolean}
     */
    moveTo: function(bounds, zoomChanged, dragging) {
        this._dragging = dragging;
        OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments);
        delete this._dragging;
    },
    
    /**
     * APIMethod: setOpacity
     * Sets the opacity for the entire layer (all images)
     * 
     * Parameters:
     * opacity - {Float}
     */
    setOpacity: function(opacity) {
        if (opacity !== this.opacity) {
            if (this.map != null) {
                this.map.events.triggerEvent("changelayer", {
                    layer: this,
                    property: "opacity"
                });
            }
            this.opacity = opacity;
        }
        // Though this layer's opacity may not change, we're sharing a container
        // and need to update the opacity for the entire container.
        if (this.getVisibility()) {
            var container = this.getMapContainer();
            OpenLayers.Util.modifyDOMElement(
                container, null, null, null, null, null, null, opacity
            );
        }
    },

    /**
     * APIMethod: destroy
     * Clean up this layer.
     */
    destroy: function() {
        /**
         * We have to override this method because the event pane destroy
         * deletes the mapObject reference before removing this layer from
         * the map.
         */
        if (this.map) {
            this.setGMapVisibility(false);
            var cache = OpenLayers.Layer.Google.cache[this.map.id];
            if (cache && cache.count <= 1) {
                this.removeGMapElements();
            }            
        }
        OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments);
    },
    
    /**
     * Method: removeGMapElements
     * Remove all elements added to the dom.  This should only be called if
     * this is the last of the Google layers for the given map.
     */
    removeGMapElements: function() {
        var cache = OpenLayers.Layer.Google.cache[this.map.id];
        if (cache) {
            // remove shared elements from dom
            var container = this.mapObject && this.getMapContainer();                
            if (container && container.parentNode) {
                container.parentNode.removeChild(container);
            }
            var termsOfUse = cache.termsOfUse;
            if (termsOfUse && termsOfUse.parentNode) {
                termsOfUse.parentNode.removeChild(termsOfUse);
            }
            var poweredBy = cache.poweredBy;
            if (poweredBy && poweredBy.parentNode) {
                poweredBy.parentNode.removeChild(poweredBy);
            }
            if (this.mapObject && window.google && google.maps &&
                    google.maps.event && google.maps.event.clearListeners) {
                google.maps.event.clearListeners(this.mapObject, 'tilesloaded');
            }
        }
    },

    /**
     * APIMethod: removeMap
     * On being removed from the map, also remove termsOfUse and poweredBy divs
     * 
     * Parameters:
     * map - {<OpenLayers.Map>}
     */
    removeMap: function(map) {
        // hide layer before removing
        if (this.visibility && this.mapObject) {
            this.setGMapVisibility(false);
        }
        // check to see if last Google layer in this map
        var cache = OpenLayers.Layer.Google.cache[map.id];
        if (cache) {
            if (cache.count <= 1) {
                this.removeGMapElements();
                delete OpenLayers.Layer.Google.cache[map.id];
            } else {
                // decrement the layer count
                --cache.count;
            }
        }
        // remove references to gmap elements
        delete this.termsOfUse;
        delete this.poweredBy;
        delete this.mapObject;
        delete this.dragObject;
        OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments);
    },
    
  //
  // 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: 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: 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();
    },

  
    /************************************
     *                                  *
     *       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;  
    },
    
  // 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;
    },
    
    CLASS_NAME: "OpenLayers.Layer.Google"
});

/**
 * Property: OpenLayers.Layer.Google.cache
 * {Object} Cache for elements that should only be created once per map.
 */
OpenLayers.Layer.Google.cache = {};


/**
 * Constant: OpenLayers.Layer.Google.v2
 * 
 * Mixin providing functionality specific to the Google Maps API v2.
 * 
 * This API has been deprecated by Google.
 * Developers are encouraged to migrate to v3 of the API; support for this
 * is provided by <OpenLayers.Layer.Google.v3>
 */
OpenLayers.Layer.Google.v2 = {
    
    /**
     * Property: termsOfUse
     * {DOMElement} Div for Google's copyright and terms of use link
     */
    termsOfUse: null, 

    /**
     * Property: poweredBy
     * {DOMElement} Div for Google's powered by logo and link
     */
    poweredBy: null, 

    /**
     * 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, 
    
    /** 
     * Method: loadMapObject
     * Load the GMap and register appropriate event listeners. If we can't 
     *     load GMap2, then display a warning message.
     */
    loadMapObject:function() {
        if (!this.type) {
            this.type = G_NORMAL_MAP;
        }
        var mapObject, termsOfUse, poweredBy;
        var cache = OpenLayers.Layer.Google.cache[this.map.id];
        if (cache) {
            // there are already Google layers added to this map
            mapObject = cache.mapObject;
            termsOfUse = cache.termsOfUse;
            poweredBy = cache.poweredBy;
            // increment the layer count
            ++cache.count;
        } else {
            // this is the first Google layer for this map

            var container = this.map.viewPortDiv;
            var div = document.createElement("div");
            div.id = this.map.id + "_GMap2Container";
            div.style.position = "absolute";
            div.style.width = "100%";
            div.style.height = "100%";
            container.appendChild(div);

            // create GMap and shuffle elements
            try {
                mapObject = new GMap2(div);
                
                // move the ToS and branding stuff up to the container div
                termsOfUse = div.lastChild;
                container.appendChild(termsOfUse);
                termsOfUse.style.zIndex = "1100";
                termsOfUse.style.right = "";
                termsOfUse.style.bottom = "";
                termsOfUse.className = "olLayerGoogleCopyright";

                poweredBy = div.lastChild;
                container.appendChild(poweredBy);
                poweredBy.style.zIndex = "1100";
                poweredBy.style.right = "";
                poweredBy.style.bottom = "";
                poweredBy.className = "olLayerGooglePoweredBy gmnoprint";
                
            } catch (e) {
                throw(e);
            }
            // cache elements for use by any other google layers added to
            // this same map
            OpenLayers.Layer.Google.cache[this.map.id] = {
                mapObject: mapObject,
                termsOfUse: termsOfUse,
                poweredBy: poweredBy,
                count: 1
            };
        }

        this.mapObject = mapObject;
        this.termsOfUse = termsOfUse;
        this.poweredBy = poweredBy;
        
        // ensure this layer type is one of the mapObject types
        if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),
                                    this.type) === -1) {
            this.mapObject.addMapType(this.type);
        }

        //since v 2.93 getDragObject is now available.
        if(typeof mapObject.getDragObject == "function") {
            this.dragObject = mapObject.getDragObject();
        } else {
            this.dragPanMapObject = null;
        }
        
        if(this.isBaseLayer === false) {
            this.setGMapVisibility(this.div.style.display !== "none");
        }

    },

    /**
     * APIMethod: onMapResize
     */
    onMapResize: function() {
        // workaround for resizing of invisible or not yet fully loaded layers
        // where GMap2.checkResize() does not work. We need to load the GMap
        // for the old div size, then checkResize(), and then call
        // layer.moveTo() to trigger GMap.setCenter() (which will finish
        // the GMap initialization).
        if(this.visibility && this.mapObject.isLoaded()) {
            this.mapObject.checkResize();
        } else {
            if(!this._resized) {
                var layer = this;
                var handle = GEvent.addListener(this.mapObject, "load", function() {
                    GEvent.removeListener(handle);
                    delete layer._resized;
                    layer.mapObject.checkResize();
                    layer.moveTo(layer.map.getCenter(), layer.map.getZoom());
                });
            }
            this._resized = true;
        }
    },

    /**
     * Method: setGMapVisibility
     * Display the GMap container and associated elements.
     * 
     * Parameters:
     * visible - {Boolean} Display the GMap elements.
     */
    setGMapVisibility: function(visible) {
        var cache = OpenLayers.Layer.Google.cache[this.map.id];
        if (cache) {
            var container = this.mapObject.getContainer();
            if (visible === true) {
                this.mapObject.setMapType(this.type);
                container.style.display = "";
                this.termsOfUse.style.left = "";
                this.termsOfUse.style.display = "";
                this.poweredBy.style.display = "";            
                cache.displayed = this.id;
            } else {
                if (cache.displayed === this.id) {
                    delete cache.displayed;
                }
                if (!cache.displayed) {
                    container.style.display = "none";
                    this.termsOfUse.style.display = "none";
                    // move ToU far to the left in addition to setting display
                    // to "none", because at the end of the GMap2 load
                    // sequence, display: none will be unset and ToU would be
                    // visible after loading a map with a google layer that is
                    // initially hidden. 
                    this.termsOfUse.style.left = "-9999px";
                    this.poweredBy.style.display = "none";
                }
            }
        }
    },
    
    /**
     * Method: getMapContainer
     * 
     * Returns:
     * {DOMElement} the GMap container's div
     */
    getMapContainer: function() {
        return this.mapObject.getContainer();
    },

  //
  // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
  //

    /**
     * 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;
    },


    /************************************
     *                                  *
     *   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));
    },


  // 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: 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: getMapObjectPixelFromXY
     * 
     * Parameters:
     * x - {Integer}
     * y - {Integer}
     * 
     * Returns:
     * {Object} MapObject Pixel from x and y parameters
     */
    getMapObjectPixelFromXY: function(x, y) {
        return new GPoint(x, y);
    }
    
};
/* ======================================================================
    OpenLayers/Format.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 */

/**
 * 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({
    
    /**
     * Property: options
     * {Object} A reference to options passed to the constructor.
     */
    options: null,
    
    /**
     * 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,

    /**
     * APIProperty: data
     * {Object} When <keepData> is true, this is the parsed string sent to
     *     <read>.
     */
    data: null,

    /**
     * APIProperty: keepData
     * {Object} Maintain a reference (<data>) to the most recently read data.
     *     Default is false.
     */
    keepData: false,

    /**
     * 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
     *
     * Valid options:
     * keepData - {Boolean} If true, upon <read>, the data property will be
     *     set to the parsed object (e.g. the json or xml object).
     *
     * Returns:
     * An instance of OpenLayers.Format
     */
    initialize: function(options) {
        OpenLayers.Util.extend(this, options);
        this.options = options;
    },
    
    /**
     * APIMethod: destroy
     * Clean up.
     */
    destroy: function() {
    },

    /**
     * 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) {
        throw new Error('Read not implemented.');
    },
    
    /**
     * 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) {
        throw new Error('Write not implemented.');
    },

    CLASS_NAME: "OpenLayers.Format"
});     
/* ======================================================================
    OpenLayers/Format/XML.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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: namespaces
     * {Object} Mapping of namespace aliases to namespace URIs.  Properties
     *     of this object should not be set individually.  Read-only.  All
     *     XML subclasses should have their own namespaces object.  Use
     *     <setNamespace> to add or set a namespace alias after construction.
     */
    namespaces: null,
    
    /**
     * Property: namespaceAlias
     * {Object} Mapping of namespace URI to namespace alias.  This object
     *     is read-only.  Use <setNamespace> to add or set a namespace alias.
     */
    namespaceAlias: null,
    
    /**
     * Property: defaultPrefix
     * {String} The default namespace alias for creating element nodes.
     */
    defaultPrefix: null,
    
    /**
     * 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: {},
    
    /**
     * 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: {},

    /**
     * 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]);
        // clone the namespace object and set all namespace aliases
        this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
        this.namespaceAlias = {};
        for(var alias in this.namespaces) {
            this.namespaceAlias[this.namespaces[alias]] = alias;
        }
    },
    
    /**
     * APIMethod: destroy
     * Clean up.
     */
    destroy: function() {
        this.xmldom = null;
        OpenLayers.Format.prototype.destroy.apply(this, arguments);
    },
    
    /**
     * Method: setNamespace
     * Set a namespace alias and URI for the format.
     *
     * Parameters:
     * alias - {String} The namespace alias (prefix).
     * uri - {String} The namespace URI.
     */
    setNamespace: function(alias, uri) {
        this.namespaces[alias] = uri;
        this.namespaceAlias[uri] = alias;
    },

    /**
     * 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;
            }
        );

        if(this.keepData) {
            this.data = node;
        }

        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: createDocumentFragment
     * Create a document fragment node that can be appended to another node
     *     created by createElementNS.  This will call 
     *     document.createDocumentFragment outside of IE.  In IE, the ActiveX
     *     object's createDocumentFragment method is used.
     *
     * Returns:
     * {Element} A document fragment.
     */
    createDocumentFragment: function() {
        var element;
        if (this.xmldom) {
            element = this.xmldom.createDocumentFragment();
        } else {
            element = document.createDocumentFragment();
        }
        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 (typeof text !== "string") {
            text = String(text);
        }
        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 textual value of the 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 = def || "";
        if(node) {
            for(var child=node.firstChild; child; child=child.nextSibling) {
                switch(child.nodeType) {
                    case 3: // text node
                    case 4: // cdata section
                        value += child.nodeValue;
                }
            }
        }
        return value;
    },

    /**
     * APIMethod: isSimpleContent
     * Test if the given node has only simple content (i.e. no child element
     *     nodes).
     *
     * Parameters:
     * node - {DOMElement} An element node.
     *
     * Returns:
     * {Boolean} The node has no child element nodes (nodes of type 1). 
     */
    isSimpleContent: function(node) {
        var simple = true;
        for(var child=node.firstChild; child; child=child.nextSibling) {
            if(child.nodeType === 1) {
                simple = false;
                break;
            }
        }
        return simple;
    },
    
    /**
     * APIMethod: contentType
     * Determine the content type for a given node.
     *
     * Parameters:
     * node - {DOMElement}
     *
     * Returns:
     * {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
     *     if the node has no, simple, complex, or mixed content.
     */
    contentType: function(node) {
        var simple = false,
            complex = false;
            
        var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;

        for(var child=node.firstChild; child; child=child.nextSibling) {
            switch(child.nodeType) {
                case 1: // element
                    complex = true;
                    break;
                case 8: // comment
                    break;
                default:
                    simple = true;
            }
            if(complex && simple) {
                break;
            }
        }
        
        if(complex && simple) {
            type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
        } else if(complex) {
            return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
        } else if(simple) {
            return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
        }
        return type;
    },

    /**
     * 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";
            }
        }
    },

    /**
     * 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.
     *
     * Valid options:
     * uri - {String} Optional namespace uri for the element - supply a prefix
     *     instead if the namespace uri is a property of the format's namespace
     *     object.
     * attributes - {Object} Optional attributes to be set using the
     *     <setAttributes> method.
     * value - {String} Optional text to be appended as a text node.
     *
     * Returns:
     * {Element} An element node.
     */
    createElementNSPlus: function(name, options) {
        options = options || {};
        // 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) {
            var 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);
        }
        var value = options.value;
        if(value != null) {
            node.appendChild(this.createTextNode(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, uri;
        for(var name in obj) {
            if(obj[name] != null && obj[name].toString) {
                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);
            }
        }
    },

    /**
     * Method: readNode
     * Shorthand for applying one of the named readers given the node
     *     namespace and local name.  Readers take two args (node, obj) and
     *     generally extend or modify the second.
     *
     * Parameters:
     * node - {DOMElement} The node to be read (required).
     * obj - {Object} The object to be modified (optional).
     *
     * Returns:
     * {Object} The input object, modified (or a new one if none was provided).
     */
    readNode: function(node, obj) {
        if(!obj) {
            obj = {};
        }
        var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix];
        if(group) {
            var local = node.localName || node.nodeName.split(":").pop();
            var reader = group[local] || group["*"];
            if(reader) {
                reader.apply(this, [node, obj]);
            }
        }
        return obj;
    },

    /**
     * Method: readChildNodes
     * Shorthand for applying the named readers to all children of a node.
     *     For each child of type 1 (element), <readSelf> is called.
     *
     * Parameters:
     * node - {DOMElement} The node to be read (required).
     * obj - {Object} The object to be modified (optional).
     *
     * Returns:
     * {Object} The input object, modified.
     */
    readChildNodes: function(node, obj) {
        if(!obj) {
            obj = {};
        }
        var children = node.childNodes;
        var child;
        for(var i=0, len=children.length; i<len; ++i) {
            child = children[i];
            if(child.nodeType == 1) {
                this.readNode(child, obj);
            }
        }
        return 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:
     * 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.  If a local name is used
     *     and no parent is supplied, then the default namespace is assumed.
     * obj - {Object} Structure containing data for the writer.
     * parent - {DOMElement} Result will be appended to this node.  If no parent
     *     is supplied, the node will not be appended to anything.
     *
     * Returns:
     * {DOMElement} The child node.
     */
    writeNode: function(name, obj, parent) {
        var prefix, local;
        var split = name.indexOf(":");
        if(split > 0) {
            prefix = name.substring(0, split);
            local = name.substring(split + 1);
        } else {
            if(parent) {
                prefix = this.namespaceAlias[parent.namespaceURI];
            } else {
                prefix = this.defaultPrefix;
            }
            local = name;
        }
        var child = this.writers[prefix][local].apply(this, [obj]);
        if(parent) {
            parent.appendChild(child);
        }
        return child;
    },

    /**
     * APIMethod: getChildEl
     * Get the first child element.  Optionally only return the first child
     *     if it matches the given name and namespace URI.
     *
     * Parameters:
     * node - {DOMElement} The parent node.
     * name - {String} Optional node name (local) to search for.
     * uri - {String} Optional namespace URI to search for.
     *
     * Returns:
     * {DOMElement} The first child.  Returns null if no element is found, if
     *     something significant besides an element is found, or if the element
     *     found does not match the optional name and uri.
     */
    getChildEl: function(node, name, uri) {
        return node && this.getThisOrNextEl(node.firstChild, name, uri);
    },
    
    /**
     * APIMethod: getNextEl
     * Get the next sibling element.  Optionally get the first sibling only
     *     if it matches the given local name and namespace URI.
     *
     * Parameters:
     * node - {DOMElement} The node.
     * name - {String} Optional local name of the sibling to search for.
     * uri - {String} Optional namespace URI of the sibling to search for.
     *
     * Returns:
     * {DOMElement} The next sibling element.  Returns null if no element is
     *     found, something significant besides an element is found, or the
     *     found element does not match the optional name and uri.
     */
    getNextEl: function(node, name, uri) {
        return node && this.getThisOrNextEl(node.nextSibling, name, uri);
    },
    
    /**
     * Method: getThisOrNextEl
     * Return this node or the next element node.  Optionally get the first
     *     sibling with the given local name or namespace URI.
     *
     * Parameters:
     * node - {DOMElement} The node.
     * name - {String} Optional local name of the sibling to search for.
     * uri - {String} Optional namespace URI of the sibling to search for.
     *
     * Returns:
     * {DOMElement} The next sibling element.  Returns null if no element is
     *     found, something significant besides an element is found, or the
     *     found element does not match the query.
     */
    getThisOrNextEl: function(node, name, uri) {
        outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
            switch(sibling.nodeType) {
                case 1: // Element
                    if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
                       (!uri || uri === sibling.namespaceURI)) {
                        // matches
                        break outer;
                    }
                    sibling = null;
                    break outer;
                case 3: // Text
                    if(/^\s*$/.test(sibling.nodeValue)) {
                        break;
                    }
                case 4: // CDATA
                case 6: // ENTITY_NODE
                case 12: // NOTATION_NODE
                case 10: // DOCUMENT_TYPE_NODE
                case 11: // DOCUMENT_FRAGMENT_NODE
                    sibling = null;
                    break outer;
            } // ignore comments and processing instructions
        }
        return sibling || null;
    },
    
    /**
     * APIMethod: lookupNamespaceURI
     * Takes a prefix and returns the namespace URI associated with it on the given
     *     node if found (and null if not). Supplying null for the prefix will
     *     return the default namespace.
     *
     * For browsers that support it, this calls the native lookupNamesapceURI
     *     function.  In other browsers, this is an implementation of
     *     http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
     *
     * For browsers that don't support the attribute.ownerElement property, this
     *     method cannot be called on attribute nodes.
     *     
     * Parameters:
     * node - {DOMElement} The node from which to start looking.
     * prefix - {String} The prefix to lookup or null to lookup the default namespace.
     * 
     * Returns:
     * {String} The namespace URI for the given prefix.  Returns null if the prefix
     *     cannot be found or the node is the wrong type.
     */
    lookupNamespaceURI: function(node, prefix) {
        var uri = null;
        if(node) {
            if(node.lookupNamespaceURI) {
                uri = node.lookupNamespaceURI(prefix);
            } else {
                outer: switch(node.nodeType) {
                    case 1: // ELEMENT_NODE
                        if(node.namespaceURI !== null && node.prefix === prefix) {
                            uri = node.namespaceURI;
                            break outer;
                        }
                        var len = node.attributes.length;
                        if(len) {
                            var attr;
                            for(var i=0; i<len; ++i) {
                                attr = node.attributes[i];
                                if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
                                    uri = attr.value || null;
                                    break outer;
                                } else if(attr.name === "xmlns" && prefix === null) {
                                    uri = attr.value || null;
                                    break outer;
                                }
                            }
                        }
                        uri = this.lookupNamespaceURI(node.parentNode, prefix);
                        break outer;
                    case 2: // ATTRIBUTE_NODE
                        uri = this.lookupNamespaceURI(node.ownerElement, prefix);
                        break outer;
                    case 9: // DOCUMENT_NODE
                        uri = this.lookupNamespaceURI(node.documentElement, prefix);
                        break outer;
                    case 6: // ENTITY_NODE
                    case 12: // NOTATION_NODE
                    case 10: // DOCUMENT_TYPE_NODE
                    case 11: // DOCUMENT_FRAGMENT_NODE
                        break outer;
                    default: 
                        // TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
                        // PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
                        uri =  this.lookupNamespaceURI(node.parentNode, prefix);
                        break outer;
                }
            }
        }
        return uri;
    },
    
    /**
     * Method: getXMLDoc
     * Get an XML document for nodes that are not supported in HTML (e.g.
     * createCDATASection). On IE, this will either return an existing or
     * create a new <xmldom> on the instance. On other browsers, this will
     * either return an existing or create a new shared document (see
     * <OpenLayers.Format.XML.document>).
     *
     * Returns:
     * {XMLDocument}
     */
    getXMLDoc: function() {
        if (!OpenLayers.Format.XML.document && !this.xmldom) {
            if (document.implementation && document.implementation.createDocument) {
                OpenLayers.Format.XML.document =
                    document.implementation.createDocument("", "", null);
            } else if (!this.xmldom && window.ActiveXObject) {
                this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
            }
        }
        return OpenLayers.Format.XML.document || this.xmldom;
    },

    CLASS_NAME: "OpenLayers.Format.XML" 

});     

OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};

/**
 * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
 * Takes a prefix and returns the namespace URI associated with it on the given
 *     node if found (and null if not). Supplying null for the prefix will
 *     return the default namespace.
 *
 * For browsers that support it, this calls the native lookupNamesapceURI
 *     function.  In other browsers, this is an implementation of
 *     http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
 *
 * For browsers that don't support the attribute.ownerElement property, this
 *     method cannot be called on attribute nodes.
 *     
 * Parameters:
 * node - {DOMElement} The node from which to start looking.
 * prefix - {String} The prefix to lookup or null to lookup the default namespace.
 * 
 * Returns:
 * {String} The namespace URI for the given prefix.  Returns null if the prefix
 *     cannot be found or the node is the wrong type.
 */
OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
    OpenLayers.Format.XML.prototype.lookupNamespaceURI,
    OpenLayers.Format.XML.prototype
);

/**
 * Property: OpenLayers.Format.XML.document
 * {XMLDocument} XML document to reuse for creating non-HTML compliant nodes,
 * like document.createCDATASection.
 */
OpenLayers.Format.XML.document = null;
/* ======================================================================
    OpenLayers/Format/WFST.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format.js
 */

/**
 * Function: OpenLayers.Format.WFST
 * Used to create a versioned WFS protocol.  Default version is 1.0.0.
 *
 * Returns:
 * {<OpenLayers.Format>} A WFST format of the given version.
 */
OpenLayers.Format.WFST = function(options) {
    options = OpenLayers.Util.applyDefaults(
        options, OpenLayers.Format.WFST.DEFAULTS
    );
    var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")];
    if(!cls) {
        throw "Unsupported WFST version: " + options.version;
    }
    return new cls(options);
};

/**
 * Constant: OpenLayers.Format.WFST.DEFAULTS
 * {Object} Default properties for the WFST format.
 */
OpenLayers.Format.WFST.DEFAULTS = {
    "version": "1.0.0"
};
/* ======================================================================
    OpenLayers/Feature.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 */

/**
 * 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.Anchored>.
     */
    popupClass: null,

    /** 
     * 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);
            }
        }
        // remove the marker from the layer
        if (this.layer != null && this.marker != null) {
            this.layer.removeMarker(this.marker);
        }

        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) {
            if (!this.popup) {
                var anchor = (this.marker) ? this.marker.icon : null;
                var popupClass = this.popupClass ? 
                    this.popupClass : OpenLayers.Popup.Anchored;
                this.popup = new popupClass(this.id + "_popup", 
                                            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/Feature/Vector.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

// 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, serializable properties that
     *     describe the feature.
     */
    attributes: null,

    /**
     * Property: bounds
     * {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
     *     property can be set by an <OpenLayers.Format> object when
     *     deserializing the feature, so in most cases it represents an
     *     information set by the server. 
     */
    bounds: null,

    /** 
     * Property: state 
     * {String} 
     */
    state: null,
    
    /** 
     * APIProperty: style 
     * {Object} 
     */
    style: null,

    /**
     * APIProperty: url
     * {String} If this property is set it will be taken into account by
     *     {<OpenLayers.HTTP>} when upadting or deleting the feature.
     */
    url: null,
    
    /**
     * Property: renderIntent
     * {String} rendering intent currently being used
     */
    renderIntent: "default",
    
    /**
     * APIProperty: modified
     * {Object} An object with the originals of the geometry and attributes of
     * the feature, if they were changed. Currently this property is only read
     * by <OpenLayers.Format.WFST.v1>, and written by
     * <OpenLayers.Control.ModifyFeature>, which sets the geometry property.
     * Applications can set the originals of modified attributes in the
     * attributes property. Note that applications have to check if this
     * object and the attributes property is already created before using it.
     * After a change made with ModifyFeature, this object could look like
     *
     * (code)
     * {
     *     geometry: >Object
     * }
     * (end)
     *
     * When an application has made changes to feature attributes, it could
     * have set the attributes to something like this:
     *
     * (code)
     * {
     *     attributes: {
     *         myAttribute: "original"
     *     }
     * }
     * (end)
     *
     * Note that <OpenLayers.Format.WFST.v1> only checks for truthy values in
     * *modified.geometry* and the attribute names in *modified.attributes*,
     * but it is recommended to set the original values (and not just true) as
     * attribute value, so applications could use this information to undo
     * changes.
     */
    modified: null,

    /** 
     * 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;
        this.modified = 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: getVisibility
     * Determine whether the feature is displayed or not. It may not displayed
     *     because:
     *     - its style display property is set to 'none',
     *     - it doesn't belong to any layer,
     *     - the styleMap creates a symbolizer with display property set to 'none'
     *          for it,
     *     - the layer which it belongs to is not visible.
     * 
     * Returns:
     * {Boolean} The feature is currently displayed.
     */
    getVisibility: function() {
        return !(this.style && this.style.display == 'none' ||
                 !this.layer ||
                 this.layer && this.layer.styleMap &&
                 this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' ||
                 this.layer && !this.layer.getVisibility());
    },
    
    /**
     * 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>|Object} OpenLayers.LonLat or an
     *     object with a 'lon' and 'lat' properties.
     * 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:
     * location - {<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 undefined;
        }

        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. These
 *     styles correspond for the most part, to the styling properties defined
 *     by the SVG standard. 
 *     Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties
 *     Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties
 *
 * Symbolizer properties:
 * fill - {Boolean} Set to false if no fill is desired.
 * fillColor - {String} Hex fill color.  Default is "#ee9900".
 * fillOpacity - {Number} Fill opacity (0-1).  Default is 0.4 
 * stroke - {Boolean} Set to false if no stroke is desired.
 * strokeColor - {String} Hex stroke color.  Default is "#ee9900".
 * strokeOpacity - {Number} Stroke opacity (0-1).  Default is 1.
 * strokeWidth - {Number} Pixel stroke width.  Default is 1.
 * strokeLinecap - {String} Stroke cap type.  Default is "round".  [butt | round | square]
 * strokeDashstyle - {String} Stroke dash style.  Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid]
 * graphic - {Boolean} Set to false if no graphic is desired.
 * pointRadius - {Number} Pixel point radius.  Default is 6.
 * pointerEvents - {String}  Default is "visiblePainted".
 * cursor - {String} Default is "".
 * externalGraphic - {String} Url to an external graphic that will be used for rendering points.
 * graphicWidth - {Number} Pixel width for sizing an external graphic.
 * graphicHeight - {Number} Pixel height for sizing an external graphic.
 * graphicOpacity - {Number} Opacity (0-1) for an external graphic.
 * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic.
 * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic.
 * rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset).
 * graphicZIndex - {Number} The integer z-index value to use in rendering.
 * graphicName - {String} Named graphic to use when rendering points.  Supported values include "circle" (default),
 *     "square", "star", "x", "cross", "triangle".
 * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead
 * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer.
 * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic.
 * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic.
 * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic.
 * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic.
 * backgroundHeight - {Number} The height of the background graphic.  If not provided, the graphicHeight will be used.
 * backgroundWidth - {Number} The width of the background width.  If not provided, the graphicWidth will be used.
 * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either
 *     fillText or mozDrawText to be available.
 * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string
 *     composed of two characters. The first character is for the horizontal alignment, the second for the vertical
 *     alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical
 *     alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm".
 * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer.
 * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer.
 * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls.
 *     Default is false.
 * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers.
 * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the  SVG renderers.
 * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers.
 * fontColor - {String} The font color for the label, to be provided like CSS.
 * fontOpacity - {Number} Opacity (0-1) for the label
 * fontFamily - {String} The font family for the label, to be provided like in CSS.
 * fontSize - {String} The font size for the label, to be provided like in CSS.
 * fontStyle - {String} The font style for the label, to be provided like in CSS.
 * fontWeight - {String} The font weight for the label, to be provided like in CSS.
 * display - {String} Symbolizers will have no effect if display is set to "none".  All other values have no effect.
 */ 
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",
        fontColor: "#000000",
        labelAlign: "cm",
        labelOutlineColor: "white",
        labelOutlineWidth: 3
    },
    '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",
        fontColor: "#000000",
        labelAlign: "cm",
        labelOutlineColor: "white",
        labelOutlineWidth: 3

    },
    'temporary': {
        fillColor: "#66cccc",
        fillOpacity: 0.2, 
        hoverFillColor: "white",
        hoverFillOpacity: 0.8,
        strokeColor: "#66cccc",
        strokeOpacity: 1,
        strokeLinecap: "round",
        strokeWidth: 2,
        strokeDashstyle: "solid",
        hoverStrokeColor: "red",
        hoverStrokeOpacity: 1,
        hoverStrokeWidth: 0.2,
        pointRadius: 6,
        hoverPointRadius: 1,
        hoverPointUnit: "%",
        pointerEvents: "visiblePainted",
        cursor: "inherit",
        fontColor: "#000000",
        labelAlign: "cm",
        labelOutlineColor: "white",
        labelOutlineWidth: 3

    },
    'delete': {
        display: "none"
    }
};    
/* ======================================================================
    OpenLayers/Style.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/Feature/Vector.js
 */

/**
 * Class: OpenLayers.Style
 * This class represents a UserStyle obtained
 *     from a SLD, containing styling rules.
 */
OpenLayers.Style = OpenLayers.Class({

    /**
     * Property: id
     * {String} A unique id for this session.
     */
    id: null,
    
    /**
     * 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,
    
    /**
     * APIProperty: 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. If <defaultsPerSymbolizer> is set to
     * true, the defaultStyle will only be taken into account if there are
     * rules defined.
     */
    defaultStyle: null,
    
    /**
     * Property: defaultsPerSymbolizer
     * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
     * of every rule. Properties of the <defaultStyle> will also be used to set
     * missing symbolizer properties if the symbolizer has stroke, fill or
     * graphic set to true. Default is false.
     */
    defaultsPerSymbolizer: false,
    
    /**
     * 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
     *     style.
     *
     * Valid options:
     * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
     *     style.
     * 
     * Returns:
     * {<OpenLayers.Style>}
     */
    initialize: function(style, options) {

        OpenLayers.Util.extend(this, options);
        this.rules = [];
        if(options && options.rules) {
            this.addRules(options.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"]);

        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
    },

    /** 
     * 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.defaultsPerSymbolizer ? {} : 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";
        }
        
        if (style.label != null && typeof style.label !== "string") {
            style.label = String(style.label);
        }
        
        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;
        
        if(this.defaultsPerSymbolizer === true) {
            var defaults = this.defaultStyle;
            OpenLayers.Util.applyDefaults(symbolizer, {
                pointRadius: defaults.pointRadius
            });
            if(symbolizer.stroke === true || symbolizer.graphic === true) {
                OpenLayers.Util.applyDefaults(symbolizer, {
                    strokeWidth: defaults.strokeWidth,
                    strokeColor: defaults.strokeColor,
                    strokeOpacity: defaults.strokeOpacity,
                    strokeDashstyle: defaults.strokeDashstyle,
                    strokeLinecap: defaults.strokeLinecap
                });
            }
            if(symbolizer.fill === true || symbolizer.graphic === true) {
                OpenLayers.Util.applyDefaults(symbolizer, {
                    fillColor: defaults.fillColor,
                    fillOpacity: defaults.fillOpacity
                });
            }
            if(symbolizer.graphic === true) {
                OpenLayers.Util.applyDefaults(symbolizer, {
                    pointRadius: this.defaultStyle.pointRadius,
                    externalGraphic: this.defaultStyle.externalGraphic,
                    graphicName: this.defaultStyle.graphicName,
                    graphicOpacity: this.defaultStyle.graphicOpacity,
                    graphicWidth: this.defaultStyle.graphicWidth,
                    graphicHeight: this.defaultStyle.graphicHeight,
                    graphicXOffset: this.defaultStyle.graphicXOffset,
                    graphicYOffset: this.defaultStyle.graphicYOffset
                });
            }
        }

        // 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 = OpenLayers.Util.extend({}, feature.attributes || feature.data);
        OpenLayers.Util.extend(context, this.context);
        
        for (var i in this.propertyStyles) {
            style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
        }
        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++) {
            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) {
        Array.prototype.push.apply(this.rules, 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];
            }
        }
    },
    
    /**
     * APIMethod: clone
     * Clones this style.
     * 
     * Returns:
     * {<OpenLayers.Style>} Clone of this style.
     */
    clone: function() {
        var options = OpenLayers.Util.extend({}, this);
        // clone rules
        if(this.rules) {
            options.rules = [];
            for(var i=0, len=this.rules.length; i<len; ++i) {
                options.rules.push(this.rules[i].clone());
            }
        }
        // clone context
        options.context = this.context && OpenLayers.Util.extend({}, this.context);
        //clone default style
        var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
        return new OpenLayers.Style(defaultStyle, options);
    },
    
    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>} optional feature to pass to
 *           <OpenLayers.String.format> for evaluating functions in the
 *           context.
 * property - {String} optional, name of the property for which the literal is
 *            being created 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, property) {
    if (typeof value == "string" && value.indexOf("${") != -1) {
        value = OpenLayers.String.format(value, context, [feature, property]);
        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',
    'Raster'];
/* ======================================================================
    OpenLayers/Filter.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/Style.js
 */

/**
 * Class: OpenLayers.Filter
 * This class represents an OGC Filter.
 */
OpenLayers.Filter = OpenLayers.Class({
    
    /** 
     * Constructor: OpenLayers.Filter
     * This class represents a generic filter.
     *
     * 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.  Instances or subclasses
     * are supposed to override this method.
     * 
     * Parameters:
     * context - {Object} Context to use in evaluating the filter.  If a vector
     *     feature is provided, the feature.attributes will be used as context.
     * 
     * Returns:
     * {Boolean} The filter applies.
     */
    evaluate: function(context) {
        return true;
    },
    
    /**
     * APIMethod: clone
     * Clones this filter. Should be implemented by subclasses.
     * 
     * Returns:
     * {<OpenLayers.Filter>} Clone of this filter.
     */
    clone: function() {
        return null;
    },
    
    /**
     * APIMethod: toString
     *
     * Returns:
     * {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
     *     representation of the filter returned. Otherwise "[Object object]"
     *     will be returned.
     */
    toString: function() {
        var string;
        if (OpenLayers.Format && OpenLayers.Format.CQL) {
            string = OpenLayers.Format.CQL.prototype.write(this);
        } else {
            string = Object.prototype.toString.call(this);
        }
        return string;
    },
    
    CLASS_NAME: "OpenLayers.Filter"
});
/* ======================================================================
    OpenLayers/Filter/Spatial.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Filter.js
 */

/**
 * Class: OpenLayers.Filter.Spatial
 * This class represents a spatial filter.
 * Currently implemented: BBOX, DWithin and Intersects
 * 
 * Inherits from:
 * - <OpenLayers.Filter>
 */
OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, {

    /**
     * APIProperty: type
     * {String} Type of spatial filter.
     *
     * The type should be one of:
     * - OpenLayers.Filter.Spatial.BBOX
     * - OpenLayers.Filter.Spatial.INTERSECTS
     * - OpenLayers.Filter.Spatial.DWITHIN
     * - OpenLayers.Filter.Spatial.WITHIN
     * - OpenLayers.Filter.Spatial.CONTAINS
     */
    type: null,
    
    /**
     * APIProperty: property
     * {String} Name of the context property to compare.
     */
    property: null,
    
    /**
     * APIProperty: value
     * {<OpenLayers.Bounds> || <OpenLayers.Geometry>} The bounds or geometry
     *     to be used by the filter.  Use bounds for BBOX filters and geometry
     *     for INTERSECTS or DWITHIN filters.
     */
    value: null,

    /**
     * APIProperty: distance
     * {Number} The distance to use in a DWithin spatial filter.
     */
    distance: null,

    /**
     * APIProperty: distanceUnits
     * {String} The units to use for the distance, e.g. 'm'.
     */
    distanceUnits: null,
    
    /** 
     * Constructor: OpenLayers.Filter.Spatial
     * Creates a spatial filter.
     *
     * Parameters:
     * options - {Object} An optional object with properties to set on the
     *     filter.
     * 
     * Returns:
     * {<OpenLayers.Filter.Spatial>}
     */

   /**
    * Method: evaluate
    * Evaluates this filter for a specific feature.
    * 
    * Parameters:
    * feature - {<OpenLayers.Feature.Vector>} feature to apply the filter to.
    * 
    * Returns:
    * {Boolean} The feature meets filter criteria.
    */
    evaluate: function(feature) {
        var intersect = false;
        switch(this.type) {
            case OpenLayers.Filter.Spatial.BBOX:
            case OpenLayers.Filter.Spatial.INTERSECTS:
                if(feature.geometry) {
                    var geom = this.value;
                    if(this.value.CLASS_NAME == "OpenLayers.Bounds") {
                        geom = this.value.toGeometry();
                    }
                    if(feature.geometry.intersects(geom)) {
                        intersect = true;
                    }
                }
                break;
            default:
                throw new Error('evaluate is not implemented for this filter type.');
        }
        return intersect;
    },

    /**
     * APIMethod: clone
     * Clones this filter.
     * 
     * Returns:
     * {<OpenLayers.Filter.Spatial>} Clone of this filter.
     */
    clone: function() {
        var options = OpenLayers.Util.applyDefaults({
            value: this.value && this.value.clone && this.value.clone()
        }, this);
        return new OpenLayers.Filter.Spatial(options);
    },
    CLASS_NAME: "OpenLayers.Filter.Spatial"
});

OpenLayers.Filter.Spatial.BBOX = "BBOX";
OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
/* ======================================================================
    OpenLayers/Filter/FeatureId.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/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 inside the params object.
     */
    fids: null,
    
    /** 
     * Property: type
     * {String} Type to identify this filter.
     */
    type: "FID",
    
    /** 
     * 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;
    },
    
    /**
     * APIMethod: clone
     * Clones this filter.
     * 
     * Returns:
     * {<OpenLayers.Filter.FeatureId>} Clone of this filter.
     */
    clone: function() {
        var filter = new OpenLayers.Filter.FeatureId();
        OpenLayers.Util.extend(filter, this);
        filter.fids = this.fids.slice();
        return filter;
    },
    
    CLASS_NAME: "OpenLayers.Filter.FeatureId"
});
/* ======================================================================
    OpenLayers/Format/WFST/v1.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML.js
 * @requires OpenLayers/Format/WFST.js
 * @requires OpenLayers/Filter/Spatial.js
 * @requires OpenLayers/Filter/FeatureId.js
 */

/**
 * Class: OpenLayers.Format.WFST.v1
 * Superclass for WFST parsers.
 *
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
OpenLayers.Format.WFST.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
    
    /**
     * Property: namespaces
     * {Object} Mapping of namespace aliases to namespace URIs.
     */
    namespaces: {
        xlink: "http://www.w3.org/1999/xlink",
        xsi: "http://www.w3.org/2001/XMLSchema-instance",
        wfs: "http://www.opengis.net/wfs",
        gml: "http://www.opengis.net/gml",
        ogc: "http://www.opengis.net/ogc",
        ows: "http://www.opengis.net/ows"
    },
    
    /**
     * Property: defaultPrefix
     */
    defaultPrefix: "wfs",

    /**
     * Property: version
     * {String} WFS version number.
     */
    version: null,

    /**
     * Property: schemaLocation
     * {String} Schema location for a particular minor version.
     */
    schemaLocations: null,
    
    /**
     * APIProperty: srsName
     * {String} URI for spatial reference system.
     */
    srsName: null,

    /**
     * APIProperty: extractAttributes
     * {Boolean} Extract attributes from GML.  Default is true.
     */
    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,

    /**
     * Property: stateName
     * {Object} Maps feature states to node names.
     */
    stateName: null,
    
    /**
     * Constructor: OpenLayers.Format.WFST.v1
     * Instances of this class are not created directly.  Use the
     *     <OpenLayers.Format.WFST.v1_0_0> or <OpenLayers.Format.WFST.v1_1_0>
     *     constructor instead.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */
    initialize: function(options) {
        // set state name mapping
        this.stateName = {};
        this.stateName[OpenLayers.State.INSERT] = "wfs:Insert";
        this.stateName[OpenLayers.State.UPDATE] = "wfs:Update";
        this.stateName[OpenLayers.State.DELETE] = "wfs:Delete";
        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
    },
    
    /**
     * Method: getSrsName
     */
    getSrsName: function(feature, options) {
        var srsName = options && options.srsName;
        if(!srsName) {
            if(feature && feature.layer) {
                srsName = feature.layer.projection.getCode();
            } else {
                srsName = this.srsName;
            }
        }
        return srsName;
    },

    /**
     * APIMethod: read
     * Parse the response from a transaction.  Because WFS is split into
     *     Transaction requests (create, update, and delete) and GetFeature
     *     requests (read), this method handles parsing of both types of
     *     responses.
     *
     * Parameters:
     * data - {String | Document} The WFST document to read
     * options - {Object} Options for the reader
     *
     * Valid options properties:
     * output - {String} either "features" or "object". The default is
     *     "features", which means that the method will return an array of
     *     features. If set to "object", an object with a "features" property
     *     and other properties read by the parser will be returned.
     *
     * Returns:
     * {Array | Object} Output depending on the output option.
     */
    read: function(data, options) {
        options = options || {};
        OpenLayers.Util.applyDefaults(options, {
            output: "features"
        });
        
        if(typeof data == "string") { 
            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
        }
        if(data && data.nodeType == 9) {
            data = data.documentElement;
        }
        var obj = {};
        if(data) {
            this.readNode(data, obj, true);
        }
        if(obj.features && options.output === "features") {
            obj = obj.features;
        }
        return obj;
    },
    
    /**
     * 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: {
        "wfs": {
            "FeatureCollection": function(node, obj) {
                obj.features = [];
                this.readChildNodes(node, obj);
            }
        }
    },
    
    /**
     * Method: write
     * Given an array of features, write a WFS transaction.  This assumes
     *     the features have a state property that determines the operation
     *     type - insert, update, or delete.
     *
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>)} A list of features. See
     *     below for a more detailed description of the influence of the
     *     feature's *modified* property.
     * options - {Object}
     *
     * feature.modified rules:
     * If a feature has a modified property set, the following checks will be
     * made before a feature's geometry or attribute is included in an Update
     * transaction:
     * - *modified* is not set at all: The geometry and all attributes will be
     *     included.
     * - *modified.geometry* is set (null or a geometry): The geometry will be
     *     included. If *modified.attributes* is not set, all attributes will
     *     be included.
     * - *modified.attributes* is set: Only the attributes set (i.e. to null or
     *     a value) in *modified.attributes* will be included. 
     *     If *modified.geometry* is not set, the geometry will not be included.
     *
     * Valid options include:
     * - *multi* {Boolean} If set to true, geometries will be casted to
     *   Multi geometries before writing.
     *
     * Returns:
     * {String} A serialized WFS transaction.
     */
    write: function(features, options) {
        var node = this.writeNode("wfs:Transaction", {
            features:features,
            options: options
        });
        var value = this.schemaLocationAttr();
        if(value) {
            this.setAttributeNS(
                node, this.namespaces["xsi"], "xsi:schemaLocation",  value
            );
        }
        return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
    },
    
    /**
     * 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: {
        "wfs": {
            "GetFeature": function(options) {
                var node = this.createElementNSPlus("wfs:GetFeature", {
                    attributes: {
                        service: "WFS",
                        version: this.version,
                        handle: options && options.handle,
                        outputFormat: options && options.outputFormat,
                        maxFeatures: options && options.maxFeatures,
                        "xsi:schemaLocation": this.schemaLocationAttr(options)
                    }
                });
                if (typeof this.featureType == "string") {
                    this.writeNode("Query", options, node);
                } else {
                    for (var i=0,len = this.featureType.length; i<len; i++) { 
                        options.featureType = this.featureType[i]; 
                        this.writeNode("Query", options, node); 
                    } 
                }
                return node;
            },
            "Transaction": function(obj) {
                obj = obj || {};
                var options = obj.options || {};
                var node = this.createElementNSPlus("wfs:Transaction", {
                    attributes: {
                        service: "WFS",
                        version: this.version,
                        handle: options.handle
                    }
                });
                var i, len;
                var features = obj.features;
                if(features) {
                    // temporarily re-assigning geometry types
                    if (options.multi === true) {
                        OpenLayers.Util.extend(this.geometryTypes, {
                            "OpenLayers.Geometry.Point": "MultiPoint",
                            "OpenLayers.Geometry.LineString": (this.multiCurve === true) ? "MultiCurve": "MultiLineString",
                            "OpenLayers.Geometry.Polygon": (this.multiSurface === true) ? "MultiSurface" : "MultiPolygon"
                        });
                    }
                    var name, feature;
                    for(i=0, len=features.length; i<len; ++i) {
                        feature = features[i];
                        name = this.stateName[feature.state];
                        if(name) {
                            this.writeNode(name, {
                                feature: feature, 
                                options: options
                            }, node);
                        }
                    }
                    // switch back to original geometry types assignment
                    if (options.multi === true) {
                        this.setGeometryTypes();
                    }
                }
                if (options.nativeElements) {
                    for (i=0, len=options.nativeElements.length; i<len; ++i) {
                        this.writeNode("wfs:Native", 
                            options.nativeElements[i], node);
                    }
                }
                return node;
            },
            "Native": function(nativeElement) {
                var node = this.createElementNSPlus("wfs:Native", {
                    attributes: {
                        vendorId: nativeElement.vendorId,
                        safeToIgnore: nativeElement.safeToIgnore
                    },
                    value: nativeElement.value
                });
                return node;
            },
            "Insert": function(obj) {
                var feature = obj.feature;
                var options = obj.options;
                var node = this.createElementNSPlus("wfs:Insert", {
                    attributes: {
                        handle: options && options.handle
                    }
                });
                this.srsName = this.getSrsName(feature);
                this.writeNode("feature:_typeName", feature, node);
                return node;
            },
            "Update": function(obj) {
                var feature = obj.feature;
                var options = obj.options;
                var node = this.createElementNSPlus("wfs:Update", {
                    attributes: {
                        handle: options && options.handle,
                        typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
                            this.featureType
                    }
                });
                if(this.featureNS) {
                    node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
                }
                
                // add in geometry
                var modified = feature.modified;
                if (this.geometryName !== null && (!modified || modified.geometry !== undefined)) {
                    this.srsName = this.getSrsName(feature);
                    this.writeNode(
                        "Property", {name: this.geometryName, value: feature.geometry}, node
                    );
                }
        
                // add in attributes
                for(var key in feature.attributes) {
                    if(feature.attributes[key] !== undefined &&
                                (!modified || !modified.attributes ||
                                (modified.attributes && modified.attributes[key] !== undefined))) {
                        this.writeNode(
                            "Property", {name: key, value: feature.attributes[key]}, node
                        );
                    }
                }
                
                // add feature id filter
                this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
                    fids: [feature.fid]
                }), node);
        
                return node;
            },
            "Property": function(obj) {
                var node = this.createElementNSPlus("wfs:Property");
                this.writeNode("Name", obj.name, node);
                if(obj.value !== null) {
                    this.writeNode("Value", obj.value, node);
                }
                return node;
            },
            "Name": function(name) {
                return this.createElementNSPlus("wfs:Name", {value: name});
            },
            "Value": function(obj) {
                var node;
                if(obj instanceof OpenLayers.Geometry) {
                    node = this.createElementNSPlus("wfs:Value");
                    var geom = this.writeNode("feature:_geometry", obj).firstChild;
                    node.appendChild(geom);
                } else {
                    node = this.createElementNSPlus("wfs:Value", {value: obj});                
                }
                return node;
            },
            "Delete": function(obj) {
                var feature = obj.feature;
                var options = obj.options;
                var node = this.createElementNSPlus("wfs:Delete", {
                    attributes: {
                        handle: options && options.handle,
                        typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
                            this.featureType
                    }
                });
                if(this.featureNS) {
                    node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
                }
                this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
                    fids: [feature.fid]
                }), node);
                return node;
            }
        }
    },

    /**
     * Method: schemaLocationAttr
     * Generate the xsi:schemaLocation attribute value.
     *
     * Returns:
     * {String} The xsi:schemaLocation attribute or undefined if none.
     */
    schemaLocationAttr: function(options) {
        options = OpenLayers.Util.extend({
            featurePrefix: this.featurePrefix,
            schema: this.schema
        }, options);
        var schemaLocations = OpenLayers.Util.extend({}, this.schemaLocations);
        if(options.schema) {
            schemaLocations[options.featurePrefix] = options.schema;
        }
        var parts = [];
        var uri;
        for(var key in schemaLocations) {
            uri = this.namespaces[key];
            if(uri) {
                parts.push(uri + " " + schemaLocations[key]);
            }
        }
        var value = parts.join(" ") || undefined;
        return value;
    },
    
    /**
     * Method: setFilterProperty
     * Set the property of each spatial filter.
     *
     * Parameters:
     * filter - {<OpenLayers.Filter>}
     */
    setFilterProperty: function(filter) {
        if(filter.filters) {
            for(var i=0, len=filter.filters.length; i<len; ++i) {
                OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this, filter.filters[i]);
            }
        } else {
            if(filter instanceof OpenLayers.Filter.Spatial && !filter.property) {
                // got a spatial filter without property, so set it
                filter.property = this.geometryName;
            }
        }
    },

    CLASS_NAME: "OpenLayers.Format.WFST.v1" 

});
/* ======================================================================
    OpenLayers/Format/OGCExceptionReport.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML.js
 */

/**
 * Class: OpenLayers.Format.OGCExceptionReport
 * Class to read exception reports for various OGC services and versions.
 *
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
OpenLayers.Format.OGCExceptionReport = OpenLayers.Class(OpenLayers.Format.XML, {

    /**
     * Property: namespaces
     * {Object} Mapping of namespace aliases to namespace URIs.
     */
    namespaces: {
        ogc: "http://www.opengis.net/ogc"
    },

    /**
     * Property: regExes
     * Compiled regular expressions for manipulating strings.
     */
    regExes: {
        trimSpace: (/^\s*|\s*$/g),
        removeSpace: (/\s*/g),
        splitSpace: (/\s+/),
        trimComma: (/\s*,\s*/g)
    },

    /**
     * Property: defaultPrefix
     */
    defaultPrefix: "ogc",

    /**
     * Constructor: OpenLayers.Format.OGCExceptionReport
     * Create a new parser for OGC exception reports.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */

    /**
     * APIMethod: read
     * Read OGC exception report data from a string, and return an object with
     * information about the exceptions.
     *
     * Parameters:
     * data - {String} or {DOMElement} data to read/parse.
     *
     * Returns:
     * {Object} Information about the exceptions that occurred.
     */
    read: function(data) {
        var result;
        if(typeof data == "string") {
            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
        }
        var root = data.documentElement;
        var exceptionInfo = {exceptionReport: null}; 
        if (root) {
            this.readChildNodes(data, exceptionInfo);
            if (exceptionInfo.exceptionReport === null) {
                // fall-back to OWSCommon since this is a common output format for exceptions
                // we cannot easily use the ows readers directly since they differ for 1.0 and 1.1
                exceptionInfo = new OpenLayers.Format.OWSCommon().read(data);
            }
        }
        return exceptionInfo;
    },

    /**
     * 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": {
            "ServiceExceptionReport": function(node, obj) {
                obj.exceptionReport = {exceptions: []};
                this.readChildNodes(node, obj.exceptionReport);
            },
            "ServiceException": function(node, exceptionReport) {
                var exception = {
                    code: node.getAttribute("code"),
                    locator: node.getAttribute("locator"),
                    text: this.getChildValue(node)
                };
                exceptionReport.exceptions.push(exception);
            }
        }
    },
    
    CLASS_NAME: "OpenLayers.Format.OGCExceptionReport"
    
});
/* ======================================================================
    OpenLayers/Format/XML/VersionedOGC.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML.js
 * @requires OpenLayers/Format/OGCExceptionReport.js
 */

/**
 * Class: OpenLayers.Format.XML.VersionedOGC
 * Base class for versioned formats, i.e. a format which supports multiple
 * versions.
 *
 * To enable checking if parsing succeeded, you will need to define a property
 * called errorProperty on the parser you want to check. The parser will then
 * check the returned object to see if that property is present. If it is, it
 * assumes the parsing was successful. If it is not present (or is null), it will
 * pass the document through an OGCExceptionReport parser.
 * 
 * If errorProperty is undefined for the parser, this error checking mechanism
 * will be disabled.
 *
 *
 * 
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
OpenLayers.Format.XML.VersionedOGC = OpenLayers.Class(OpenLayers.Format.XML, {
    
    /**
     * APIProperty: defaultVersion
     * {String} Version number to assume if none found.
     */
    defaultVersion: null,
    
    /**
     * APIProperty: version
     * {String} Specify a version string if one is known.
     */
    version: null,

    /**
     * APIProperty: profile
     * {String} If provided, use a custom profile.
     */
    profile: null,

    /**
     * APIProperty: allowFallback
     * {Boolean} If a profiled parser cannot be found for the returned version,
     * use a non-profiled parser as the fallback. Application code using this
     * should take into account that the return object structure might be
     * missing the specifics of the profile. Defaults to false.
     */
    allowFallback: false,

    /**
     * Property: name
     * {String} The name of this parser, this is the part of the CLASS_NAME
     * except for "OpenLayers.Format."
     */
    name: null,

    /**
     * APIProperty: stringifyOutput
     * {Boolean} If true, write will return a string otherwise a DOMElement.
     * Default is false.
     */
    stringifyOutput: false,

    /**
     * Property: parser
     * {Object} Instance of the versioned parser.  Cached for multiple read and
     *     write calls of the same version.
     */
    parser: null,

    /**
     * Constructor: OpenLayers.Format.XML.VersionedOGC.
     * Constructor.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on
     *     the object.
     */
    initialize: function(options) {
        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
        var className = this.CLASS_NAME;
        this.name = className.substring(className.lastIndexOf(".")+1);
    },

    /**
     * Method: getVersion
     * Returns the version to use. Subclasses can override this function
     * if a different version detection is needed.
     *
     * Parameters:
     * root - {DOMElement}
     * options - {Object} Optional configuration object.
     *
     * Returns:
     * {String} The version to use.
     */
    getVersion: function(root, options) {
        var version;
        // read
        if (root) {
            version = this.version;
            if(!version) {
                version = root.getAttribute("version");
                if(!version) {
                    version = this.defaultVersion;
                }
            }
        } else { // write
            version = (options && options.version) || 
                this.version || this.defaultVersion;
        }
        return version;
    },

    /**
     * Method: getParser
     * Get an instance of the cached parser if available, otherwise create one.
     *
     * Parameters:
     * version - {String}
     *
     * Returns:
     * {<OpenLayers.Format>}
     */
    getParser: function(version) {
        version = version || this.defaultVersion;
        var profile = this.profile ? "_" + this.profile : "";
        if(!this.parser || this.parser.VERSION != version) {
            var format = OpenLayers.Format[this.name][
                "v" + version.replace(/\./g, "_") + profile
            ];
            if(!format) {
                if (profile !== "" && this.allowFallback) {
                    // fallback to the non-profiled version of the parser
                    profile = "";
                    format = OpenLayers.Format[this.name][
                        "v" + version.replace(/\./g, "_")
                    ];
                }
                if (!format) {
                    throw "Can't find a " + this.name + " parser for version " +
                          version + profile;
                }
            }
            this.parser = new format(this.options);
        }
        return this.parser;
    },

    /**
     * APIMethod: write
     * Write a document.
     *
     * Parameters:
     * obj - {Object} An object representing the document.
     * options - {Object} Optional configuration object.
     *
     * Returns:
     * {String} The document as a string
     */
    write: function(obj, options) {
        var version = this.getVersion(null, options);
        this.parser = this.getParser(version);
        var root = this.parser.write(obj, options);
        if (this.stringifyOutput === false) {
            return root;
        } else {
            return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
        }
    },

    /**
     * APIMethod: read
     * Read a doc and return an object representing the document.
     *
     * Parameters:
     * data - {String | DOMElement} Data to read.
     * options - {Object} Options for the reader.
     *
     * Returns:
     * {Object} An object representing the document.
     */
    read: function(data, options) {
        if(typeof data == "string") {
            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
        }
        var root = data.documentElement;
        var version = this.getVersion(root);
        this.parser = this.getParser(version);          // Select the parser
        var obj = this.parser.read(data, options);      // Parse the data

        var errorProperty = this.parser.errorProperty || null;
        if (errorProperty !== null && obj[errorProperty] === undefined) {
            // an error must have happened, so parse it and report back
            var format = new OpenLayers.Format.OGCExceptionReport();
            obj.error = format.read(data);
        }
        obj.version = version;
        return obj;
    },

    CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
});
/* ======================================================================
    OpenLayers/Filter/Logical.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/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.Logical.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.
     * 
     * Parameters:
     * context - {Object} Context to use in evaluating the filter.  A vector
     *     feature may also be provided to evaluate feature attributes in 
     *     comparison filters or geometries in spatial filters.
     * 
     * Returns:
     * {Boolean} The filter applies.
     */
    evaluate: function(context) {
        var i, len;
        switch(this.type) {
            case OpenLayers.Filter.Logical.AND:
                for (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 (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));
        }
        return undefined;
    },
    
    /**
     * APIMethod: clone
     * Clones this filter.
     * 
     * Returns:
     * {<OpenLayers.Filter.Logical>} Clone of this filter.
     */
    clone: function() {
        var filters = [];        
        for(var i=0, len=this.filters.length; i<len; ++i) {
            filters.push(this.filters[i].clone());
        }
        return new OpenLayers.Filter.Logical({
            type: this.type,
            filters: filters
        });
    },
    
    CLASS_NAME: "OpenLayers.Filter.Logical"
});


OpenLayers.Filter.Logical.AND = "&&";
OpenLayers.Filter.Logical.OR  = "||";
OpenLayers.Filter.Logical.NOT = "!";
/* ======================================================================
    OpenLayers/Filter/Comparison.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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                     = "~";
     * - OpenLayers.Filter.Comparison.IS_NULL                  = "NULL";
     */
    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,
    
    /**
     * Property: matchCase
     * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO
     *     comparisons.  The Filter Encoding 1.1 specification added a matchCase
     *     attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo
     *     elements.  This property will be serialized with those elements only
     *     if using the v1.1.0 filter format. However, when evaluating filters
     *     here, the matchCase property will always be respected (for EQUAL_TO
     *     and NOT_EQUAL_TO).  Default is true. 
     */
    matchCase: true,
    
    /**
     * 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]);
        // since matchCase on PropertyIsLike is not schema compliant, we only
        // want to use this if explicitly asked for
        if (this.type === OpenLayers.Filter.Comparison.LIKE 
            && options.matchCase === undefined) {
                this.matchCase = null;
        }
    },

    /**
     * APIMethod: evaluate
     * Evaluates this filter in a specific context.
     * 
     * Parameters:
     * context - {Object} Context to use in evaluating the filter.  If a vector
     *     feature is provided, the feature.attributes will be used as context.
     * 
     * Returns:
     * {Boolean} The filter applies.
     */
    evaluate: function(context) {
        if (context instanceof OpenLayers.Feature.Vector) {
            context = context.attributes;
        }
        var result = false;
        var got = context[this.property];
        var exp;
        switch(this.type) {
            case OpenLayers.Filter.Comparison.EQUAL_TO:
                exp = this.value;
                if(!this.matchCase &&
                   typeof got == "string" && typeof exp == "string") {
                    result = (got.toUpperCase() == exp.toUpperCase());
                } else {
                    result = (got == exp);
                }
                break;
            case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
                exp = this.value;
                if(!this.matchCase &&
                   typeof got == "string" && typeof exp == "string") {
                    result = (got.toUpperCase() != exp.toUpperCase());
                } else {
                    result = (got != exp);
                }
                break;
            case OpenLayers.Filter.Comparison.LESS_THAN:
                result = got < this.value;
                break;
            case OpenLayers.Filter.Comparison.GREATER_THAN:
                result = got > this.value;
                break;
            case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
                result = got <= this.value;
                break;
            case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
                result = got >= this.value;
                break;
            case OpenLayers.Filter.Comparison.BETWEEN:
                result = (got >= this.lowerBoundary) &&
                    (got <= this.upperBoundary);
                break;
            case OpenLayers.Filter.Comparison.LIKE:
                var regexp = new RegExp(this.value, "gi");
                result = regexp.test(got);
                break;
            case OpenLayers.Filter.Comparison.IS_NULL:
                result = (got === null);
                break;
        }
        return result;
    },
    
    /**
     * 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 "."
     * escapeChar - {Char} escape character in the above value, default is
     *              "!"
     * 
     * Returns:
     * {String} regular expression string
     */
    value2regex: function(wildCard, singleChar, escapeChar) {
        if (wildCard == ".") {
            throw new Error("'.' is an unsupported wildCard character for " +
                            "OpenLayers.Filter.Comparison");
        }
        

        // 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;
    },
    
    /**
     * APIMethod: clone
     * Clones this filter.
     * 
     * Returns:
     * {<OpenLayers.Filter.Comparison>} Clone of this filter.
     */
    clone: function() {
        return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this);
    },
    
    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.Comparison.IS_NULL                  = "NULL";
/* ======================================================================
    OpenLayers/Format/Filter.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML/VersionedOGC.js
 * @requires OpenLayers/Filter/FeatureId.js
 * @requires OpenLayers/Filter/Logical.js
 * @requires OpenLayers/Filter/Comparison.js
 */

/**
 * Class: OpenLayers.Format.Filter
 * Read/Write ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
 *     constructor.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.XML.VersionedOGC>
 */
OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
    
    /**
     * APIProperty: defaultVersion
     * {String} Version number to assume if none found.  Default is "1.0.0".
     */
    defaultVersion: "1.0.0",
    
    /**
     * 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.
     */
    
    /**
     * 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.
     */

    CLASS_NAME: "OpenLayers.Format.Filter" 
});
/* ======================================================================
    OpenLayers/Filter/Function.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Filter.js
 */

/**
 * Class: OpenLayers.Filter.Function
 * This class represents a filter function.
 * We are using this class for creation of complex 
 * filters that can contain filter functions as values.
 * Nesting function as other functions parameter is supported.
 * 
 * Inherits from:
 * - <OpenLayers.Filter>
 */
OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, {

    /**
     * APIProperty: name
     * {String} Name of the function.
     */
    name: null,
    
    /**
     * APIProperty: params
     * {Array(<OpenLayers.Filter.Function> || String || Number)} Function parameters
     * For now support only other Functions, String or Number
     */
    params: null,  
    
    /** 
     * Constructor: OpenLayers.Filter.Function
     * Creates a filter function.
     *
     * Parameters:
     * options - {Object} An optional object with properties to set on the
     *     function.
     * 
     * Returns:
     * {<OpenLayers.Filter.Function>}
     */

    CLASS_NAME: "OpenLayers.Filter.Function"
});

/* ======================================================================
    OpenLayers/BaseTypes/Date.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/SingleFile.js
 */

/**
 * Namespace: OpenLayers.Date
 * Contains implementations of Date.parse and date.toISOString that match the
 *     ECMAScript 5 specification for parsing RFC 3339 dates.
 *     http://tools.ietf.org/html/rfc3339
 */
OpenLayers.Date = {

    /** 
     * APIProperty: dateRegEx
     * The regex to be used for validating dates. You can provide your own
     * regex for instance for adding support for years before BC. Default
     * value is: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/
     */
    dateRegEx: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,

    /**
     * APIMethod: toISOString
     * Generates a string representing a date.  The format of the string follows
     *     the profile of ISO 8601 for date and time on the Internet (see
     *     http://tools.ietf.org/html/rfc3339).  If the toISOString method is
     *     available on the Date prototype, that is used.  The toISOString
     *     method for Date instances is defined in ECMA-262.
     *
     * Parameters:
     * date - {Date} A date object.
     *
     * Returns:
     * {String} A string representing the date (e.g.
     *     "2010-08-07T16:58:23.123Z").  If the date does not have a valid time
     *     (i.e. isNaN(date.getTime())) this method returns the string "Invalid
     *     Date".  The ECMA standard says the toISOString method should throw
     *     RangeError in this case, but Firefox returns a string instead.  For
     *     best results, use isNaN(date.getTime()) to determine date validity
     *     before generating date strings.
     */
    toISOString: (function() {
        if ("toISOString" in Date.prototype) {
            return function(date) {
                return date.toISOString();
            };
        } else {
            return function(date) {
                var str;
                if (isNaN(date.getTime())) {
                    // ECMA-262 says throw RangeError, Firefox returns
                    // "Invalid Date"
                    str = "Invalid Date";
                } else {
                    str =
                        date.getUTCFullYear() + "-" +
                        OpenLayers.Number.zeroPad(date.getUTCMonth() + 1, 2) + "-" +
                        OpenLayers.Number.zeroPad(date.getUTCDate(), 2) + "T" +
                        OpenLayers.Number.zeroPad(date.getUTCHours(), 2) + ":" +
                        OpenLayers.Number.zeroPad(date.getUTCMinutes(), 2) + ":" +
                        OpenLayers.Number.zeroPad(date.getUTCSeconds(), 2) + "." +
                        OpenLayers.Number.zeroPad(date.getUTCMilliseconds(), 3) + "Z";
                }
                return str;
            };
        }

    })(),

    /**
     * APIMethod: parse
     * Generate a date object from a string.  The format for the string follows
     *     the profile of ISO 8601 for date and time on the Internet (see
     *     http://tools.ietf.org/html/rfc3339).  We don't call the native
     *     Date.parse because of inconsistency between implmentations.  In
     *     Chrome, calling Date.parse with a string that doesn't contain any
     *     indication of the timezone (e.g. "2011"), the date is interpreted
     *     in local time.  On Firefox, the assumption is UTC.
     *
     * Parameters:
     * str - {String} A string representing the date (e.g.
     *     "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z",
     *     "2010-08-07T11:58:23.123-06").
     *
     * Returns:
     * {Date} A date object.  If the string could not be parsed, an invalid
     *     date is returned (i.e. isNaN(date.getTime())).
     */
    parse: function(str) {
        var date;
        var match = str.match(this.dateRegEx);
        if (match && (match[1] || match[7])) { // must have at least year or time
            var year = parseInt(match[1], 10) || 0;
            var month = (parseInt(match[2], 10) - 1) || 0;
            var day = parseInt(match[3], 10) || 1;
            date = new Date(Date.UTC(year, month, day));
            // optional time
            var type = match[7];
            if (type) {
                var hours = parseInt(match[4], 10);
                var minutes = parseInt(match[5], 10);
                var secFrac = parseFloat(match[6]);
                var seconds = secFrac | 0;
                var milliseconds = Math.round(1000 * (secFrac - seconds));
                date.setUTCHours(hours, minutes, seconds, milliseconds);
                // check offset
                if (type !== "Z") {
                    var hoursOffset = parseInt(type, 10);
                    var minutesOffset = parseInt(match[8], 10) || 0;
                    var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60);
                    date = new Date(date.getTime() + offset);
                }
            }
        } else {
            date = new Date("invalid");
        }
        return date;
    }
};
/* ======================================================================
    OpenLayers/Format/Filter/v1.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */
/**
 * @requires OpenLayers/Format/Filter.js
 * @requires OpenLayers/Format/XML.js
 * @requires OpenLayers/Filter/Function.js
 * @requires OpenLayers/BaseTypes/Date.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",
        gml: "http://www.opengis.net/gml",
        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 = {};
        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": {
            "_expression": function(node) {
                // only the simplest of ogc:expression handled
                // "some text and an <PropertyName>attribute</PropertyName>"}
                var obj, value = "";
                for(var child=node.firstChild; child; child=child.nextSibling) {
                    switch(child.nodeType) {
                        case 1:
                            obj = this.readNode(child);
                            if (obj.property) {
                                value += "${" + obj.property + "}";
                            } else if (obj.value !== undefined) {
                                value += obj.value;
                            }
                            break;
                        case 3: // text node
                        case 4: // cdata section
                            value += child.nodeValue;
                    }
                }
                return value;
            },
            "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);
            },
            "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);
            },
            "Literal": function(node, obj) {
                obj.value = OpenLayers.String.numericIf(
                    this.getChildValue(node), true);
            },
            "PropertyName": function(node, filter) {
                filter.property = this.getChildValue(node);
            },
            "LowerBoundary": function(node, filter) {
                filter.lowerBoundary = OpenLayers.String.numericIf(
                    this.readers.ogc._expression.call(this, node), true);
            },
            "UpperBoundary": function(node, filter) {
                filter.upperBoundary = OpenLayers.String.numericIf(
                    this.readers.ogc._expression.call(this, node), true);
            },
            "Intersects": function(node, obj) {
                this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS);
            },
            "Within": function(node, obj) {
                this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN);
            },
            "Contains": function(node, obj) {
                this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS);
            },
            "DWithin": function(node, obj) {
                this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
            },
            "Distance": function(node, obj) {
                obj.distance = parseInt(this.getChildValue(node));
                obj.distanceUnits = node.getAttribute("units");
            },
            "Function": function(node, obj) {
                //TODO write decoder for it
                return;
            },
            "PropertyIsNull": function(node, obj) {
                var filter = new OpenLayers.Filter.Comparison({
                    type: OpenLayers.Filter.Comparison.IS_NULL
                });
                this.readChildNodes(node, filter);
                obj.filters.push(filter);
            }
        }
    },
    
    /**
     * Method: readSpatial
     *
     * Read a {<OpenLayers.Filter.Spatial>} filter.
     * 
     * Parameters:
     * node - {DOMElement} A DOM element that contains an ogc:expression.
     * obj - {Object} The target object.
     * type - {String} One of the OpenLayers.Filter.Spatial.* constants.
     *
     * Returns:
     * {<OpenLayers.Filter.Spatial>} The created filter.
     */
    readSpatial: function(node, obj, type) {
        var filter = new OpenLayers.Filter.Spatial({
            type: type
        });
        this.readChildNodes(node, filter);
        filter.value = filter.components[0];
        delete filter.components;
        obj.filters.push(filter);
    },

    /**
     * APIMethod: encodeLiteral
     * Generates the string representation of a value for use in <Literal> 
     *     elements.  The default encoder writes Date values as ISO 8601 
     *     strings.
     *
     * Parameters:
     * value - {Object} Literal value to encode
     *
     * Returns:
     * {String} String representation of the provided value.
     */
    encodeLiteral: function(value) {
        if (value instanceof Date) {
            value = OpenLayers.Date.toISOString(value);
        }
        return value;
    },

    /**
     * Method: writeOgcExpression
     * Limited support for writing OGC expressions. Currently it supports
     * (<OpenLayers.Filter.Function> || String || Number)
     *
     * Parameters:
     * value - (<OpenLayers.Filter.Function> || String || Number)
     * node - {DOMElement} A parent DOM element 
     *
     * Returns:
     * {DOMElement} Updated node element.
     */
    writeOgcExpression: function(value, node) {
        if (value instanceof OpenLayers.Filter.Function){
            this.writeNode("Function", value, node);
        } else {
            this.writeNode("Literal", value, node);
        }
        return node;
    },    
    
    /**
     * 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");
                this.writeNode(this.getFilterType(filter), filter, node);
                return node;
            },
            "_featureIds": function(filter) {
                var node = this.createDocumentFragment();
                for (var i=0, ii=filter.fids.length; i<ii; ++i) {
                    this.writeNode("ogc:FeatureId", filter.fids[i], node);
                }
                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, ii=filter.filters.length; i<ii; ++i) {
                    childFilter = filter.filters[i];
                    this.writeNode(
                        this.getFilterType(childFilter), childFilter, node
                    );
                }
                return node;
            },
            "Or": function(filter) {
                var node = this.createElementNSPlus("ogc:Or");
                var childFilter;
                for (var i=0, ii=filter.filters.length; i<ii; ++i) {
                    childFilter = filter.filters[i];
                    this.writeNode(
                        this.getFilterType(childFilter), childFilter, node
                    );
                }
                return node;
            },
            "Not": function(filter) {
                var node = this.createElementNSPlus("ogc:Not");
                var childFilter = filter.filters[0];
                this.writeNode(
                    this.getFilterType(childFilter), childFilter, node
                );
                return node;
            },
            "PropertyIsLessThan": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsGreaterThan": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsLessThanOrEqualTo": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsGreaterThanOrEqualTo": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsBetween": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsBetween");
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                this.writeNode("LowerBoundary", filter, node);
                this.writeNode("UpperBoundary", filter, node);
                return node;
            },
            "PropertyName": function(filter) {
                // no ogc:expression handling for now
                return this.createElementNSPlus("ogc:PropertyName", {
                    value: filter.property
                });
            },
            "Literal": function(value) {
                var encode = this.encodeLiteral ||
                    OpenLayers.Format.Filter.v1.prototype.encodeLiteral;
                return this.createElementNSPlus("ogc:Literal", {
                    value: encode(value)
                });
            },
            "LowerBoundary": function(filter) {
                // handle Literals or Functions for now
                var node = this.createElementNSPlus("ogc:LowerBoundary");
                this.writeOgcExpression(filter.lowerBoundary, node);
                return node;
            },
            "UpperBoundary": function(filter) {
                // handle Literals or Functions for now
                var node = this.createElementNSPlus("ogc:UpperBoundary");
                this.writeNode("Literal", filter.upperBoundary, node);
                return node;
            },
            "INTERSECTS": function(filter) {
                return this.writeSpatial(filter, "Intersects");
            },
            "WITHIN": function(filter) {
                return this.writeSpatial(filter, "Within");
            },
            "CONTAINS": function(filter) {
                return this.writeSpatial(filter, "Contains");
            },
            "DWITHIN": function(filter) {
                var node = this.writeSpatial(filter, "DWithin");
                this.writeNode("Distance", filter, node);
                return node;
            },
            "Distance": function(filter) {
                return this.createElementNSPlus("ogc:Distance", {
                    attributes: {
                        units: filter.distanceUnits
                    },
                    value: filter.distance
                });
            },
            "Function": function(filter) {
                var node = this.createElementNSPlus("ogc:Function", {
                    attributes: {
                        name: filter.name
                    }
                });
                var params = filter.params;
                for(var i=0, len=params.length; i<len; i++){
                    this.writeOgcExpression(params[i], node);
                }
                return node;
            },
            "PropertyIsNull": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsNull");
                this.writeNode("PropertyName", filter, node);
                return node;
            }
        }
    },

    /**
     * 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",
        "NULL": "PropertyIsNull",
        "BBOX": "BBOX",
        "DWITHIN": "DWITHIN",
        "WITHIN": "WITHIN",
        "CONTAINS": "CONTAINS",
        "INTERSECTS": "INTERSECTS",
        "FID": "_featureIds"
    },

    CLASS_NAME: "OpenLayers.Format.Filter.v1" 

});
/* ======================================================================
    OpenLayers/Geometry.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */
 
/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.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.
 *
 * Note that if you use the <OpenLayers.Geometry.fromWKT> method, you must
 * explicitly include the OpenLayers.Format.WKT in your build.
 */
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();
    },
    
    /**
     * Method: setBounds
     * Set the bounds for this Geometry.
     * 
     * Parameters:
     * bounds - {<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.
        //
    },
    
    /**
     * APIMethod: distanceTo
     * Calculate the closest distance between two geometries (on the x-y plane).
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} The target geometry.
     * options - {Object} Optional properties for configuring the distance
     *     calculation.
     *
     * Valid options depend on the specific geometry type.
     * 
     * Returns:
     * {Number | Object} The distance between this geometry and the target.
     *     If details is true, the return will be an object with distance,
     *     x0, y0, x1, and x2 properties.  The x0 and y0 properties represent
     *     the coordinates of the closest point on this geometry. The x1 and y1
     *     properties represent the coordinates of the closest point on the
     *     target geometry.
     */
    distanceTo: function(geometry, options) {
    },
    
    /**
     * APIMethod: getVertices
     * Return a list of all points in this geometry.
     *
     * Parameters:
     * nodes - {Boolean} For lines, only return vertices that are
     *     endpoints.  If false, for lines, only vertices that are not
     *     endpoints will be returned.  If not provided, all vertices will
     *     be returned.
     *
     * Returns:
     * {Array} A list of all vertices in the geometry.
     */
    getVertices: function(nodes) {
    },

    /**
     * Method: atPoint
     * Note - This is only an approximation based on the bounds of the 
     * geometry.
     * 
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
     *     object with a 'lon' and 'lat' properties.
     * 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;
    },
    
    /**
     * APIMethod: getCentroid
     * Calculate the centroid of this geometry. This method is defined in subclasses.
     *
     * Returns:
     * {<OpenLayers.Geometry.Point>} The centroid of the collection
     */
    getCentroid: function() {
        return null;
    },

    /**
     * Method: toString
     * Returns a text representation of the geometry.  If the WKT format is
     *     included in a build, this will be the Well-Known Text 
     *     representation.
     *
     * Returns:
     * {String} String representation of this geometry.
     */
    toString: function() {
        var string;
        if (OpenLayers.Format && OpenLayers.Format.WKT) {
            string = OpenLayers.Format.WKT.prototype.write(
                new OpenLayers.Feature.Vector(this)
            );
        } else {
            string = Object.prototype.toString.call(this);
        }
        return string;
    },

    CLASS_NAME: "OpenLayers.Geometry"
});

/**
 * Function: OpenLayers.Geometry.fromWKT
 * Generate a geometry given a Well-Known Text string.  For this method to
 *     work, you must include the OpenLayers.Format.WKT in your build 
 *     explicitly.
 *
 * Parameters:
 * wkt - {String} A string representing the geometry in Well-Known Text.
 *
 * Returns:
 * {<OpenLayers.Geometry>} A geometry of the appropriate class.
 */
OpenLayers.Geometry.fromWKT = function(wkt) {
    var geom;
    if (OpenLayers.Format && OpenLayers.Format.WKT) {
        var format = OpenLayers.Geometry.fromWKT.format;
        if (!format) {
            format = new OpenLayers.Format.WKT();
            OpenLayers.Geometry.fromWKT.format = format;
        }
        var result = format.read(wkt);
        if (result instanceof OpenLayers.Feature.Vector) {
            geom = result.geometry;
        } else if (OpenLayers.Util.isArray(result)) {
            var len = result.length;
            var components = new Array(len);
            for (var i=0; i<len; ++i) {
                components[i] = result[i].geometry;
            }
            geom = new OpenLayers.Geometry.Collection(components);
        }
    }
    return geom;
};
    
/**
 * 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.
 * options - {Object} Optional properties for calculating the intersection.
 *
 * Valid options:
 * 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.
 * tolerance - {Number} If a non-null value is provided, if the segments are
 *     within the tolerance distance, this will be considered an intersection.
 *     In addition, if the point option is true and the calculated intersection
 *     is within the tolerance distance of an end point, the endpoint will be
 *     returned instead of the calculated intersection.  Further, if the
 *     intersection is within the tolerance of endpoints on both segments, or
 *     if two segment endpoints are within the tolerance distance of eachother
 *     (but no intersection is otherwise calculated), an endpoint on the
 *     first segment provided 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, options) {
    var point = options && options.point;
    var tolerance = options && options.tolerance;
    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);
            }
        }
    }
    if(tolerance) {
        var dist;
        if(intersection) {
            if(point) {
                var segs = [seg1, seg2];
                var seg, x, y;
                // check segment endpoints for proximity to intersection
                // set intersection to first endpoint within the tolerance
                outer: for(var i=0; i<2; ++i) {
                    seg = segs[i];
                    for(var j=1; j<3; ++j) {
                        x = seg["x" + j];
                        y = seg["y" + j];
                        dist = Math.sqrt(
                            Math.pow(x - intersection.x, 2) +
                            Math.pow(y - intersection.y, 2)
                        );
                        if(dist < tolerance) {
                            intersection.x = x;
                            intersection.y = y;
                            break outer;
                        }
                    }
                }
                
            }
        } else {
            // no calculated intersection, but segments could be within
            // the tolerance of one another
            var segs = [seg1, seg2];
            var source, target, x, y, p, result;
            // check segment endpoints for proximity to intersection
            // set intersection to first endpoint within the tolerance
            outer: for(var i=0; i<2; ++i) {
                source = segs[i];
                target = segs[(i+1)%2];
                for(var j=1; j<3; ++j) {
                    p = {x: source["x"+j], y: source["y"+j]};
                    result = OpenLayers.Geometry.distanceToSegment(p, target);
                    if(result.distance < tolerance) {
                        if(point) {
                            intersection = new OpenLayers.Geometry.Point(p.x, p.y);
                        } else {
                            intersection = true;
                        }
                        break outer;
                    }
                }
            }
        }
    }
    return intersection;
};

/**
 * Function: OpenLayers.Geometry.distanceToSegment
 *
 * Parameters:
 * point - {Object} An object with x and y properties representing the
 *     point coordinates.
 * segment - {Object} An object with x1, y1, x2, and y2 properties
 *     representing endpoint coordinates.
 *
 * Returns:
 * {Object} An object with distance, along, x, and y properties.  The distance
 *     will be the shortest distance between the input point and segment.
 *     The x and y properties represent the coordinates along the segment
 *     where the shortest distance meets the segment. The along attribute
 *     describes how far between the two segment points the given point is.
 */
OpenLayers.Geometry.distanceToSegment = function(point, segment) {
    var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
    result.distance = Math.sqrt(result.distance);
    return result;
};

/**
 * Function: OpenLayers.Geometry.distanceSquaredToSegment
 *
 * Usually the distanceToSegment function should be used. This variant however
 * can be used for comparisons where the exact distance is not important.
 *
 * Parameters:
 * point - {Object} An object with x and y properties representing the
 *     point coordinates.
 * segment - {Object} An object with x1, y1, x2, and y2 properties
 *     representing endpoint coordinates.
 *
 * Returns:
 * {Object} An object with squared distance, along, x, and y properties.
 *     The distance will be the shortest distance between the input point and
 *     segment. The x and y properties represent the coordinates along the
 *     segment where the shortest distance meets the segment. The along
 *     attribute describes how far between the two segment points the given
 *     point is.
 */
OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
    var x0 = point.x;
    var y0 = point.y;
    var x1 = segment.x1;
    var y1 = segment.y1;
    var x2 = segment.x2;
    var y2 = segment.y2;
    var dx = x2 - x1;
    var dy = y2 - y1;
    var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
                (Math.pow(dx, 2) + Math.pow(dy, 2));
    var x, y;
    if(along <= 0.0) {
        x = x1;
        y = y1;
    } else if(along >= 1.0) {
        x = x2;
        y = y2;
    } else {
        x = x1 + along * dx;
        y = y1 + along * dy;
    }
    return {
        distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
        x: x, y: y,
        along: along
    };
};
/* ======================================================================
    OpenLayers/Geometry/Point.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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
     * Calculate the closest distance between two geometries (on the x-y plane).
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} The target geometry.
     * options - {Object} Optional properties for configuring the distance
     *     calculation.
     *
     * Valid options:
     * details - {Boolean} Return details from the distance calculation.
     *     Default is false.
     * edge - {Boolean} Calculate the distance from this geometry to the
     *     nearest edge of the target geometry.  Default is true.  If true,
     *     calling distanceTo from a geometry that is wholly contained within
     *     the target will result in a non-zero distance.  If false, whenever
     *     geometries intersect, calling distanceTo will return 0.  If false,
     *     details cannot be returned.
     *
     * Returns:
     * {Number | Object} The distance between this geometry and the target.
     *     If details is true, the return will be an object with distance,
     *     x0, y0, x1, and x2 properties.  The x0 and y0 properties represent
     *     the coordinates of the closest point on this geometry. The x1 and y1
     *     properties represent the coordinates of the closest point on the
     *     target geometry.
     */
    distanceTo: function(geometry, options) {
        var edge = !(options && options.edge === false);
        var details = edge && options && options.details;
        var distance, x0, y0, x1, y1, result;
        if(geometry instanceof OpenLayers.Geometry.Point) {
            x0 = this.x;
            y0 = this.y;
            x1 = geometry.x;
            y1 = geometry.y;
            distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
            result = !details ?
                distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
        } else {
            result = geometry.distanceTo(this, options);
            if(details) {
                // switch coord order since this geom is target
                result = {
                    x0: result.x1, y0: result.y1,
                    x1: result.x0, y1: result.y0,
                    distance: result.distance
                };
            }
        }
        return result;
    },
    
    /** 
     * APIMethod: equals
     * Determine whether another geometry is equivalent to this one.  Geometries
     *     are considered equivalent if all components have the same coordinates.
     * 
     * Parameters:
     * geom - {<OpenLayers.Geometry.Point>} The geometry to test. 
     *
     * Returns:
     * {Boolean} The supplied geometry is equivalent to this geometry.
     */
    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 geometry by the given displacement along positive x and y axes.
     *     This modifies the position of the geometry and clears the cached
     *     bounds.
     *
     * Parameters:
     * x - {Float} Distance to move geometry in positive x direction. 
     * y - {Float} Distance to move geometry in positive y direction.
     */
    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: getCentroid
     *
     * Returns:
     * {<OpenLayers.Geometry.Point>} The centroid of the collection
     */
    getCentroid: function() {
        return new OpenLayers.Geometry.Point(this.x, this.y);
    },

    /**
     * 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.
     * 
     * Returns:
     * {<OpenLayers.Geometry>} - The current geometry. 
     */
    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();
        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;
        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;
    },

    /**
     * APIMethod: getVertices
     * Return a list of all points in this geometry.
     *
     * Parameters:
     * nodes - {Boolean} For lines, only return vertices that are
     *     endpoints.  If false, for lines, only vertices that are not
     *     endpoints will be returned.  If not provided, all vertices will
     *     be returned.
     *
     * Returns:
     * {Array} A list of all vertices in the geometry.
     */
    getVertices: function(nodes) {
        return [this];
    },

    CLASS_NAME: "OpenLayers.Geometry.Point"
});
/* ======================================================================
    OpenLayers/Geometry/Collection.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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.
 *
 * Inherits 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;
        OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
    },

    /**
     * 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;
        var bounds = new OpenLayers.Bounds();
        var components = this.components;
        if (components) {
            for (var i=0, len=components.length; i<len; i++) {
                bounds.extend(components[i].getBounds());
            }
        }
        // to preserve old behavior, we only set bounds if non-null
        // in the future, we could add bounds.isEmpty()
        if (bounds.left != null && bounds.bottom != null && 
            bounds.right != null && bounds.top != null) {
            this.setBounds(bounds);
        }
    },

    /**
     * APIMethod: addComponents
     * Add components to this geometry.
     *
     * Parameters:
     * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
     */
    addComponents: function(components){
        if(!(OpenLayers.Util.isArray(components))) {
            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
     *
     * Returns: 
     * {Boolean} A component was removed.
     */
    removeComponents: function(components) {
        var removed = false;

        if(!(OpenLayers.Util.isArray(components))) {
            components = [components];
        }
        for(var i=components.length-1; i>=0; --i) {
            removed = this.removeComponent(components[i]) || removed;
        }
        return removed;
    },
    
    /**
     * Method: removeComponent
     * Remove a component from this geometry.
     *
     * Parameters:
     * component - {<OpenLayers.Geometry>} 
     *
     * Returns: 
     * {Boolean} The component was removed.
     */
    removeComponent: function(component) {
        
        OpenLayers.Util.removeItem(this.components, component);
        
        // clearBounds() so that it gets recalculated on the next call
        // to this.getBounds();
        this.clearBounds();
        return true;
    },

    /**
     * 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: getGeodesicArea
     * Calculate the approximate area of the polygon were it projected onto
     *     the earth.
     *
     * Parameters:
     * projection - {<OpenLayers.Projection>} The spatial reference system
     *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
     *     assumed.
     * 
     * Reference:
     * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
     *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
     *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
     *
     * Returns:
     * {float} The approximate geodesic area of the geometry in square meters.
     */
    getGeodesicArea: function(projection) {
        var area = 0.0;
        for(var i=0, len=this.components.length; i<len; i++) {
            area += this.components[i].getGeodesicArea(projection);
        }
        return area;
    },
    
    /**
     * APIMethod: getCentroid
     *
     * Compute the centroid for this geometry collection.
     *
     * Parameters:
     * weighted - {Boolean} Perform the getCentroid computation recursively,
     * returning an area weighted average of all geometries in this collection.
     *
     * Returns:
     * {<OpenLayers.Geometry.Point>} The centroid of the collection
     */
    getCentroid: function(weighted) {
        if (!weighted) {
            return this.components.length && this.components[0].getCentroid();
        }
        var len = this.components.length;
        if (!len) {
            return false;
        }
        
        var areas = [];
        var centroids = [];
        var areaSum = 0;
        var minArea = Number.MAX_VALUE;
        var component;
        for (var i=0; i<len; ++i) {
            component = this.components[i];
            var area = component.getArea();
            var centroid = component.getCentroid(true);
            if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
                continue;
            }
            areas.push(area);
            areaSum += area;
            minArea = (area < minArea && area > 0) ? area : minArea;
            centroids.push(centroid);
        }
        len = areas.length;
        if (areaSum === 0) {
            // all the components in this collection have 0 area
            // probably a collection of points -- weight all the points the same
            for (var i=0; i<len; ++i) {
                areas[i] = 1;
            }
            areaSum = areas.length;
        } else {
            // normalize all the areas where the smallest area will get
            // a value of 1
            for (var i=0; i<len; ++i) {
                areas[i] /= minArea;
            }
            areaSum /= minArea;
        }
        
        var xSum = 0, ySum = 0, centroid, area;
        for (var i=0; i<len; ++i) {
            centroid = centroids[i];
            area = areas[i];
            xSum += centroid.x * area;
            ySum += centroid.y * area;
        }
        
        return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
    },

    /**
     * APIMethod: getGeodesicLength
     * Calculate the approximate length of the geometry were it projected onto
     *     the earth.
     *
     * projection - {<OpenLayers.Projection>} The spatial reference system
     *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
     *     assumed.
     * 
     * Returns:
     * {Float} The appoximate geodesic length of the geometry in meters.
     */
    getGeodesicLength: function(projection) {
        var length = 0.0;
        for(var i=0, len=this.components.length; i<len; i++) {
            length += this.components[i].getGeodesicLength(projection);
        }
        return length;
    },

    /**
     * APIMethod: move
     * Moves a geometry by the given displacement along positive x and y axes.
     *     This modifies the position of the geometry and clears the cached
     *     bounds.
     *
     * Parameters:
     * x - {Float} Distance to move geometry in positive x direction. 
     * y - {Float} Distance to move geometry in positive y direction.
     */
    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.
     * 
     * Returns:
     * {<OpenLayers.Geometry>} - The current geometry. 
     */
    resize: function(scale, origin, ratio) {
        for(var i=0; i<this.components.length; ++i) {
            this.components[i].resize(scale, origin, ratio);
        }
        return this;
    },

    /**
     * APIMethod: distanceTo
     * Calculate the closest distance between two geometries (on the x-y plane).
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} The target geometry.
     * options - {Object} Optional properties for configuring the distance
     *     calculation.
     *
     * Valid options:
     * details - {Boolean} Return details from the distance calculation.
     *     Default is false.
     * edge - {Boolean} Calculate the distance from this geometry to the
     *     nearest edge of the target geometry.  Default is true.  If true,
     *     calling distanceTo from a geometry that is wholly contained within
     *     the target will result in a non-zero distance.  If false, whenever
     *     geometries intersect, calling distanceTo will return 0.  If false,
     *     details cannot be returned.
     *
     * Returns:
     * {Number | Object} The distance between this geometry and the target.
     *     If details is true, the return will be an object with distance,
     *     x0, y0, x1, and y1 properties.  The x0 and y0 properties represent
     *     the coordinates of the closest point on this geometry. The x1 and y1
     *     properties represent the coordinates of the closest point on the
     *     target geometry.
     */
    distanceTo: function(geometry, options) {
        var edge = !(options && options.edge === false);
        var details = edge && options && options.details;
        var result, best, distance;
        var min = Number.POSITIVE_INFINITY;
        for(var i=0, len=this.components.length; i<len; ++i) {
            result = this.components[i].distanceTo(geometry, options);
            distance = details ? result.distance : result;
            if(distance < min) {
                min = distance;
                best = result;
                if(min == 0) {
                    break;
                }
            }
        }
        return best;
    },

    /** 
     * APIMethod: equals
     * Determine whether another geometry is equivalent to this one.  Geometries
     *     are considered equivalent if all components have the same coordinates.
     * 
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} The geometry to test. 
     *
     * Returns:
     * {Boolean} The supplied geometry is equivalent to this geometry.
     */
    equals: function(geometry) {
        var equivalent = true;
        if(!geometry || !geometry.CLASS_NAME ||
           (this.CLASS_NAME != geometry.CLASS_NAME)) {
            equivalent = false;
        } else if(!(OpenLayers.Util.isArray(geometry.components)) ||
                  (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;
    },

    /**
     * APIMethod: getVertices
     * Return a list of all points in this geometry.
     *
     * Parameters:
     * nodes - {Boolean} For lines, only return vertices that are
     *     endpoints.  If false, for lines, only vertices that are not
     *     endpoints will be returned.  If not provided, all vertices will
     *     be returned.
     *
     * Returns:
     * {Array} A list of all vertices in the geometry.
     */
    getVertices: function(nodes) {
        var vertices = [];
        for(var i=0, len=this.components.length; i<len; ++i) {
            Array.prototype.push.apply(
                vertices, this.components[i].getVertices(nodes)
            );
        }
        return vertices;
    },


    CLASS_NAME: "OpenLayers.Geometry.Collection"
});
/* ======================================================================
    OpenLayers/Geometry/MultiPoint.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Geometry/Collection.js
 * @requires OpenLayers/Geometry/Point.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>}
     */

    /**
     * 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/Curve.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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>)}
     */
    
    /**
     * 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;
    },

    /**
     * APIMethod: getGeodesicLength
     * Calculate the approximate length of the geometry were it projected onto
     *     the earth.
     *
     * projection - {<OpenLayers.Projection>} The spatial reference system
     *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
     *     assumed.
     * 
     * Returns:
     * {Float} The appoximate geodesic length of the geometry in meters.
     */
    getGeodesicLength: function(projection) {
        var geom = this;  // so we can work with a clone if needed
        if(projection) {
            var gg = new OpenLayers.Projection("EPSG:4326");
            if(!gg.equals(projection)) {
                geom = this.clone().transform(projection, gg);
            }
        }
        var length = 0.0;
        if(geom.components && (geom.components.length > 1)) {
            var p1, p2;
            for(var i=1, len=geom.components.length; i<len; i++) {
                p1 = geom.components[i-1];
                p2 = geom.components[i];
                // this returns km and requires lon/lat properties
                length += OpenLayers.Util.distVincenty(
                    {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
                );
            }
        }
        // convert to m
        return length * 1000;
    },

    CLASS_NAME: "OpenLayers.Geometry.Curve"
});
/* ======================================================================
    OpenLayers/Geometry/LineString.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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
     *
     */

    /**
     * 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
     *
     * Returns: 
     * {Boolean} The component was removed.
     */
    removeComponent: function(point) {
        var removed = this.components && (this.components.length > 2);
        if (removed) {
            OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
                                                                  arguments);
        }
        return removed;
    },
    
    /**
     * 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), point1, point2;
        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);
    },
    
    /**
     * Method: splitWithSegment
     * Split this geometry with the given segment.
     *
     * Parameters:
     * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
     *     segment endpoint coordinates.
     * options - {Object} Properties of this object will be used to determine
     *     how the split is conducted.
     *
     * Valid options:
     * edge - {Boolean} Allow splitting when only edges intersect.  Default is
     *     true.  If false, a vertex on the source segment must be within the
     *     tolerance distance of the intersection to be considered a split.
     * tolerance - {Number} If a non-null value is provided, intersections
     *     within the tolerance distance of one of the source segment's
     *     endpoints will be assumed to occur at the endpoint.
     *
     * Returns:
     * {Object} An object with *lines* and *points* properties.  If the given
     *     segment intersects this linestring, the lines array will reference
     *     geometries that result from the split.  The points array will contain
     *     all intersection points.  Intersection points are sorted along the
     *     segment (in order from x1,y1 to x2,y2).
     */
    splitWithSegment: function(seg, options) {
        var edge = !(options && options.edge === false);
        var tolerance = options && options.tolerance;
        var lines = [];
        var verts = this.getVertices();
        var points = [];
        var intersections = [];
        var split = false;
        var vert1, vert2, point;
        var node, vertex, target;
        var interOptions = {point: true, tolerance: tolerance};
        var result = null;
        for(var i=0, stop=verts.length-2; i<=stop; ++i) {
            vert1 = verts[i];
            points.push(vert1.clone());
            vert2 = verts[i+1];
            target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
            point = OpenLayers.Geometry.segmentsIntersect(
                seg, target, interOptions
            );
            if(point instanceof OpenLayers.Geometry.Point) {
                if((point.x === seg.x1 && point.y === seg.y1) ||
                   (point.x === seg.x2 && point.y === seg.y2) ||
                   point.equals(vert1) || point.equals(vert2)) {
                    vertex = true;
                } else {
                    vertex = false;
                }
                if(vertex || edge) {
                    // push intersections different than the previous
                    if(!point.equals(intersections[intersections.length-1])) {
                        intersections.push(point.clone());
                    }
                    if(i === 0) {
                        if(point.equals(vert1)) {
                            continue;
                        }
                    }
                    if(point.equals(vert2)) {
                        continue;
                    }
                    split = true;
                    if(!point.equals(vert1)) {
                        points.push(point);
                    }
                    lines.push(new OpenLayers.Geometry.LineString(points));
                    points = [point.clone()];
                }
            }
        }
        if(split) {
            points.push(vert2.clone());
            lines.push(new OpenLayers.Geometry.LineString(points));
        }
        if(intersections.length > 0) {
            // sort intersections along segment
            var xDir = seg.x1 < seg.x2 ? 1 : -1;
            var yDir = seg.y1 < seg.y2 ? 1 : -1;
            result = {
                lines: lines,
                points: intersections.sort(function(p1, p2) {
                    return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
                })
            };
        }
        return result;
    },

    /**
     * Method: split
     * Use this geometry (the source) to attempt to split a target geometry.
     * 
     * Parameters:
     * target - {<OpenLayers.Geometry>} The target geometry.
     * options - {Object} Properties of this object will be used to determine
     *     how the split is conducted.
     *
     * Valid options:
     * mutual - {Boolean} Split the source geometry in addition to the target
     *     geometry.  Default is false.
     * edge - {Boolean} Allow splitting when only edges intersect.  Default is
     *     true.  If false, a vertex on the source must be within the tolerance
     *     distance of the intersection to be considered a split.
     * tolerance - {Number} If a non-null value is provided, intersections
     *     within the tolerance distance of an existing vertex on the source
     *     will be assumed to occur at the vertex.
     * 
     * Returns:
     * {Array} A list of geometries (of this same type as the target) that
     *     result from splitting the target with the source geometry.  The
     *     source and target geometry will remain unmodified.  If no split
     *     results, null will be returned.  If mutual is true and a split
     *     results, return will be an array of two arrays - the first will be
     *     all geometries that result from splitting the source geometry and
     *     the second will be all geometries that result from splitting the
     *     target geometry.
     */
    split: function(target, options) {
        var results = null;
        var mutual = options && options.mutual;
        var sourceSplit, targetSplit, sourceParts, targetParts;
        if(target instanceof OpenLayers.Geometry.LineString) {
            var verts = this.getVertices();
            var vert1, vert2, seg, splits, lines, point;
            var points = [];
            sourceParts = [];
            for(var i=0, stop=verts.length-2; i<=stop; ++i) {
                vert1 = verts[i];
                vert2 = verts[i+1];
                seg = {
                    x1: vert1.x, y1: vert1.y,
                    x2: vert2.x, y2: vert2.y
                };
                targetParts = targetParts || [target];
                if(mutual) {
                    points.push(vert1.clone());
                }
                for(var j=0; j<targetParts.length; ++j) {
                    splits = targetParts[j].splitWithSegment(seg, options);
                    if(splits) {
                        // splice in new features
                        lines = splits.lines;
                        if(lines.length > 0) {
                            lines.unshift(j, 1);
                            Array.prototype.splice.apply(targetParts, lines);
                            j += lines.length - 2;
                        }
                        if(mutual) {
                            for(var k=0, len=splits.points.length; k<len; ++k) {
                                point = splits.points[k];
                                if(!point.equals(vert1)) {
                                    points.push(point);
                                    sourceParts.push(new OpenLayers.Geometry.LineString(points));
                                    if(point.equals(vert2)) {
                                        points = [];
                                    } else {
                                        points = [point.clone()];
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if(mutual && sourceParts.length > 0 && points.length > 0) {
                points.push(vert2.clone());
                sourceParts.push(new OpenLayers.Geometry.LineString(points));
            }
        } else {
            results = target.splitWith(this, options);
        }
        if(targetParts && targetParts.length > 1) {
            targetSplit = true;
        } else {
            targetParts = [];
        }
        if(sourceParts && sourceParts.length > 1) {
            sourceSplit = true;
        } else {
            sourceParts = [];
        }
        if(targetSplit || sourceSplit) {
            if(mutual) {
                results = [sourceParts, targetParts];
            } else {
                results = targetParts;
            }
        }
        return results;
    },

    /**
     * Method: splitWith
     * Split this geometry (the target) with the given geometry (the source).
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} A geometry used to split this
     *     geometry (the source).
     * options - {Object} Properties of this object will be used to determine
     *     how the split is conducted.
     *
     * Valid options:
     * mutual - {Boolean} Split the source geometry in addition to the target
     *     geometry.  Default is false.
     * edge - {Boolean} Allow splitting when only edges intersect.  Default is
     *     true.  If false, a vertex on the source must be within the tolerance
     *     distance of the intersection to be considered a split.
     * tolerance - {Number} If a non-null value is provided, intersections
     *     within the tolerance distance of an existing vertex on the source
     *     will be assumed to occur at the vertex.
     * 
     * Returns:
     * {Array} A list of geometries (of this same type as the target) that
     *     result from splitting the target with the source geometry.  The
     *     source and target geometry will remain unmodified.  If no split
     *     results, null will be returned.  If mutual is true and a split
     *     results, return will be an array of two arrays - the first will be
     *     all geometries that result from splitting the source geometry and
     *     the second will be all geometries that result from splitting the
     *     target geometry.
     */
    splitWith: function(geometry, options) {
        return geometry.split(this, options);

    },

    /**
     * APIMethod: getVertices
     * Return a list of all points in this geometry.
     *
     * Parameters:
     * nodes - {Boolean} For lines, only return vertices that are
     *     endpoints.  If false, for lines, only vertices that are not
     *     endpoints will be returned.  If not provided, all vertices will
     *     be returned.
     *
     * Returns:
     * {Array} A list of all vertices in the geometry.
     */
    getVertices: function(nodes) {
        var vertices;
        if(nodes === true) {
            vertices = [
                this.components[0],
                this.components[this.components.length-1]
            ];
        } else if (nodes === false) {
            vertices = this.components.slice(1, this.components.length-1);
        } else {
            vertices = this.components.slice();
        }
        return vertices;
    },

    /**
     * APIMethod: distanceTo
     * Calculate the closest distance between two geometries (on the x-y plane).
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} The target geometry.
     * options - {Object} Optional properties for configuring the distance
     *     calculation.
     *
     * Valid options:
     * details - {Boolean} Return details from the distance calculation.
     *     Default is false.
     * edge - {Boolean} Calculate the distance from this geometry to the
     *     nearest edge of the target geometry.  Default is true.  If true,
     *     calling distanceTo from a geometry that is wholly contained within
     *     the target will result in a non-zero distance.  If false, whenever
     *     geometries intersect, calling distanceTo will return 0.  If false,
     *     details cannot be returned.
     *
     * Returns:
     * {Number | Object} The distance between this geometry and the target.
     *     If details is true, the return will be an object with distance,
     *     x0, y0, x1, and x2 properties.  The x0 and y0 properties represent
     *     the coordinates of the closest point on this geometry. The x1 and y1
     *     properties represent the coordinates of the closest point on the
     *     target geometry.
     */
    distanceTo: function(geometry, options) {
        var edge = !(options && options.edge === false);
        var details = edge && options && options.details;
        var result, best = {};
        var min = Number.POSITIVE_INFINITY;
        if(geometry instanceof OpenLayers.Geometry.Point) {
            var segs = this.getSortedSegments();
            var x = geometry.x;
            var y = geometry.y;
            var seg;
            for(var i=0, len=segs.length; i<len; ++i) {
                seg = segs[i];
                result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
                if(result.distance < min) {
                    min = result.distance;
                    best = result;
                    if(min === 0) {
                        break;
                    }
                } else {
                    // if distance increases and we cross y0 to the right of x0, no need to keep looking.
                    if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) {
                        break;
                    }
                }
            }
            if(details) {
                best = {
                    distance: best.distance,
                    x0: best.x, y0: best.y,
                    x1: x, y1: y
                };
            } else {
                best = best.distance;
            }
        } else if(geometry instanceof OpenLayers.Geometry.LineString) { 
            var segs0 = this.getSortedSegments();
            var segs1 = geometry.getSortedSegments();
            var seg0, seg1, intersection, x0, y0;
            var len1 = segs1.length;
            var interOptions = {point: true};
            outer: for(var i=0, len=segs0.length; i<len; ++i) {
                seg0 = segs0[i];
                x0 = seg0.x1;
                y0 = seg0.y1;
                for(var j=0; j<len1; ++j) {
                    seg1 = segs1[j];
                    intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
                    if(intersection) {
                        min = 0;
                        best = {
                            distance: 0,
                            x0: intersection.x, y0: intersection.y,
                            x1: intersection.x, y1: intersection.y
                        };
                        break outer;
                    } else {
                        result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
                        if(result.distance < min) {
                            min = result.distance;
                            best = {
                                distance: min,
                                x0: x0, y0: y0,
                                x1: result.x, y1: result.y
                            };
                        }
                    }
                }
            }
            if(!details) {
                best = best.distance;
            }
            if(min !== 0) {
                // check the final vertex in this line's sorted segments
                if(seg0) {
                    result = geometry.distanceTo(
                        new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
                        options
                    );
                    var dist = details ? result.distance : result;
                    if(dist < min) {
                        if(details) {
                            best = {
                                distance: min,
                                x0: result.x1, y0: result.y1,
                                x1: result.x0, y1: result.y0
                            };
                        } else {
                            best = dist;
                        }
                    }
                }
            }
        } else {
            best = geometry.distanceTo(this, options);
            // swap since target comes from this line
            if(details) {
                best = {
                    distance: best.distance,
                    x0: best.x1, y0: best.y1,
                    x1: best.x0, y1: best.y0
                };
            }
        }
        return best;
    },
    
    /**
     * APIMethod: simplify
     * This function will return a simplified LineString.
     * Simplification is based on the Douglas-Peucker algorithm.
     *
     *
     * Parameters:
     * tolerance - {number} threshhold for simplification in map units
     *
     * Returns:
     * {OpenLayers.Geometry.LineString} the simplified LineString
     */
    simplify: function(tolerance){
        if (this && this !== null) {
            var points = this.getVertices();
            if (points.length < 3) {
                return this;
            }
    
            var compareNumbers = function(a, b){
                return (a-b);
            };
    
            /**
             * Private function doing the Douglas-Peucker reduction
             */
            var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
                var maxDistance = 0;
                var indexFarthest = 0;
    
                for (var index = firstPoint, distance; index < lastPoint; index++) {
                    distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
                    if (distance > maxDistance) {
                        maxDistance = distance;
                        indexFarthest = index;
                    }
                }
    
                if (maxDistance > tolerance && indexFarthest != firstPoint) {
                    //Add the largest point that exceeds the tolerance
                    pointIndexsToKeep.push(indexFarthest);
                    douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
                    douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
                }
            };
    
            /**
             * Private function calculating the perpendicular distance
             * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
             */
            var perpendicularDistance = function(point1, point2, point){
                //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)|   *Area of triangle
                //Base = v((x1-x2)²+(x1-x2)²)                               *Base of Triangle*
                //Area = .5*Base*H                                          *Solve for height
                //Height = Area/.5/Base
    
                var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
                var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
                var height = area / bottom * 2;
    
                return height;
            };
    
            var firstPoint = 0;
            var lastPoint = points.length - 1;
            var pointIndexsToKeep = [];
    
            //Add the first and last index to the keepers
            pointIndexsToKeep.push(firstPoint);
            pointIndexsToKeep.push(lastPoint);
    
            //The first and the last point cannot be the same
            while (points[firstPoint].equals(points[lastPoint])) {
                lastPoint--;
                //Addition: the first point not equal to first point in the LineString is kept as well
                pointIndexsToKeep.push(lastPoint);
            }
    
            douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
            var returnPoints = [];
            pointIndexsToKeep.sort(compareNumbers);
            for (var index = 0; index < pointIndexsToKeep.length; index++) {
                returnPoints.push(points[pointIndexsToKeep[index]]);
            }
            return new OpenLayers.Geometry.LineString(returnPoints);
    
        }
        else {
            return this;
        }
    },

    CLASS_NAME: "OpenLayers.Geometry.LineString"
});
/* ======================================================================
    OpenLayers/Geometry/MultiLineString.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Geometry/Collection.js
 * @requires OpenLayers/Geometry/LineString.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>)} 
     *
     */
    
    /**
     * Method: split
     * Use this geometry (the source) to attempt to split a target geometry.
     * 
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} The target geometry.
     * options - {Object} Properties of this object will be used to determine
     *     how the split is conducted.
     *
     * Valid options:
     * mutual - {Boolean} Split the source geometry in addition to the target
     *     geometry.  Default is false.
     * edge - {Boolean} Allow splitting when only edges intersect.  Default is
     *     true.  If false, a vertex on the source must be within the tolerance
     *     distance of the intersection to be considered a split.
     * tolerance - {Number} If a non-null value is provided, intersections
     *     within the tolerance distance of an existing vertex on the source
     *     will be assumed to occur at the vertex.
     * 
     * Returns:
     * {Array} A list of geometries (of this same type as the target) that
     *     result from splitting the target with the source geometry.  The
     *     source and target geometry will remain unmodified.  If no split
     *     results, null will be returned.  If mutual is true and a split
     *     results, return will be an array of two arrays - the first will be
     *     all geometries that result from splitting the source geometry and
     *     the second will be all geometries that result from splitting the
     *     target geometry.
     */
    split: function(geometry, options) {
        var results = null;
        var mutual = options && options.mutual;
        var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
        var sourceParts = [];
        var targetParts = [geometry];
        for(var i=0, len=this.components.length; i<len; ++i) {
            sourceLine = this.components[i];
            sourceSplit = false;
            for(var j=0; j < targetParts.length; ++j) { 
                splits = sourceLine.split(targetParts[j], options);
                if(splits) {
                    if(mutual) {
                        sourceLines = splits[0];
                        for(var k=0, klen=sourceLines.length; k<klen; ++k) {
                            if(k===0 && sourceParts.length) {
                                sourceParts[sourceParts.length-1].addComponent(
                                    sourceLines[k]
                                );
                            } else {
                                sourceParts.push(
                                    new OpenLayers.Geometry.MultiLineString([
                                        sourceLines[k]
                                    ])
                                );
                            }
                        }
                        sourceSplit = true;
                        splits = splits[1];
                    }
                    if(splits.length) {
                        // splice in new target parts
                        splits.unshift(j, 1);
                        Array.prototype.splice.apply(targetParts, splits);
                        break;
                    }
                }
            }
            if(!sourceSplit) {
                // source line was not hit
                if(sourceParts.length) {
                    // add line to existing multi
                    sourceParts[sourceParts.length-1].addComponent(
                        sourceLine.clone()
                    );
                } else {
                    // create a fresh multi
                    sourceParts = [
                        new OpenLayers.Geometry.MultiLineString(
                            sourceLine.clone()
                        )
                    ];
                }
            }
        }
        if(sourceParts && sourceParts.length > 1) {
            sourceSplit = true;
        } else {
            sourceParts = [];
        }
        if(targetParts && targetParts.length > 1) {
            targetSplit = true;
        } else {
            targetParts = [];
        }
        if(sourceSplit || targetSplit) {
            if(mutual) {
                results = [sourceParts, targetParts];
            } else {
                results = targetParts;
            }
        }
        return results;
    },
    
    /**
     * Method: splitWith
     * Split this geometry (the target) with the given geometry (the source).
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} A geometry used to split this
     *     geometry (the source).
     * options - {Object} Properties of this object will be used to determine
     *     how the split is conducted.
     *
     * Valid options:
     * mutual - {Boolean} Split the source geometry in addition to the target
     *     geometry.  Default is false.
     * edge - {Boolean} Allow splitting when only edges intersect.  Default is
     *     true.  If false, a vertex on the source must be within the tolerance
     *     distance of the intersection to be considered a split.
     * tolerance - {Number} If a non-null value is provided, intersections
     *     within the tolerance distance of an existing vertex on the source
     *     will be assumed to occur at the vertex.
     * 
     * Returns:
     * {Array} A list of geometries (of this same type as the target) that
     *     result from splitting the target with the source geometry.  The
     *     source and target geometry will remain unmodified.  If no split
     *     results, null will be returned.  If mutual is true and a split
     *     results, return will be an array of two arrays - the first will be
     *     all geometries that result from splitting the source geometry and
     *     the second will be all geometries that result from splitting the
     *     target geometry.
     */
    splitWith: function(geometry, options) {
        var results = null;
        var mutual = options && options.mutual;
        var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
        if(geometry instanceof OpenLayers.Geometry.LineString) {
            targetParts = [];
            sourceParts = [geometry];
            for(var i=0, len=this.components.length; i<len; ++i) {
                targetSplit = false;
                targetLine = this.components[i];
                for(var j=0; j<sourceParts.length; ++j) {
                    splits = sourceParts[j].split(targetLine, options);
                    if(splits) {
                        if(mutual) {
                            sourceLines = splits[0];
                            if(sourceLines.length) {
                                // splice in new source parts
                                sourceLines.unshift(j, 1);
                                Array.prototype.splice.apply(sourceParts, sourceLines);
                                j += sourceLines.length - 2;
                            }
                            splits = splits[1];
                            if(splits.length === 0) {
                                splits = [targetLine.clone()];
                            }
                        }
                        for(var k=0, klen=splits.length; k<klen; ++k) {
                            if(k===0 && targetParts.length) {
                                targetParts[targetParts.length-1].addComponent(
                                    splits[k]
                                );
                            } else {
                                targetParts.push(
                                    new OpenLayers.Geometry.MultiLineString([
                                        splits[k]
                                    ])
                                );
                            }
                        }
                        targetSplit = true;                    
                    }
                }
                if(!targetSplit) {
                    // target component was not hit
                    if(targetParts.length) {
                        // add it to any existing multi-line
                        targetParts[targetParts.length-1].addComponent(
                            targetLine.clone()
                        );
                    } else {
                        // or start with a fresh multi-line
                        targetParts = [
                            new OpenLayers.Geometry.MultiLineString([
                                targetLine.clone()
                            ])
                        ];
                    }
                    
                }
            }
        } else {
            results = geometry.split(this);
        }
        if(sourceParts && sourceParts.length > 1) {
            sourceSplit = true;
        } else {
            sourceParts = [];
        }
        if(targetParts && targetParts.length > 1) {
            targetSplit = true;
        } else {
            targetParts = [];
        }
        if(sourceSplit || targetSplit) {
            if(mutual) {
                results = [sourceParts, targetParts];
            } else {
                results = targetParts;
            }
        }
        return results;
    },

    CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
});
/* ======================================================================
    OpenLayers/Geometry/LinearRing.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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
     */

    /**
     * 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.
     *
     * Parameters:
     * 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>}
     *
     * Returns: 
     * {Boolean} The component was removed.
     */
    removeComponent: function(point) {
        var removed = this.components && (this.components.length > 3);
        if (removed) {
            //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]);
        }
        return removed;
    },
    
    /**
     * APIMethod: move
     * Moves a geometry by the given displacement along positive x and y axes.
     *     This modifies the position of the geometry and clears the cached
     *     bounds.
     *
     * Parameters:
     * x - {Float} Distance to move geometry in positive x direction. 
     * y - {Float} Distance to move geometry in positive y direction.
     */
    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.
     * 
     * Returns:
     * {<OpenLayers.Geometry>} - The current geometry. 
     */
    resize: function(scale, origin, ratio) {
        for(var i=0, len=this.components.length; i<len - 1; ++i) {
            this.components[i].resize(scale, origin, ratio);
        }
        return this;
    },
    
    /**
     * 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: getCentroid
     *
     * Returns:
     * {<OpenLayers.Geometry.Point>} The centroid of the collection
     */
    getCentroid: function() {
        if (this.components) {
            var len = this.components.length;
            if (len > 0 && len <= 2) {
                return this.components[0].clone();
            } else if (len > 2) {
                var sumX = 0.0;
                var sumY = 0.0;
                var x0 = this.components[0].x;
                var y0 = this.components[0].y;
                var area = -1 * this.getArea();
                if (area != 0) {
                    for (var i = 0; i < len - 1; i++) {
                        var b = this.components[i];
                        var c = this.components[i+1];
                        sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
                        sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
                    }
                    var x = x0 + sumX / (6 * area);
                    var y = y0 + sumY / (6 * area);
                } else {
                    for (var i = 0; i < len - 1; i++) {
                        sumX += this.components[i].x;
                        sumY += this.components[i].y;
                    }
                    var x = sumX / (len - 1);
                    var y = sumY / (len - 1);
                }
                return new OpenLayers.Geometry.Point(x, y);
            } else {
                return null;
            }
        }
    },

    /**
     * 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;
    },
    
    /**
     * APIMethod: getGeodesicArea
     * Calculate the approximate area of the polygon were it projected onto
     *     the earth.  Note that this area will be positive if ring is oriented
     *     clockwise, otherwise it will be negative.
     *
     * Parameters:
     * projection - {<OpenLayers.Projection>} The spatial reference system
     *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
     *     assumed.
     * 
     * Reference:
     * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
     *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
     *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
     *
     * Returns:
     * {float} The approximate signed geodesic area of the polygon in square
     *     meters.
     */
    getGeodesicArea: function(projection) {
        var ring = this;  // so we can work with a clone if needed
        if(projection) {
            var gg = new OpenLayers.Projection("EPSG:4326");
            if(!gg.equals(projection)) {
                ring = this.clone().transform(projection, gg);
            }
        }
        var area = 0.0;
        var len = ring.components && ring.components.length;
        if(len > 2) {
            var p1, p2;
            for(var i=0; i<len-1; i++) {
                p1 = ring.components[i];
                p2 = ring.components[i+1];
                area += OpenLayers.Util.rad(p2.x - p1.x) *
                        (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
                        Math.sin(OpenLayers.Util.rad(p2.y)));
            }
            area = area * 6378137.0 * 6378137.0 / 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 (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
        }
        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;
    },

    /**
     * APIMethod: getVertices
     * Return a list of all points in this geometry.
     *
     * Parameters:
     * nodes - {Boolean} For lines, only return vertices that are
     *     endpoints.  If false, for lines, only vertices that are not
     *     endpoints will be returned.  If not provided, all vertices will
     *     be returned.
     *
     * Returns:
     * {Array} A list of all vertices in the geometry.
     */
    getVertices: function(nodes) {
        return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
    },

    CLASS_NAME: "OpenLayers.Geometry.LinearRing"
});
/* ======================================================================
    OpenLayers/Geometry/Polygon.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Geometry/Collection.js
 * @requires OpenLayers/Geometry/LinearRing.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>)} 
     */

    /** 
     * 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;
    },

    /** 
     * APIMethod: getGeodesicArea
     * Calculate the approximate area of the polygon were it projected onto
     *     the earth.
     *
     * Parameters:
     * projection - {<OpenLayers.Projection>} The spatial reference system
     *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
     *     assumed.
     * 
     * Reference:
     * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
     *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
     *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
     *
     * Returns:
     * {float} The approximate geodesic area of the polygon in square meters.
     */
    getGeodesicArea: function(projection) {
        var area = 0.0;
        if(this.components && (this.components.length > 0)) {
            area += Math.abs(this.components[0].getGeodesicArea(projection));
            for(var i=1, len=this.components.length; i<len; i++) {
                area -= Math.abs(this.components[i].getGeodesicArea(projection));
            }
        }
        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;
    },

    /**
     * APIMethod: distanceTo
     * Calculate the closest distance between two geometries (on the x-y plane).
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} The target geometry.
     * options - {Object} Optional properties for configuring the distance
     *     calculation.
     *
     * Valid options:
     * details - {Boolean} Return details from the distance calculation.
     *     Default is false.
     * edge - {Boolean} Calculate the distance from this geometry to the
     *     nearest edge of the target geometry.  Default is true.  If true,
     *     calling distanceTo from a geometry that is wholly contained within
     *     the target will result in a non-zero distance.  If false, whenever
     *     geometries intersect, calling distanceTo will return 0.  If false,
     *     details cannot be returned.
     *
     * Returns:
     * {Number | Object} The distance between this geometry and the target.
     *     If details is true, the return will be an object with distance,
     *     x0, y0, x1, and y1 properties.  The x0 and y0 properties represent
     *     the coordinates of the closest point on this geometry. The x1 and y1
     *     properties represent the coordinates of the closest point on the
     *     target geometry.
     */
    distanceTo: function(geometry, options) {
        var edge = !(options && options.edge === false);
        var result;
        // this is the case where we might not be looking for distance to edge
        if(!edge && this.intersects(geometry)) {
            result = 0;
        } else {
            result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply(
                this, [geometry, options]
            );
        }
        return result;
    },

    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/Geometry/MultiPolygon.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Geometry/Collection.js
 * @requires OpenLayers/Geometry/Polygon.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
     *
     */

    CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
});
/* ======================================================================
    OpenLayers/Format/GML.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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/Write GML. Create a new instance with the <OpenLayers.Format.GML>
 *     constructor.  Supports the GML simple features profile.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
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 one geometry per feature - look for highest "order"
        var order = ["MultiPolygon", "Polygon",
                     "MultiLineString", "LineString",
                     "MultiPoint", "Point", "Envelope"];
        // FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope,
        // this code creates a geometry derived from the Envelope. This is not correct.
        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
                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 {
                    throw new TypeError("Unsupported geometry type: " + type);
                }
                // stop looking for different geometry types
                break;
            }
        }

        var bounds;
        var boxNodes = this.getElementsByTagNameNS(node, this.gmlns, "Box");
        for(i=0; i<boxNodes.length; ++i) {
            var boxNode = boxNodes[i];
            var box = this.parseGeometry["box"].apply(this, [boxNode]);
            var parentNode = boxNode.parentNode;
            var parentName = parentNode.localName ||
                             parentNode.nodeName.split(":").pop();
            if(parentName === "boundedBy") {
                bounds = box;
            } else {
                geometry = box.toGeometry();
            }
        }
        
        // construct feature (optionally with attributes)
        var attributes;
        if(this.extractAttributes) {
            attributes = this.parseAttributes(node);
        }
        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
        feature.bounds = bounds;
        
        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.getChildValue(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.getChildValue(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: parseGeometry.box
         * Given a GML node representing a box geometry, create an
         *     OpenLayers.Bounds.
         *
         * Parameters:
         * node - {DOMElement} A GML node.
         *
         * Returns:
         * {<OpenLayers.Bounds>} A bounds representing the box.
         */
        box: function(node) {
            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
                                                   "coordinates");
            var coordString;
            var coords, beginPoint = null, endPoint = null;
            if (nodeList.length > 0) {
                coordString = nodeList[0].firstChild.nodeValue;
                coords = coordString.split(" ");
                if (coords.length == 2) {
                    beginPoint = coords[0].split(",");
                    endPoint = coords[1].split(",");
                }
            }
            if (beginPoint !== null && endPoint !== null) {
                return new OpenLayers.Bounds(parseFloat(beginPoint[0]),
                    parseFloat(beginPoint[1]),
                    parseFloat(endPoint[0]),
                    parseFloat(endPoint[1]) );
            }
        }
        
    },
    
    /**
     * 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;
                            }
                        } else {
                            // If child has no childNodes (grandchildren),
                            // set an attribute with null value.
                            // e.g. <prefix:fieldname/> becomes
                            // {fieldname: null}
                            attributes[child.nodeName.split(":").pop()] = null;
                        }
                    }
                }
                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(!(OpenLayers.Util.isArray(features))) {
            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: buildGeometry.bounds
         * Given an OpenLayers bounds, create a GML box.
         *
         * Parameters:
         * bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
         *
         * Returns:
         * {DOMElement} A GML box node.
         */
        bounds: function(bounds) {
            var gml = this.createElementNS(this.gmlns, "gml:Box");
            gml.appendChild(this.buildCoordinatesNode(bounds));
            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 parts = [];

        if(geometry instanceof OpenLayers.Bounds){
            parts.push(geometry.left + "," + geometry.bottom);
            parts.push(geometry.right + "," + geometry.top);
        } else {
            var points = (geometry.components) ? geometry.components : [geometry];
            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/GML/Base.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML.js
 * @requires OpenLayers/Format/GML.js
 */

/**
 * Though required in the full build, if the GML format is excluded, we set
 * the namespace here.
 */
if(!OpenLayers.Format.GML) {
    OpenLayers.Format.GML = {};
}

/**
 * Class: OpenLayers.Format.GML.Base
 * Superclass for GML parsers.
 *
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, {
    
    /**
     * Property: namespaces
     * {Object} Mapping of namespace aliases to namespace URIs.
     */
    namespaces: {
        gml: "http://www.opengis.net/gml",
        xlink: "http://www.w3.org/1999/xlink",
        xsi: "http://www.w3.org/2001/XMLSchema-instance",
        wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
    },
    
    /**
     * Property: defaultPrefix
     */
    defaultPrefix: "gml",

    /**
     * Property: schemaLocation
     * {String} Schema location for a particular minor version.
     */
    schemaLocation: null,
    
    /**
     * APIProperty: featureType
     * {Array(String) or String} The local (without prefix) feature typeName(s).
     */
    featureType: null,
    
    /**
     * APIProperty: featureNS
     * {String} The feature namespace.  Must be set in the options at
     *     construction.
     */
    featureNS: null,

    /**
     * APIProperty: geometry
     * {String} Name of geometry element.  Defaults to "geometry". If null, it
     * will be set on <read> when the first geometry is parsed.
     */
    geometryName: "geometry",

    /**
     * APIProperty: extractAttributes
     * {Boolean} Extract attributes from GML.  Default is true.
     */
    extractAttributes: true,
    
    /**
     * APIProperty: srsName
     * {String} URI for spatial reference system.  This is optional for
     *     single part geometries and mandatory for collections and multis.
     *     If set, the srsName attribute will be written for all geometries.
     *     Default is null.
     */
    srsName: null,

    /**
     * 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,

    /**
     * Property: geometryTypes
     * {Object} Maps OpenLayers geometry class names to GML element names.
     *     Use <setGeometryTypes> before accessing this property.
     */
    geometryTypes: null,

    /**
     * Property: singleFeatureType
     * {Boolean} True if there is only 1 featureType, and not an array
     *     of featuretypes.
     */
    singleFeatureType: null,
    
    /**
     * Property: autoConfig
     * {Boolean} Indicates if the format was configured without a <featureNS>,
     * but auto-configured <featureNS> and <featureType> during read.
     * Subclasses making use of <featureType> auto-configuration should make
     * the first call to the <readNode> method (usually in the read method)
     * with true as 3rd argument, so the auto-configured featureType can be
     * reset and the format can be reused for subsequent reads with data from
     * different featureTypes. Set to false after read if you want to keep the
     * auto-configured values.
     */

    /**
     * Property: regExes
     * Compiled regular expressions for manipulating strings.
     */
    regExes: {
        trimSpace: (/^\s*|\s*$/g),
        removeSpace: (/\s*/g),
        splitSpace: (/\s+/),
        trimComma: (/\s*,\s*/g),
        featureMember: (/^(.*:)?featureMembers?$/)
    },

    /**
     * Constructor: OpenLayers.Format.GML.Base
     * Instances of this class are not created directly.  Use the
     *     <OpenLayers.Format.GML.v2> or <OpenLayers.Format.GML.v3> constructor
     *     instead.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     *
     * Valid options properties:
     * featureType - {Array(String) or String} Local (without prefix) feature 
     *     typeName(s) (required for write).
     * featureNS - {String} Feature namespace (required for write).
     * geometryName - {String} Geometry element name (required for write).
     */
    initialize: function(options) {
        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
        this.setGeometryTypes();
        if(options && options.featureNS) {
            this.setNamespace("feature", options.featureNS);
        }
        this.singleFeatureType = !options || (typeof options.featureType === "string");
    },
    
    /**
     * Method: read
     *
     * Parameters:
     * data - {DOMElement} A gml:featureMember element, a gml:featureMembers
     *     element, or an element containing either of the above at any level.
     *
     * 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]);
        }
        if(data && data.nodeType == 9) {
            data = data.documentElement;
        }
        var features = [];
        this.readNode(data, {features: features}, true);
        if(features.length == 0) {
            // look for gml:featureMember elements
            var elements = this.getElementsByTagNameNS(
                data, this.namespaces.gml, "featureMember"
            );
            if(elements.length) {
                for(var i=0, len=elements.length; i<len; ++i) {
                    this.readNode(elements[i], {features: features}, true);
                }
            } else {
                // look for gml:featureMembers elements (this is v3, but does no harm here)
                var elements = this.getElementsByTagNameNS(
                    data, this.namespaces.gml, "featureMembers"
                );
                if(elements.length) {
                    // there can be only one
                    this.readNode(elements[0], {features: features}, true);
                }
            }
        }
        return features;
    },
    
    /**
     * Method: readNode
     * Shorthand for applying one of the named readers given the node
     *     namespace and local name.  Readers take two args (node, obj) and
     *     generally extend or modify the second.
     *
     * Parameters:
     * node - {DOMElement} The node to be read (required).
     * obj - {Object} The object to be modified (optional).
     * first - {Boolean} Should be set to true for the first node read. This
     *     is usually the readNode call in the read method. Without this being
     *     set, auto-configured properties will stick on subsequent reads.
     *
     * Returns:
     * {Object} The input object, modified (or a new one if none was provided).
     */
    readNode: function(node, obj, first) {
        // on subsequent calls of format.read(), we want to reset auto-
        // configured properties and auto-configure again.
        if (first === true && this.autoConfig === true) {
            this.featureType = null;
            delete this.namespaceAlias[this.featureNS];
            delete this.namespaces["feature"];
            this.featureNS = null;
        }
        // featureType auto-configuration
        if (!this.featureNS && (!(node.prefix in this.namespaces) &&
                node.parentNode.namespaceURI == this.namespaces["gml"] &&
                this.regExes.featureMember.test(node.parentNode.nodeName))) {
            this.featureType = node.nodeName.split(":").pop();
            this.setNamespace("feature", node.namespaceURI);
            this.featureNS = node.namespaceURI;
            this.autoConfig = true;
        }
        return OpenLayers.Format.XML.prototype.readNode.apply(this, [node, obj]);
    },
    
    /**
     * 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: {
        "gml": {
            "_inherit": function(node, obj, container) {
                // To be implemented by version specific parsers
            },
            "featureMember": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "featureMembers": function(node, obj) {
                this.readChildNodes(node, obj);                
            },
            "name": function(node, obj) {
                obj.name = this.getChildValue(node);
            },
            "boundedBy": function(node, obj) {
                var container = {};
                this.readChildNodes(node, container);
                if(container.components && container.components.length > 0) {
                    obj.bounds = container.components[0];
                }
            },
            "Point": function(node, container) {
                var obj = {points: []};
                this.readChildNodes(node, obj);
                if(!container.components) {
                    container.components = [];
                }
                container.components.push(obj.points[0]);
            },
            "coordinates": function(node, obj) {
                var str = this.getChildValue(node).replace(
                    this.regExes.trimSpace, ""
                );
                str = str.replace(this.regExes.trimComma, ",");
                var pointList = str.split(this.regExes.splitSpace);
                var coords;
                var numPoints = pointList.length;
                var points = new Array(numPoints);
                for(var i=0; i<numPoints; ++i) {
                    coords = pointList[i].split(",");
                    if (this.xy) {
                        points[i] = new OpenLayers.Geometry.Point(
                            coords[0], coords[1], coords[2]
                        );
                    } else {
                        points[i] = new OpenLayers.Geometry.Point(
                            coords[1], coords[0], coords[2]
                        );
                    }
                }
                obj.points = points;
            },
            "coord": function(node, obj) {
                var coord = {};
                this.readChildNodes(node, coord);
                if(!obj.points) {
                    obj.points = [];
                }
                obj.points.push(new OpenLayers.Geometry.Point(
                    coord.x, coord.y, coord.z
                ));
            },
            "X": function(node, coord) {
                coord.x = this.getChildValue(node);
            },
            "Y": function(node, coord) {
                coord.y = this.getChildValue(node);
            },
            "Z": function(node, coord) {
                coord.z = this.getChildValue(node);
            },
            "MultiPoint": function(node, container) {
                var obj = {components: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                container.components = [
                    new OpenLayers.Geometry.MultiPoint(obj.components)
                ];
            },
            "pointMember": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "LineString": function(node, container) {
                var obj = {};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                if(!container.components) {
                    container.components = [];
                }
                container.components.push(
                    new OpenLayers.Geometry.LineString(obj.points)
                );
            },
            "MultiLineString": function(node, container) {
                var obj = {components: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                container.components = [
                    new OpenLayers.Geometry.MultiLineString(obj.components)
                ];
            },
            "lineStringMember": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "Polygon": function(node, container) {
                var obj = {outer: null, inner: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                obj.inner.unshift(obj.outer);
                if(!container.components) {
                    container.components = [];
                }
                container.components.push(
                    new OpenLayers.Geometry.Polygon(obj.inner)
                );
            },
            "LinearRing": function(node, obj) {
                var container = {};
                this.readers.gml._inherit.apply(this, [node, container]);
                this.readChildNodes(node, container);
                obj.components = [new OpenLayers.Geometry.LinearRing(
                    container.points
                )];
            },
            "MultiPolygon": function(node, container) {
                var obj = {components: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                container.components = [
                    new OpenLayers.Geometry.MultiPolygon(obj.components)
                ];
            },
            "polygonMember": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "GeometryCollection": function(node, container) {
                var obj = {components: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                container.components = [
                    new OpenLayers.Geometry.Collection(obj.components)
                ];
            },
            "geometryMember": function(node, obj) {
                this.readChildNodes(node, obj);
            }
        },
        "feature": {
            "*": function(node, obj) {
                // The node can either be named like the featureType, or it
                // can be a child of the feature:featureType.  Children can be
                // geometry or attributes.
                var name;
                var local = node.localName || node.nodeName.split(":").pop();
                // Since an attribute can have the same name as the feature type
                // we only want to read the node as a feature if the parent
                // node can have feature nodes as children.  In this case, the
                // obj.features property is set.
                if (obj.features) {
                    if (!this.singleFeatureType &&
                        (OpenLayers.Util.indexOf(this.featureType, local) !== -1)) {
                        name = "_typeName";
                    } else if(local === this.featureType) {
                        name = "_typeName";
                    }
                } else {
                    // Assume attribute elements have one child node and that the child
                    // is a text node.  Otherwise assume it is a geometry node.
                    if(node.childNodes.length == 0 ||
                       (node.childNodes.length == 1 && node.firstChild.nodeType == 3)) {
                        if(this.extractAttributes) {
                            name = "_attribute";
                        }
                    } else {
                        name = "_geometry";
                    }
                }
                if(name) {
                    this.readers.feature[name].apply(this, [node, obj]);
                }
            },
            "_typeName": function(node, obj) {
                var container = {components: [], attributes: {}};
                this.readChildNodes(node, container);
                // look for common gml namespaced elements
                if(container.name) {
                    container.attributes.name = container.name;
                }
                var feature = new OpenLayers.Feature.Vector(
                    container.components[0], container.attributes
                );
                if (!this.singleFeatureType) {
                    feature.type = node.nodeName.split(":").pop();
                    feature.namespace = node.namespaceURI;
                }
                var fid = node.getAttribute("fid") ||
                    this.getAttributeNS(node, this.namespaces["gml"], "id");
                if(fid) {
                    feature.fid = fid;
                }
                if(this.internalProjection && this.externalProjection &&
                   feature.geometry) {
                    feature.geometry.transform(
                        this.externalProjection, this.internalProjection
                    );
                }
                if(container.bounds) {
                    feature.bounds = container.bounds;
                }
                obj.features.push(feature);
            },
            "_geometry": function(node, obj) {
                if (!this.geometryName) {
                    this.geometryName = node.nodeName.split(":").pop();
                }
                this.readChildNodes(node, obj);
            },
            "_attribute": function(node, obj) {
                var local = node.localName || node.nodeName.split(":").pop();
                var value = this.getChildValue(node);
                obj.attributes[local] = value;
            }
        },
        "wfs": {
            "FeatureCollection": function(node, obj) {
                this.readChildNodes(node, obj);
            }
        }
    },
    
    /**
     * Method: write
     *
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
     *     An array of features or a single feature.
     *
     * Returns:
     * {String} Given an array of features, a doc with a gml:featureMembers
     *     element will be returned.  Given a single feature, a doc with a
     *     gml:featureMember element will be returned.
     */
    write: function(features) {
        var name;
        if(OpenLayers.Util.isArray(features)) {
            name = "featureMembers";
        } else {
            name = "featureMember";
        }
        var root = this.writeNode("gml:" + name, features);
        this.setAttributeNS(
            root, this.namespaces["xsi"],
            "xsi:schemaLocation", this.schemaLocation
        );

        return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
    },
    
    /**
     * 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: {
        "gml": {
            "featureMember": function(feature) {
                var node = this.createElementNSPlus("gml:featureMember");
                this.writeNode("feature:_typeName", feature, node);
                return node;
            },
            "MultiPoint": function(geometry) {
                var node = this.createElementNSPlus("gml:MultiPoint");
                var components = geometry.components || [geometry];
                for(var i=0, ii=components.length; i<ii; ++i) {
                    this.writeNode("pointMember", components[i], node);
                }
                return node;
            },
            "pointMember": function(geometry) {
                var node = this.createElementNSPlus("gml:pointMember");
                this.writeNode("Point", geometry, node);
                return node;
            },
            "MultiLineString": function(geometry) {
                var node = this.createElementNSPlus("gml:MultiLineString");
                var components = geometry.components || [geometry];
                for(var i=0, ii=components.length; i<ii; ++i) {
                    this.writeNode("lineStringMember", components[i], node);
                }
                return node;
            },
            "lineStringMember": function(geometry) {
                var node = this.createElementNSPlus("gml:lineStringMember");
                this.writeNode("LineString", geometry, node);
                return node;
            },
            "MultiPolygon": function(geometry) {
                var node = this.createElementNSPlus("gml:MultiPolygon");
                var components = geometry.components || [geometry];
                for(var i=0, ii=components.length; i<ii; ++i) {
                    this.writeNode(
                        "polygonMember", components[i], node
                    );
                }
                return node;
            },
            "polygonMember": function(geometry) {
                var node = this.createElementNSPlus("gml:polygonMember");
                this.writeNode("Polygon", geometry, node);
                return node;
            },
            "GeometryCollection": function(geometry) {
                var node = this.createElementNSPlus("gml:GeometryCollection");
                for(var i=0, len=geometry.components.length; i<len; ++i) {
                    this.writeNode("geometryMember", geometry.components[i], node);
                }
                return node;
            },
            "geometryMember": function(geometry) {
                var node = this.createElementNSPlus("gml:geometryMember");
                var child = this.writeNode("feature:_geometry", geometry);
                node.appendChild(child.firstChild);
                return node;
            }
        },
        "feature": {
            "_typeName": function(feature) {
                var node = this.createElementNSPlus("feature:" + this.featureType, {
                    attributes: {fid: feature.fid}
                });
                if(feature.geometry) {
                    this.writeNode("feature:_geometry", feature.geometry, node);
                }
                for(var name in feature.attributes) {
                    var value = feature.attributes[name];
                    if(value != null) {
                        this.writeNode(
                            "feature:_attribute",
                            {name: name, value: value}, node
                        );
                    }
                }
                return node;
            },
            "_geometry": function(geometry) {
                if(this.externalProjection && this.internalProjection) {
                    geometry = geometry.clone().transform(
                        this.internalProjection, this.externalProjection
                    );
                }    
                var node = this.createElementNSPlus(
                    "feature:" + this.geometryName
                );
                var type = this.geometryTypes[geometry.CLASS_NAME];
                var child = this.writeNode("gml:" + type, geometry, node);
                if(this.srsName) {
                    child.setAttribute("srsName", this.srsName);
                }
                return node;
            },
            "_attribute": function(obj) {
                return this.createElementNSPlus("feature:" + obj.name, {
                    value: obj.value
                });
            }
        },
        "wfs": {
            "FeatureCollection": function(features) {
                /**
                 * This is only here because GML2 only describes abstract
                 * feature collections.  Typically, you would not be using
                 * the GML format to write wfs elements.  This just provides
                 * some way to write out lists of features.  GML3 defines the
                 * featureMembers element, so that is used by default instead.
                 */
                var node = this.createElementNSPlus("wfs:FeatureCollection");
                for(var i=0, len=features.length; i<len; ++i) {
                    this.writeNode("gml:featureMember", features[i], node);
                }
                return node;
            }
        }
    },
    
    /**
     * Method: setGeometryTypes
     * Sets the <geometryTypes> mapping.
     */
    setGeometryTypes: function() {
        this.geometryTypes = {
            "OpenLayers.Geometry.Point": "Point",
            "OpenLayers.Geometry.MultiPoint": "MultiPoint",
            "OpenLayers.Geometry.LineString": "LineString",
            "OpenLayers.Geometry.MultiLineString": "MultiLineString",
            "OpenLayers.Geometry.Polygon": "Polygon",
            "OpenLayers.Geometry.MultiPolygon": "MultiPolygon",
            "OpenLayers.Geometry.Collection": "GeometryCollection"
        };
    },

    CLASS_NAME: "OpenLayers.Format.GML.Base" 

});
/* ======================================================================
    OpenLayers/Format/GML/v3.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/GML/Base.js
 */

/**
 * Class: OpenLayers.Format.GML.v3
 * Parses GML version 3.
 *
 * Inherits from:
 *  - <OpenLayers.Format.GML.Base>
 */
OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
    
    /**
     * Property: schemaLocation
     * {String} Schema location for a particular minor version.  The writers
     *     conform with the Simple Features Profile for GML.
     */
    schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd",

    /**
     * Property: curve
     * {Boolean} Write gml:Curve instead of gml:LineString elements.  This also
     *     affects the elements in multi-part geometries.  Default is false.
     *     To write gml:Curve elements instead of gml:LineString, set curve
     *     to true in the options to the contstructor (cannot be changed after
     *     instantiation).
     */
    curve: false,
    
    /**
     * Property: multiCurve
     * {Boolean} Write gml:MultiCurve instead of gml:MultiLineString.  Since
     *     the latter is deprecated in GML 3, the default is true.  To write
     *     gml:MultiLineString instead of gml:MultiCurve, set multiCurve to
     *     false in the options to the constructor (cannot be changed after
     *     instantiation).
     */
    multiCurve: true,
    
    /**
     * Property: surface
     * {Boolean} Write gml:Surface instead of gml:Polygon elements.  This also
     *     affects the elements in multi-part geometries.  Default is false.
     *     To write gml:Surface elements instead of gml:Polygon, set surface
     *     to true in the options to the contstructor (cannot be changed after
     *     instantiation).
     */
    surface: false,

    /**
     * Property: multiSurface
     * {Boolean} Write gml:multiSurface instead of gml:MultiPolygon.  Since
     *     the latter is deprecated in GML 3, the default is true.  To write
     *     gml:MultiPolygon instead of gml:multiSurface, set multiSurface to
     *     false in the options to the constructor (cannot be changed after
     *     instantiation).
     */
    multiSurface: true,

    /**
     * Constructor: OpenLayers.Format.GML.v3
     * Create a parser for GML v3.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     *
     * Valid options properties:
     * featureType - {String} Local (without prefix) feature typeName (required).
     * featureNS - {String} Feature namespace (required).
     * geometryName - {String} Geometry element name.
     */
    initialize: function(options) {
        OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
    },

    /**
     * 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: {
        "gml": OpenLayers.Util.applyDefaults({
            "_inherit": function(node, obj, container) {
                // SRSReferenceGroup attributes
                var dim = parseInt(node.getAttribute("srsDimension"), 10) ||
                    (container && container.srsDimension);
                if (dim) {
                    obj.srsDimension = dim;
                }
            },
            "featureMembers": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "Curve": function(node, container) {
                var obj = {points: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                if(!container.components) {
                    container.components = [];
                }
                container.components.push(
                    new OpenLayers.Geometry.LineString(obj.points)
                );
            },
            "segments": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "LineStringSegment": function(node, container) {
                var obj = {};
                this.readChildNodes(node, obj);
                if(obj.points) {
                    Array.prototype.push.apply(container.points, obj.points);
                }
            },
            "pos": function(node, obj) {
                var str = this.getChildValue(node).replace(
                    this.regExes.trimSpace, ""
                );
                var coords = str.split(this.regExes.splitSpace);
                var point;
                if(this.xy) {
                    point = new OpenLayers.Geometry.Point(
                        coords[0], coords[1], coords[2]
                    );
                } else {
                    point = new OpenLayers.Geometry.Point(
                        coords[1], coords[0], coords[2]
                    );
                }
                obj.points = [point];
            },
            "posList": function(node, obj) {
                var str = this.getChildValue(node).replace(
                    this.regExes.trimSpace, ""
                );
                var coords = str.split(this.regExes.splitSpace);
                // The "dimension" attribute is from the GML 3.0.1 spec.
                var dim = obj.srsDimension ||
                    parseInt(node.getAttribute("srsDimension") || node.getAttribute("dimension"), 10) || 2;
                var j, x, y, z;
                var numPoints = coords.length / dim;
                var points = new Array(numPoints);
                for(var i=0, len=coords.length; i<len; i += dim) {
                    x = coords[i];
                    y = coords[i+1];
                    z = (dim == 2) ? undefined : coords[i+2];
                    if (this.xy) {
                        points[i/dim] = new OpenLayers.Geometry.Point(x, y, z);
                    } else {
                        points[i/dim] = new OpenLayers.Geometry.Point(y, x, z);
                    }
                }
                obj.points = points;
            },
            "Surface": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "patches": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "PolygonPatch": function(node, obj) {
                this.readers.gml.Polygon.apply(this, [node, obj]);
            },
            "exterior": function(node, container) {
                var obj = {};
                this.readChildNodes(node, obj);
                container.outer = obj.components[0];
            },
            "interior": function(node, container) {
                var obj = {};
                this.readChildNodes(node, obj);
                container.inner.push(obj.components[0]);
            },
            "MultiCurve": function(node, container) {
                var obj = {components: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                if(obj.components.length > 0) {
                    container.components = [
                        new OpenLayers.Geometry.MultiLineString(obj.components)
                    ];
                }
            },
            "curveMember": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "MultiSurface": function(node, container) {
                var obj = {components: []};
                this.readers.gml._inherit.apply(this, [node, obj, container]);
                this.readChildNodes(node, obj);
                if(obj.components.length > 0) {
                    container.components = [
                        new OpenLayers.Geometry.MultiPolygon(obj.components)
                    ];
                }
            },
            "surfaceMember": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "surfaceMembers": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "pointMembers": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "lineStringMembers": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "polygonMembers": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "geometryMembers": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "Envelope": function(node, container) {
                var obj = {points: new Array(2)};
                this.readChildNodes(node, obj);
                if(!container.components) {
                    container.components = [];
                }
                var min = obj.points[0];
                var max = obj.points[1];
                container.components.push(
                    new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
                );
            },
            "lowerCorner": function(node, container) {
                var obj = {};
                this.readers.gml.pos.apply(this, [node, obj]);
                container.points[0] = obj.points[0];
            },
            "upperCorner": function(node, container) {
                var obj = {};
                this.readers.gml.pos.apply(this, [node, obj]);
                container.points[1] = obj.points[0];
            }
        }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),            
        "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
        "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
    },
    
    /**
     * Method: write
     *
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
     *     An array of features or a single feature.
     *
     * Returns:
     * {String} Given an array of features, a doc with a gml:featureMembers
     *     element will be returned.  Given a single feature, a doc with a
     *     gml:featureMember element will be returned.
     */
    write: function(features) {
        var name;
        if(OpenLayers.Util.isArray(features)) {
            name = "featureMembers";
        } else {
            name = "featureMember";
        }
        var root = this.writeNode("gml:" + name, features);
        this.setAttributeNS(
            root, this.namespaces["xsi"],
            "xsi:schemaLocation", this.schemaLocation
        );

        return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
    },

    /**
     * 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: {
        "gml": OpenLayers.Util.applyDefaults({
            "featureMembers": function(features) {
                var node = this.createElementNSPlus("gml:featureMembers");
                for(var i=0, len=features.length; i<len; ++i) {
                    this.writeNode("feature:_typeName", features[i], node);
                }
                return node;
            },
            "Point": function(geometry) {
                var node = this.createElementNSPlus("gml:Point");
                this.writeNode("pos", geometry, node);
                return node;
            },
            "pos": function(point) {
                // only 2d for simple features profile
                var pos = (this.xy) ?
                    (point.x + " " + point.y) : (point.y + " " + point.x);
                return this.createElementNSPlus("gml:pos", {
                    value: pos
                });
            },
            "LineString": function(geometry) {
                var node = this.createElementNSPlus("gml:LineString");
                this.writeNode("posList", geometry.components, node);
                return node;
            },
            "Curve": function(geometry) {
                var node = this.createElementNSPlus("gml:Curve");
                this.writeNode("segments", geometry, node);
                return node;
            },
            "segments": function(geometry) {
                var node = this.createElementNSPlus("gml:segments");
                this.writeNode("LineStringSegment", geometry, node);
                return node;
            },
            "LineStringSegment": function(geometry) {
                var node = this.createElementNSPlus("gml:LineStringSegment");
                this.writeNode("posList", geometry.components, node);
                return node;
            },
            "posList": function(points) {
                // only 2d for simple features profile
                var len = points.length;
                var parts = new Array(len);
                var point;
                for(var i=0; i<len; ++i) {
                    point = points[i];
                    if(this.xy) {
                        parts[i] = point.x + " " + point.y;
                    } else {
                        parts[i] = point.y + " " + point.x;
                    }
                }
                return this.createElementNSPlus("gml:posList", {
                    value: parts.join(" ")
                }); 
            },
            "Surface": function(geometry) {
                var node = this.createElementNSPlus("gml:Surface");
                this.writeNode("patches", geometry, node);
                return node;
            },
            "patches": function(geometry) {
                var node = this.createElementNSPlus("gml:patches");
                this.writeNode("PolygonPatch", geometry, node);
                return node;
            },
            "PolygonPatch": function(geometry) {
                var node = this.createElementNSPlus("gml:PolygonPatch", {
                    attributes: {interpolation: "planar"}
                });
                this.writeNode("exterior", geometry.components[0], node);
                for(var i=1, len=geometry.components.length; i<len; ++i) {
                    this.writeNode(
                        "interior", geometry.components[i], node
                    );
                }
                return node;
            },
            "Polygon": function(geometry) {
                var node = this.createElementNSPlus("gml:Polygon");
                this.writeNode("exterior", geometry.components[0], node);
                for(var i=1, len=geometry.components.length; i<len; ++i) {
                    this.writeNode(
                        "interior", geometry.components[i], node
                    );
                }
                return node;
            },
            "exterior": function(ring) {
                var node = this.createElementNSPlus("gml:exterior");
                this.writeNode("LinearRing", ring, node);
                return node;
            },
            "interior": function(ring) {
                var node = this.createElementNSPlus("gml:interior");
                this.writeNode("LinearRing", ring, node);
                return node;
            },
            "LinearRing": function(ring) {
                var node = this.createElementNSPlus("gml:LinearRing");
                this.writeNode("posList", ring.components, node);
                return node;
            },
            "MultiCurve": function(geometry) {
                var node = this.createElementNSPlus("gml:MultiCurve");
                var components = geometry.components || [geometry];
                for(var i=0, len=components.length; i<len; ++i) {
                    this.writeNode("curveMember", components[i], node);
                }
                return node;
            },
            "curveMember": function(geometry) {
                var node = this.createElementNSPlus("gml:curveMember");
                if(this.curve) {
                    this.writeNode("Curve", geometry, node);
                } else {
                    this.writeNode("LineString", geometry, node);
                }
                return node;
            },
            "MultiSurface": function(geometry) {
                var node = this.createElementNSPlus("gml:MultiSurface");
                var components = geometry.components || [geometry];
                for(var i=0, len=components.length; i<len; ++i) {
                    this.writeNode("surfaceMember", components[i], node);
                }
                return node;
            },
            "surfaceMember": function(polygon) {
                var node = this.createElementNSPlus("gml:surfaceMember");
                if(this.surface) {
                    this.writeNode("Surface", polygon, node);
                } else {
                    this.writeNode("Polygon", polygon, node);
                }
                return node;
            },
            "Envelope": function(bounds) {
                var node = this.createElementNSPlus("gml:Envelope");
                this.writeNode("lowerCorner", bounds, node);
                this.writeNode("upperCorner", bounds, node);
                // srsName attribute is required for gml:Envelope
                if(this.srsName) {
                    node.setAttribute("srsName", this.srsName);
                }
                return node;
            },
            "lowerCorner": function(bounds) {
                // only 2d for simple features profile
                var pos = (this.xy) ?
                    (bounds.left + " " + bounds.bottom) :
                    (bounds.bottom + " " + bounds.left);
                return this.createElementNSPlus("gml:lowerCorner", {
                    value: pos
                });
            },
            "upperCorner": function(bounds) {
                // only 2d for simple features profile
                var pos = (this.xy) ?
                    (bounds.right + " " + bounds.top) :
                    (bounds.top + " " + bounds.right);
                return this.createElementNSPlus("gml:upperCorner", {
                    value: pos
                });
            }
        }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
        "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
        "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
    },

    /**
     * Method: setGeometryTypes
     * Sets the <geometryTypes> mapping.
     */
    setGeometryTypes: function() {
        this.geometryTypes = {
            "OpenLayers.Geometry.Point": "Point",
            "OpenLayers.Geometry.MultiPoint": "MultiPoint",
            "OpenLayers.Geometry.LineString": (this.curve === true) ? "Curve": "LineString",
            "OpenLayers.Geometry.MultiLineString": (this.multiCurve === false) ? "MultiLineString" : "MultiCurve",
            "OpenLayers.Geometry.Polygon": (this.surface === true) ? "Surface" : "Polygon",
            "OpenLayers.Geometry.MultiPolygon": (this.multiSurface === false) ? "MultiPolygon" : "MultiSurface",
            "OpenLayers.Geometry.Collection": "GeometryCollection"
        };
    },
    
    CLASS_NAME: "OpenLayers.Format.GML.v3" 

});
/* ======================================================================
    OpenLayers/Format/Filter/v1_1_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/Filter/v1.js
 * @requires OpenLayers/Format/GML/v3.js
 */

/**
 * Class: OpenLayers.Format.Filter.v1_1_0
 * Write ogc:Filter version 1.1.0.
 *
 * Differences from the v1.0.0 parser:
 *  - uses GML v3 instead of GML v2
 *  - reads matchCase attribute on ogc:PropertyIsEqual and
 *        ogc:PropertyIsNotEqual elements.
 *  - writes matchCase attribute from comparison filters of type EQUAL_TO,
 *        NOT_EQUAL_TO and LIKE.
 * 
 * Inherits from: 
 *  - <OpenLayers.Format.GML.v3>
 *  - <OpenLayers.Format.Filter.v1>
 */
OpenLayers.Format.Filter.v1_1_0 = OpenLayers.Class(
    OpenLayers.Format.GML.v3, OpenLayers.Format.Filter.v1, {
    
    /**
     * Constant: VERSION
     * {String} 1.1.0
     */
    VERSION: "1.1.0",
    
    /**
     * Property: schemaLocation
     * {String} http://www.opengis.net/ogc/filter/1.1.0/filter.xsd
     */
    schemaLocation: "http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",

    /**
     * Constructor: OpenLayers.Format.Filter.v1_1_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.GML.v3.prototype.initialize.apply(
            this, [options]
        );
    },

    /**
     * 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": OpenLayers.Util.applyDefaults({
            "PropertyIsEqualTo": function(node, obj) {
                var matchCase = node.getAttribute("matchCase");
                var filter = new OpenLayers.Filter.Comparison({
                    type: OpenLayers.Filter.Comparison.EQUAL_TO,
                    matchCase: !(matchCase === "false" || matchCase === "0")
                });
                this.readChildNodes(node, filter);
                obj.filters.push(filter);
            },
            "PropertyIsNotEqualTo": function(node, obj) {
                var matchCase = node.getAttribute("matchCase");
                var filter = new OpenLayers.Filter.Comparison({
                    type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
                    matchCase: !(matchCase === "false" || matchCase === "0")
                });
                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("escapeChar");
                filter.value2regex(wildCard, singleChar, esc);
                obj.filters.push(filter);
            }
        }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
        "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
        "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"]        
    },

    /**
     * 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": OpenLayers.Util.applyDefaults({
            "PropertyIsEqualTo": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsEqualTo", {
                    attributes: {matchCase: filter.matchCase}
                });
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsNotEqualTo": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo", {
                    attributes: {matchCase: filter.matchCase}
                });
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsLike": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsLike", {
                    attributes: {
                        matchCase: filter.matchCase,
                        wildCard: "*", singleChar: ".", escapeChar: "!"
                    }
                });
                // no ogc:expression handling for now
                this.writeNode("PropertyName", filter, node);
                // convert regex string to ogc string
                this.writeNode("Literal", filter.regex2value(), node);
                return node;
            },
            "BBOX": function(filter) {
                var node = this.createElementNSPlus("ogc:BBOX");
                // PropertyName is optional in 1.1.0
                filter.property && this.writeNode("PropertyName", filter, node);
                var box = this.writeNode("gml:Envelope", filter.value);
                if(filter.projection) {
                    box.setAttribute("srsName", filter.projection);
                }
                node.appendChild(box); 
                return node;
            },
            "SortBy": function(sortProperties) {
                var node = this.createElementNSPlus("ogc:SortBy");
                for (var i=0,l=sortProperties.length;i<l;i++) {
                    this.writeNode(
                        "ogc:SortProperty",
                        sortProperties[i],
                        node
                    );
                }
                return node;
            }, 
            "SortProperty": function(sortProperty) {
                var node = this.createElementNSPlus("ogc:SortProperty");
                this.writeNode(
                    "ogc:PropertyName",
                    sortProperty,
                    node
                );
                this.writeNode(
                    "ogc:SortOrder",
                    (sortProperty.order == 'DESC') ? 'DESC' : 'ASC',
                    node
                );
                return node;
            },
            "SortOrder": function(value) {
                var node = this.createElementNSPlus("ogc:SortOrder", {
                    value: value
                });
                return node;
            }
        }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
        "gml": OpenLayers.Format.GML.v3.prototype.writers["gml"],
        "feature": OpenLayers.Format.GML.v3.prototype.writers["feature"]
    },

    /**
     * Method: writeSpatial
     *
     * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
     *
     * Parameters:
     * filter - {<OpenLayers.Filter.Spatial>} The filter.
     * name - {String} Name of the generated XML element.
     *
     * Returns:
     * {DOMElement} The created XML element.
     */
    writeSpatial: function(filter, name) {
        var node = this.createElementNSPlus("ogc:"+name);
        this.writeNode("PropertyName", filter, node);
        if(filter.value instanceof OpenLayers.Filter.Function) {
            this.writeNode("Function", filter.value, node);
        } else {
        var child;
        if(filter.value instanceof OpenLayers.Geometry) {
            child = this.writeNode("feature:_geometry", filter.value).firstChild;
        } else {
            child = this.writeNode("gml:Envelope", filter.value);
        }
        if(filter.projection) {
            child.setAttribute("srsName", filter.projection);
        }
        node.appendChild(child);
        }
        return node;
    },

    CLASS_NAME: "OpenLayers.Format.Filter.v1_1_0" 

});
/* ======================================================================
    OpenLayers/Format/OWSCommon.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML/VersionedOGC.js
 */

/**
 * Class: OpenLayers.Format.OWSCommon
 * Read OWSCommon. Create a new instance with the <OpenLayers.Format.OWSCommon>
 *     constructor.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.XML.VersionedOGC>
 */
OpenLayers.Format.OWSCommon = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
    
    /**
     * APIProperty: defaultVersion
     * {String} Version number to assume if none found.  Default is "1.0.0".
     */
    defaultVersion: "1.0.0",
    
    /**
     * Constructor: OpenLayers.Format.OWSCommon
     * Create a new parser for OWSCommon.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */

    /**
     * Method: getVersion
     * Returns the version to use. Subclasses can override this function
     * if a different version detection is needed.
     *
     * Parameters:
     * root - {DOMElement}
     * options - {Object} Optional configuration object.
     *
     * Returns:
     * {String} The version to use.
     */
    getVersion: function(root, options) {
        var version = this.version;
        if(!version) {
            // remember version does not correspond to the OWS version
            // it corresponds to the WMS/WFS/WCS etc. request version
            var uri = root.getAttribute("xmlns:ows");
            // the above will fail if the namespace prefix is different than
            // ows and if the namespace is declared on a different element
            if (uri && uri.substring(uri.lastIndexOf("/")+1) === "1.1") {
                version ="1.1.0";
            } 
            if(!version) {
                version = this.defaultVersion;
            }
        }
        return version;
    },

    /**
     * APIMethod: read
     * Read an OWSCommon document and return an object.
     *
     * Parameters:
     * data - {String | DOMElement} Data to read.
     * options - {Object} Options for the reader.
     *
     * Returns:
     * {Object} An object representing the structure of the document.
     */

    CLASS_NAME: "OpenLayers.Format.OWSCommon" 
});
/* ======================================================================
    OpenLayers/Format/OWSCommon/v1.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/OWSCommon.js
 */

/**
 * Class: OpenLayers.Format.OWSCommon.v1
 * Common readers and writers for OWSCommon v1.X formats
 *
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
OpenLayers.Format.OWSCommon.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
   
    /**
     * Property: regExes
     * Compiled regular expressions for manipulating strings.
     */
    regExes: {
        trimSpace: (/^\s*|\s*$/g),
        removeSpace: (/\s*/g),
        splitSpace: (/\s+/),
        trimComma: (/\s*,\s*/g)
    },

    /**
     * Method: read
     *
     * Parameters:
     * data - {DOMElement} An OWSCommon document element.
     * options - {Object} Options for the reader.
     *
     * Returns:
     * {Object} An object representing the OWSCommon document.
     */
    read: function(data, options) {
        options = OpenLayers.Util.applyDefaults(options, this.options);
        var ows = {};
        this.readChildNodes(data, ows);
        return ows;
    },

    /**
     * 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: {
        "ows": {
            "Exception": function(node, exceptionReport) {
                var exception = {
                    code: node.getAttribute('exceptionCode'),
                    locator: node.getAttribute('locator'),
                    texts: []
                };
                exceptionReport.exceptions.push(exception);
                this.readChildNodes(node, exception);
            },
            "ExceptionText": function(node, exception) {
                var text = this.getChildValue(node);
                exception.texts.push(text);
            },
            "ServiceIdentification": function(node, obj) {
                obj.serviceIdentification = {};
                this.readChildNodes(node, obj.serviceIdentification);
            },
            "Title": function(node, obj) {
                obj.title = this.getChildValue(node);
            },
            "Abstract": function(node, serviceIdentification) {
                serviceIdentification["abstract"] = this.getChildValue(node);
            },
            "Keywords": function(node, serviceIdentification) {
                serviceIdentification.keywords = {};
                this.readChildNodes(node, serviceIdentification.keywords);
            },
            "Keyword": function(node, keywords) {
                keywords[this.getChildValue(node)] = true;
            },
            "ServiceType": function(node, serviceIdentification) {
                serviceIdentification.serviceType = {
                    codeSpace: node.getAttribute('codeSpace'), 
                    value: this.getChildValue(node)};
            },
            "ServiceTypeVersion": function(node, serviceIdentification) {
                serviceIdentification.serviceTypeVersion = this.getChildValue(node);
            },
            "Fees": function(node, serviceIdentification) {
                serviceIdentification.fees = this.getChildValue(node);
            },
            "AccessConstraints": function(node, serviceIdentification) {
                serviceIdentification.accessConstraints = 
                    this.getChildValue(node);
            },
            "ServiceProvider": function(node, obj) {
                obj.serviceProvider = {};
                this.readChildNodes(node, obj.serviceProvider);
            },
            "ProviderName": function(node, serviceProvider) {
                serviceProvider.providerName = this.getChildValue(node);
            },
            "ProviderSite": function(node, serviceProvider) {
                serviceProvider.providerSite = this.getAttributeNS(node, 
                    this.namespaces.xlink, "href");
            },
            "ServiceContact": function(node, serviceProvider) {
                serviceProvider.serviceContact = {};
                this.readChildNodes(node, serviceProvider.serviceContact);
            },
            "IndividualName": function(node, serviceContact) {
                serviceContact.individualName = this.getChildValue(node);
            },
            "PositionName": function(node, serviceContact) {
                serviceContact.positionName = this.getChildValue(node);
            },
            "ContactInfo": function(node, serviceContact) {
                serviceContact.contactInfo = {};
                this.readChildNodes(node, serviceContact.contactInfo);
            },
            "Phone": function(node, contactInfo) {
                contactInfo.phone = {};
                this.readChildNodes(node, contactInfo.phone);
            },
            "Voice": function(node, phone) {
                phone.voice = this.getChildValue(node);
            },
            "Address": function(node, contactInfo) {
                contactInfo.address = {};
                this.readChildNodes(node, contactInfo.address);
            },
            "DeliveryPoint": function(node, address) {
                address.deliveryPoint = this.getChildValue(node);
            },
            "City": function(node, address) {
                address.city = this.getChildValue(node);
            },
            "AdministrativeArea": function(node, address) {
                address.administrativeArea = this.getChildValue(node);
            },
            "PostalCode": function(node, address) {
                address.postalCode = this.getChildValue(node);
            },
            "Country": function(node, address) {
                address.country = this.getChildValue(node);
            },
            "ElectronicMailAddress": function(node, address) {
                address.electronicMailAddress = this.getChildValue(node);
            },
            "Role": function(node, serviceContact) {
                serviceContact.role = this.getChildValue(node);
            },
            "OperationsMetadata": function(node, obj) {
                obj.operationsMetadata = {};
                this.readChildNodes(node, obj.operationsMetadata);
            },
            "Operation": function(node, operationsMetadata) {
                var name = node.getAttribute("name");
                operationsMetadata[name] = {};
                this.readChildNodes(node, operationsMetadata[name]);
            },
            "DCP": function(node, operation) {
                operation.dcp = {};
                this.readChildNodes(node, operation.dcp);
            },
            "HTTP": function(node, dcp) {
                dcp.http = {};
                this.readChildNodes(node, dcp.http);
            },
            "Get": function(node, http) {
                if (!http.get) {
                    http.get = [];
                }
                var obj = {
                    url: this.getAttributeNS(node, this.namespaces.xlink, "href")
                };
                this.readChildNodes(node, obj);
                http.get.push(obj);
            },
            "Post": function(node, http) {
                if (!http.post) {
                    http.post = [];
                }
                var obj = {
                    url: this.getAttributeNS(node, this.namespaces.xlink, "href")
                };
                this.readChildNodes(node, obj);
                http.post.push(obj);
            },
            "Parameter": function(node, operation) {
                if (!operation.parameters) {
                    operation.parameters = {};
                }
                var name = node.getAttribute("name");
                operation.parameters[name] = {};
                this.readChildNodes(node, operation.parameters[name]);
            },
            "Constraint": function(node, obj) {
                if (!obj.constraints) {
                    obj.constraints = {};
                }
                var name = node.getAttribute("name");
                obj.constraints[name] = {};
                this.readChildNodes(node, obj.constraints[name]);
            },
            "Value": function(node, allowedValues) {
                allowedValues[this.getChildValue(node)] = true;
            },
            "OutputFormat": function(node, obj) {
                obj.formats.push({value: this.getChildValue(node)});
                this.readChildNodes(node, obj);
            },
            "WGS84BoundingBox": function(node, obj) {
                var boundingBox = {};
                boundingBox.crs = node.getAttribute("crs");
                if (obj.BoundingBox) {
                    obj.BoundingBox.push(boundingBox);
                } else {
                    obj.projection = boundingBox.crs;
                    boundingBox = obj;
               }
               this.readChildNodes(node, boundingBox);
            },
            "BoundingBox": function(node, obj) {
                // FIXME: We consider that BoundingBox is the same as WGS84BoundingBox
                // LowerCorner = "min_x min_y"
                // UpperCorner = "max_x max_y"
                // It should normally depend on the projection
                this.readers['ows']['WGS84BoundingBox'].apply(this, [node, obj]);
            },
            "LowerCorner": function(node, obj) {
                var str = this.getChildValue(node).replace(
                    this.regExes.trimSpace, "");
                str = str.replace(this.regExes.trimComma, ",");
                var pointList = str.split(this.regExes.splitSpace);
                obj.left = pointList[0];
                obj.bottom = pointList[1];
            },
            "UpperCorner": function(node, obj) {
                var str = this.getChildValue(node).replace(
                    this.regExes.trimSpace, "");
                str = str.replace(this.regExes.trimComma, ",");
                var pointList = str.split(this.regExes.splitSpace);
                obj.right = pointList[0];
                obj.top = pointList[1];
                obj.bounds = new OpenLayers.Bounds(obj.left, obj.bottom,
                    obj.right, obj.top);
                delete obj.left;
                delete obj.bottom;
                delete obj.right;
                delete obj.top;
            },
            "Language": function(node, obj) {
                obj.language = this.getChildValue(node);
            }
        }
    },

    /**
     * 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: {
        "ows": {
            "BoundingBox": function(options, nodeName) {
                var node = this.createElementNSPlus(nodeName || "ows:BoundingBox", {
                    attributes: {
                        crs: options.projection
                    }
                });
                this.writeNode("ows:LowerCorner", options, node);
                this.writeNode("ows:UpperCorner", options, node);
                return node;
            },
            "LowerCorner": function(options) {
                var node = this.createElementNSPlus("ows:LowerCorner", {
                    value: options.bounds.left + " " + options.bounds.bottom });
                return node;
            },
            "UpperCorner": function(options) {
                var node = this.createElementNSPlus("ows:UpperCorner", {
                    value: options.bounds.right + " " + options.bounds.top });
                return node;
            },
            "Identifier": function(identifier) {
                var node = this.createElementNSPlus("ows:Identifier", {
                    value: identifier });
                return node;
            },
            "Title": function(title) {
                var node = this.createElementNSPlus("ows:Title", {
                    value: title });
                return node;
            },
            "Abstract": function(abstractValue) {
                var node = this.createElementNSPlus("ows:Abstract", {
                    value: abstractValue });
                return node;
            },
            "OutputFormat": function(format) {
                var node = this.createElementNSPlus("ows:OutputFormat", {
                    value: format });
                return node;
            }
        }
    },

    CLASS_NAME: "OpenLayers.Format.OWSCommon.v1"

});
/* ======================================================================
    OpenLayers/Format/OWSCommon/v1_0_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/OWSCommon/v1.js
 */

/**
 * Class: OpenLayers.Format.OWSCommon.v1_0_0
 * Parser for OWS Common version 1.0.0.
 *
 * Inherits from:
 *  - <OpenLayers.Format.OWSCommon.v1>
 */
OpenLayers.Format.OWSCommon.v1_0_0 = OpenLayers.Class(OpenLayers.Format.OWSCommon.v1, {
    
    /**
     * Property: namespaces
     * {Object} Mapping of namespace aliases to namespace URIs.
     */
    namespaces: {
        ows: "http://www.opengis.net/ows",
        xlink: "http://www.w3.org/1999/xlink"
    },    
    
    /**
     * 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: {
        "ows": OpenLayers.Util.applyDefaults({
            "ExceptionReport": function(node, obj) {
                obj.success = false;
                obj.exceptionReport = {
                    version: node.getAttribute('version'),
                    language: node.getAttribute('language'),
                    exceptions: []
                };
                this.readChildNodes(node, obj.exceptionReport);
            } 
        }, OpenLayers.Format.OWSCommon.v1.prototype.readers.ows)
    },

    /**
     * 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: {
        "ows": OpenLayers.Format.OWSCommon.v1.prototype.writers.ows
    },
    
    CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_0_0"

});
/* ======================================================================
    OpenLayers/Format/WFST/v1_1_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/WFST/v1.js
 * @requires OpenLayers/Format/Filter/v1_1_0.js
 * @requires OpenLayers/Format/OWSCommon/v1_0_0.js
 */

/**
 * Class: OpenLayers.Format.WFST.v1_1_0
 * A format for creating WFS v1.1.0 transactions.  Create a new instance with the
 *     <OpenLayers.Format.WFST.v1_1_0> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Format.Filter.v1_1_0>
 *  - <OpenLayers.Format.WFST.v1>
 */
OpenLayers.Format.WFST.v1_1_0 = OpenLayers.Class(
    OpenLayers.Format.Filter.v1_1_0, OpenLayers.Format.WFST.v1, {
    
    /**
     * Property: version
     * {String} WFS version number.
     */
    version: "1.1.0",
    
    /**
     * Property: schemaLocations
     * {Object} Properties are namespace aliases, values are schema locations.
     */
    schemaLocations: {
        "wfs": "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"
    },
    
    /**
     * Constructor: OpenLayers.Format.WFST.v1_1_0
     * A class for parsing and generating WFS v1.1.0 transactions.
     *
     * To read additional information like hit count (numberOfFeatures) from
     * the  FeatureCollection, call the <OpenLayers.Format.WFST.v1.read> method
     * with {output: "object"} as 2nd argument. Note that it is possible to
     * just request the hit count from a WFS 1.1.0 server with the
     * resultType="hits" request parameter.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     *
     * Valid options properties:
     * featureType - {String} Local (without prefix) feature typeName (required).
     * featureNS - {String} Feature namespace (optional).
     * featurePrefix - {String} Feature namespace alias (optional - only used
     *     if featureNS is provided).  Default is 'feature'.
     * geometryName - {String} Name of geometry attribute.  Default is 'the_geom'.
     */
    initialize: function(options) {
        OpenLayers.Format.Filter.v1_1_0.prototype.initialize.apply(this, [options]);
        OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
    },
    
    /**
     * Method: readNode
     * Shorthand for applying one of the named readers given the node
     *     namespace and local name.  Readers take two args (node, obj) and
     *     generally extend or modify the second.
     *
     * Parameters:
     * node - {DOMElement} The node to be read (required).
     * obj - {Object} The object to be modified (optional).
     * first - {Boolean} Should be set to true for the first node read. This
     *     is usually the readNode call in the read method. Without this being
     *     set, auto-configured properties will stick on subsequent reads.
     *
     * Returns:
     * {Object} The input object, modified (or a new one if none was provided).
     */
    readNode: function(node, obj, first) {
        // Not the superclass, only the mixin classes inherit from
        // Format.GML.v3. We need this because we don't want to get readNode
        // from the superclass's superclass, which is OpenLayers.Format.XML.
        return OpenLayers.Format.GML.v3.prototype.readNode.apply(this, arguments);
    },
    
    /**
     * 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: {
        "wfs": OpenLayers.Util.applyDefaults({
            "FeatureCollection": function(node, obj) {
                obj.numberOfFeatures = parseInt(node.getAttribute(
                    "numberOfFeatures"));
                OpenLayers.Format.WFST.v1.prototype.readers["wfs"]["FeatureCollection"].apply(
                    this, arguments);
            },
            "TransactionResponse": function(node, obj) {
                obj.insertIds = [];
                obj.success = false;
                this.readChildNodes(node, obj);
            },
            "TransactionSummary": function(node, obj) {
                // this is a limited test of success
                obj.success = true;
            },
            "InsertResults": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "Feature": function(node, container) {
                var obj = {fids: []};
                this.readChildNodes(node, obj);
                container.insertIds.push(obj.fids[0]);
            }
        }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
        "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
        "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"],
        "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.readers["ogc"],
        "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"]
    },

    /**
     * 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: {
        "wfs": OpenLayers.Util.applyDefaults({
            "GetFeature": function(options) {
                var node = OpenLayers.Format.WFST.v1.prototype.writers["wfs"]["GetFeature"].apply(this, arguments);
                options && this.setAttributes(node, {
                    resultType: options.resultType,
                    startIndex: options.startIndex,
                    count: options.count
                });
                return node;
            },
            "Query": function(options) {
                options = OpenLayers.Util.extend({
                    featureNS: this.featureNS,
                    featurePrefix: this.featurePrefix,
                    featureType: this.featureType,
                    srsName: this.srsName
                }, options);
                var prefix = options.featurePrefix;
                var node = this.createElementNSPlus("wfs:Query", {
                    attributes: {
                        typeName: (prefix ? prefix + ":" : "") +
                            options.featureType,
                        srsName: options.srsName
                    }
                });
                if(options.featureNS) {
                    node.setAttribute("xmlns:" + prefix, options.featureNS);
                }
                if(options.propertyNames) {
                    for(var i=0,len = options.propertyNames.length; i<len; i++) {
                        this.writeNode(
                            "wfs:PropertyName", 
                            {property: options.propertyNames[i]},
                            node
                        );
                    }
                }
                if(options.filter) {
                    OpenLayers.Format.WFST.v1_1_0.prototype.setFilterProperty.call(this, options.filter);
                    this.writeNode("ogc:Filter", options.filter, node);
                }
                return node;
            },
            "PropertyName": function(obj) {
                return this.createElementNSPlus("wfs:PropertyName", {
                    value: obj.property
                });
            }            
        }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
        "gml": OpenLayers.Format.GML.v3.prototype.writers["gml"],
        "feature": OpenLayers.Format.GML.v3.prototype.writers["feature"],
        "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.writers["ogc"]
    },

    CLASS_NAME: "OpenLayers.Format.WFST.v1_1_0" 
});
/* ======================================================================
    OpenLayers/Strategy.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.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>} The layer this strategy belongs to.
     */
    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
     * Called to set the <layer> property.
     *
     * Parameters:
     * layer - {<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/Strategy/Filter.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Strategy.js
 * @requires OpenLayers/Filter.js
 */

/**
 * Class: OpenLayers.Strategy.Filter
 * Strategy for limiting features that get added to a layer by 
 *     evaluating a filter.  The strategy maintains a cache of
 *     all features until removeFeatures is called on the layer.
 *
 * Inherits from:
 *  - <OpenLayers.Strategy>
 */
OpenLayers.Strategy.Filter = OpenLayers.Class(OpenLayers.Strategy, {
    
    /**
     * APIProperty: filter
     * {<OpenLayers.Filter>}  Filter for limiting features sent to the layer.
     *     Use the <setFilter> method to update this filter after construction.
     */
    filter: null,
    
    /**
     * Property: cache
     * {Array(<OpenLayers.Feature.Vector>)} List of currently cached
     *     features.
     */
    cache: null,
    
    /**
     * Property: caching
     * {Boolean} The filter is currently caching features.
     */
    caching: false,
    
    /**
     * Constructor: OpenLayers.Strategy.Filter
     * Create a new filter strategy.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     */

    /**
     * APIMethod: activate
     * Activate the strategy.  Register any listeners, do appropriate setup.
     *     By default, this strategy automatically activates itself when a layer
     *     is added to a map.
     *
     * Returns:
     * {Boolean} True if the strategy was successfully activated or false if
     *      the strategy was already active.
     */
    activate: function() {
        var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
        if (activated) {
            this.cache = [];
            this.layer.events.on({
                "beforefeaturesadded": this.handleAdd,
                "beforefeaturesremoved": this.handleRemove,
                scope: this
            });
        }
        return activated;
    },
    
    /**
     * APIMethod: deactivate
     * Deactivate the strategy.  Clear the feature cache.
     *
     * Returns:
     * {Boolean} True if the strategy was successfully deactivated or false if
     *      the strategy was already inactive.
     */
    deactivate: function() {
        this.cache = null;
        if (this.layer && this.layer.events) {
            this.layer.events.un({
                "beforefeaturesadded": this.handleAdd,
                "beforefeaturesremoved": this.handleRemove,
                scope: this
            });            
        }
        return OpenLayers.Strategy.prototype.deactivate.apply(this, arguments);
    },
    
    /**
     * Method: handleAdd
     */
    handleAdd: function(event) {
        if (!this.caching && this.filter) {
            var features = event.features;
            event.features = [];
            var feature;
            for (var i=0, ii=features.length; i<ii; ++i) {
                feature = features[i];
                if (this.filter.evaluate(feature)) {
                    event.features.push(feature);
                } else {
                    this.cache.push(feature);
                }
            }
        }
    },
    
    /**
     * Method: handleRemove
     */
    handleRemove: function(event) {
        if (!this.caching) {
            this.cache = [];
        }
    },

    /** 
     * APIMethod: setFilter
     * Update the filter for this strategy.  This will re-evaluate
     *     any features on the layer and in the cache.  Only features
     *     for which filter.evalute(feature) returns true will be
     *     added to the layer.  Others will be cached by the strategy.
     *
     * Parameters:
     * filter - {<OpenLayers.Filter>} A filter for evaluating features.
     */
    setFilter: function(filter) {
        this.filter = filter;
        var previousCache = this.cache;
        this.cache = [];
        // look through layer for features to remove from layer
        this.handleAdd({features: this.layer.features});
        // cache now contains features to remove from layer
        if (this.cache.length > 0) {
            this.caching = true;
            this.layer.removeFeatures(this.cache.slice());
            this.caching = false;
        }
        // now look through previous cache for features to add to layer
        if (previousCache.length > 0) {
            var event = {features: previousCache};
            this.handleAdd(event);
            if (event.features.length > 0) {
                // event has features to add to layer
                this.caching = true;
                this.layer.addFeatures(event.features);
                this.caching = false;
            }
        }
    },

    CLASS_NAME: "OpenLayers.Strategy.Filter"

});
/* ======================================================================
    OpenLayers/Strategy/Save.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Strategy.js
 */

/**
 * Class: OpenLayers.Strategy.Save
 * A strategy that commits newly created or modified features.  By default
 *     the strategy waits for a call to <save> before persisting changes.  By
 *     configuring the strategy with the <auto> option, changes can be saved
 *     automatically.
 *
 * Inherits from:
 *  - <OpenLayers.Strategy>
 */
OpenLayers.Strategy.Save = OpenLayers.Class(OpenLayers.Strategy, {
    
    /**
     * APIProperty: events
     * {<OpenLayers.Events>} An events object that handles all 
     *     events on the strategy object.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * strategy.events.register(type, obj, listener);
     * (end)
     *
     * Supported event types:
     * start - Triggered before saving
     * success - Triggered after a successful transaction
     * fail - Triggered after a failed transaction
     * 
     */
 
    /** 
     * Property: events
     * {<OpenLayers.Events>} Events instance for triggering this protocol
     *    events.
     */
    events: null,
    
    /**
     * APIProperty: auto
     * {Boolean | Number} Auto-save.  Default is false.  If true, features will be
     *     saved immediately after being added to the layer and with each
     *     modification or deletion.  If auto is a number, features will be
     *     saved on an interval provided by the value (in seconds).
     */
    auto: false,
    
    /**
     * Property: timer
     * {Number} The id of the timer.
     */
    timer: null,

    /**
     * Constructor: OpenLayers.Strategy.Save
     * Create a new Save strategy.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     */
    initialize: function(options) {
        OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
        this.events = new OpenLayers.Events(this);
    },
   
    /**
     * APIMethod: activate
     * Activate the strategy.  Register any listeners, do appropriate setup.
     * 
     * Returns:
     * {Boolean} The strategy was successfully activated.
     */
    activate: function() {
        var activated = OpenLayers.Strategy.prototype.activate.call(this);
        if(activated) {
            if(this.auto) {
                if(typeof this.auto === "number") {
                    this.timer = window.setInterval(
                        OpenLayers.Function.bind(this.save, this),
                        this.auto * 1000
                    );
                } else {
                    this.layer.events.on({
                        "featureadded": this.triggerSave,
                        "afterfeaturemodified": this.triggerSave,
                        scope: this
                    });
                }
            }
        }
        return activated;
    },
    
    /**
     * APIMethod: deactivate
     * Deactivate the strategy.  Unregister any listeners, do appropriate
     *     tear-down.
     * 
     * Returns:
     * {Boolean} The strategy was successfully deactivated.
     */
    deactivate: function() {
        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
        if(deactivated) {
            if(this.auto) {
                if(typeof this.auto === "number") {
                    window.clearInterval(this.timer);
                } else {
                    this.layer.events.un({
                        "featureadded": this.triggerSave,
                        "afterfeaturemodified": this.triggerSave,
                        scope: this
                    });
                }
            }
        }
        return deactivated;
    },
    
    /**
     * Method: triggerSave
     * Registered as a listener.  Calls save if a feature has insert, update,
     *     or delete state.
     *
     * Parameters:
     * event - {Object} The event this function is listening for.
     */
    triggerSave: function(event) {
        var feature = event.feature;
        if(feature.state === OpenLayers.State.INSERT ||
           feature.state === OpenLayers.State.UPDATE ||
           feature.state === OpenLayers.State.DELETE) {
            this.save([event.feature]);
        }
    },
    
    /**
     * APIMethod: save
     * Tell the layer protocol to commit unsaved features.  If the layer
     *     projection differs from the map projection, features will be
     *     transformed into the layer projection before being committed.
     *
     * Parameters:
     * features - {Array} Features to be saved.  If null, then default is all
     *     features in the layer.  Features are assumed to be in the map
     *     projection.
     */
    save: function(features) {
        if(!features) {
            features = this.layer.features;
        }
        this.events.triggerEvent("start", {features:features});
        var remote = this.layer.projection;
        var local = this.layer.map.getProjectionObject();
        if(!local.equals(remote)) {
            var len = features.length;
            var clones = new Array(len);
            var orig, clone;
            for(var i=0; i<len; ++i) {
                orig = features[i];
                clone = orig.clone();
                clone.fid = orig.fid;
                clone.state = orig.state;
                if(orig.url) {
                    clone.url = orig.url;
                }
                clone._original = orig;
                clone.geometry.transform(local, remote);
                clones[i] = clone;
            }
            features = clones;
        }
        this.layer.protocol.commit(features, {
            callback: this.onCommit,
            scope: this
        });
    },
    
    /**
     * Method: onCommit
     * Called after protocol commit.
     *
     * Parameters:
     * response - {<OpenLayers.Protocol.Response>} A response object.
     */
    onCommit: function(response) {
        var evt = {"response": response};
        if(response.success()) {
            var features = response.reqFeatures;
            // deal with inserts, updates, and deletes
            var state, feature;
            var destroys = [];
            var insertIds = response.insertIds || [];
            var j = 0;
            for(var i=0, len=features.length; i<len; ++i) {
                feature = features[i];
                // if projection was different, we may be dealing with clones
                feature = feature._original || feature;
                state = feature.state;
                if(state) {
                    if(state == OpenLayers.State.DELETE) {
                        destroys.push(feature);
                    } else if(state == OpenLayers.State.INSERT) {
                        feature.fid = insertIds[j];
                        ++j;
                    }
                    feature.state = null;
                }
            }

            if(destroys.length > 0) {
                this.layer.destroyFeatures(destroys);
            }

            this.events.triggerEvent("success", evt);

        } else {
            this.events.triggerEvent("fail", evt);
        }
    },
   
    CLASS_NAME: "OpenLayers.Strategy.Save" 
});
/* ======================================================================
    OpenLayers/Layer/HTTPRequest.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Layer.js
 */

/**
 * Class: OpenLayers.Layer.HTTPRequest
 * 
 * Inherits from: 
 *  - <OpenLayers.Layer>
 */
OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, {

    /** 
     * Constant: URL_HASH_FACTOR
     * {Float} Used to hash URL param strings for multi-WMS server selection.
     *         Set to the Golden Ratio per Knuth's recommendation.
     */
    URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2,

    /** 
     * Property: url
     * {Array(String) or String} This is either an array of url strings or 
     *                           a single url string. 
     */
    url: null,

    /** 
     * Property: params
     * {Object} Hashtable of key/value parameters
     */
    params: null,
    
    /** 
     * APIProperty: reproject
     * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html
     * for information on the replacement for this functionality. 
     * {Boolean} Whether layer should reproject itself based on base layer 
     *           locations. This allows reprojection onto commercial layers. 
     *           Default is false: Most layers can't reproject, but layers 
     *           which can create non-square geographic pixels can, like WMS.
     *           
     */
    reproject: false,

    /**
     * Constructor: OpenLayers.Layer.HTTPRequest
     * 
     * Parameters:
     * name - {String}
     * url - {Array(String) or String}
     * params - {Object}
     * options - {Object} Hashtable of extra options to tag onto the layer
     */
    initialize: function(name, url, params, options) {
        OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
        this.url = url;
        if (!this.params) {
            this.params = OpenLayers.Util.extend({}, params);
        }
    },

    /**
     * APIMethod: destroy
     */
    destroy: function() {
        this.url = null;
        this.params = null;
        OpenLayers.Layer.prototype.destroy.apply(this, arguments); 
    },
    
    /**
     * APIMethod: clone
     * 
     * Parameters:
     * obj - {Object}
     * 
     * Returns:
     * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this 
     *                                  <OpenLayers.Layer.HTTPRequest>
     */
    clone: function (obj) {
        
        if (obj == null) {
            obj = new OpenLayers.Layer.HTTPRequest(this.name,
                                                   this.url,
                                                   this.params,
                                                   this.getOptions());
        }
        
        //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: setUrl
     * 
     * Parameters:
     * newUrl - {String}
     */
    setUrl: function(newUrl) {
        this.url = newUrl;
    },

    /**
     * APIMethod: mergeNewParams
     * 
     * Parameters:
     * newParams - {Object}
     *
     * Returns:
     * redrawn: {Boolean} whether the layer was actually redrawn.
     */
    mergeNewParams:function(newParams) {
        this.params = OpenLayers.Util.extend(this.params, newParams);
        var ret = this.redraw();
        if(this.map != null) {
            this.map.events.triggerEvent("changelayer", {
                layer: this,
                property: "params"
            });
        }
        return ret;
    },

    /**
     * APIMethod: redraw
     * Redraws the layer.  Returns true if the layer was redrawn, false if not.
     *
     * Parameters:
     * force - {Boolean} Force redraw by adding random parameter.
     *
     * Returns:
     * {Boolean} The layer was redrawn.
     */
    redraw: function(force) { 
        if (force) {
            return this.mergeNewParams({"_olSalt": Math.random()});
        } else {
            return OpenLayers.Layer.prototype.redraw.apply(this, []);
        }
    },
    
    /**
     * Method: selectUrl
     * selectUrl() implements the standard floating-point multiplicative
     *     hash function described by Knuth, and hashes the contents of the 
     *     given param string into a float between 0 and 1. This float is then
     *     scaled to the size of the provided urls array, and used to select
     *     a URL.
     *
     * Parameters:
     * paramString - {String}
     * urls - {Array(String)}
     * 
     * Returns:
     * {String} An entry from the urls array, deterministically selected based
     *          on the paramString.
     */
    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)];
    },

    /** 
     * Method: getFullRequestString
     * Combine url with layer's params and these newParams. 
     *   
     *    does checking on the serverPath variable, allowing for cases when it 
     *     is supplied with trailing ? or &, as well as cases where not. 
     *
     *    return in formatted string like this:
     *        "server?key1=value1&key2=value2&key3=value3"
     * 
     * WARNING: The altUrl parameter is deprecated and will be removed in 3.0.
     *
     * Parameters:
     * newParams - {Object}
     * altUrl - {String} Use this as the url instead of the layer's url
     *   
     * Returns: 
     * {String}
     */
    getFullRequestString:function(newParams, altUrl) {

        // if not altUrl passed in, use layer's url
        var url = altUrl || this.url;
        
        // create a new params hashtable with all the layer params and the 
        // new params together. then convert to string
        var allParams = OpenLayers.Util.extend({}, this.params);
        allParams = OpenLayers.Util.extend(allParams, newParams);
        var paramsString = OpenLayers.Util.getParameterString(allParams);
        
        // if url is not a string, it should be an array of strings, 
        // in which case we will deterministically select one of them in 
        // order to evenly distribute requests to different urls.
        //
        if (OpenLayers.Util.isArray(url)) {
            url = this.selectUrl(paramsString, url);
        }   
 
        // ignore parameters that are already in the url search string
        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);
        
        return OpenLayers.Util.urlAppend(url, paramsString);
    },

    CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
});
/* ======================================================================
    OpenLayers/Tile.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 */

/**
 * Class: OpenLayers.Tile 
 * This is a class designed to designate a single tile, however
 *     it is explicitly designed to do relatively little. Tiles store 
 *     information about themselves -- such as the URL that they are related
 *     to, and their size - but do not add themselves to the layer div 
 *     automatically, for example. Create a new tile with the 
 *     <OpenLayers.Tile> constructor, or a subclass. 
 * 
 * TBD 3.0 - remove reference to url in above paragraph
 * 
 */
OpenLayers.Tile = OpenLayers.Class({
    
    /**
     * APIProperty: events
     * {<OpenLayers.Events>} An events object that handles all 
     *     events on the tile.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * tile.events.register(type, obj, listener);
     * (end)
     *
     * Supported event types:
     * beforedraw - Triggered before the tile is drawn. Used to defer
     *     drawing to an animation queue. To defer drawing, listeners need
     *     to return false, which will abort drawing. The queue handler needs
     *     to call <draw>(true) to actually draw the tile.
     * loadstart - Triggered when tile loading starts.
     * loadend - Triggered when tile loading ends.
     * loaderror - Triggered before the loadend event (i.e. when the tile is
     *     still hidden) if the tile could not be loaded.
     * reload - Triggered when an already loading tile is reloaded.
     * unload - Triggered before a tile is unloaded.
     */
    events: null,

    /**
     * APIProperty: eventListeners
     * {Object} If set as an option at construction, the eventListeners
     *     object will be registered with <OpenLayers.Events.on>.  Object
     *     structure must be a listeners object as shown in the example for
     *     the events.on method.
     *
     * This options can be set in the ``tileOptions`` option from
     * <OpenLayers.Layer.Grid>. For example, to be notified of the
     * ``loadend`` event of each tiles:
     * (code)
     * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', {
     *     tileOptions: {
     *         eventListeners: {
     *             'loadend': function(evt) {
     *                 // do something on loadend
     *             }
     *         }
     *     }
     * });
     * (end)
     */
    eventListeners: null,

    /**
     * Property: id 
     * {String} null
     */
    id: null,
    
    /** 
     * Property: layer 
     * {<OpenLayers.Layer>} layer the tile is attached to 
     */
    layer: null,
    
    /**
     * Property: url
     * {String} url of the request.
     *
     * TBD 3.0 
     * Deprecated. The base tile class does not need an url. This should be 
     * handled in subclasses. Does not belong here.
     */
    url: null,

    /** 
     * APIProperty: bounds 
     * {<OpenLayers.Bounds>} null
     */
    bounds: null,
    
    /** 
     * Property: size 
     * {<OpenLayers.Size>} null
     */
    size: null,
    
    /** 
     * Property: position 
     * {<OpenLayers.Pixel>} Top Left pixel of the tile
     */    
    position: null,
    
    /**
     * Property: isLoading
     * {Boolean} Is the tile loading?
     */
    isLoading: false,
    
    /** 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.
     */

    /** 
     * Constructor: OpenLayers.Tile
     * Constructor for a new <OpenLayers.Tile> instance.
     * 
     * Parameters:
     * layer - {<OpenLayers.Layer>} layer that the tile will go in.
     * position - {<OpenLayers.Pixel>}
     * bounds - {<OpenLayers.Bounds>}
     * url - {<String>}
     * size - {<OpenLayers.Size>}
     * options - {Object}
     */   
    initialize: function(layer, position, bounds, url, size, options) {
        this.layer = layer;
        this.position = position.clone();
        this.setBounds(bounds);
        this.url = url;
        if (size) {
            this.size = size.clone();
        }

        //give the tile a unique id based on its BBOX.
        this.id = OpenLayers.Util.createUniqueID("Tile_");

        OpenLayers.Util.extend(this, options);

        this.events = new OpenLayers.Events(this);
        if (this.eventListeners instanceof Object) {
            this.events.on(this.eventListeners);
        }
    },

    /**
     * Method: unload
     * Call immediately before destroying if you are listening to tile
     * events, so that counters are properly handled if tile is still
     * loading at destroy-time. Will only fire an event if the tile is
     * still loading.
     */
    unload: function() {
       if (this.isLoading) { 
           this.isLoading = false; 
           this.events.triggerEvent("unload"); 
       }
    },
    
    /** 
     * APIMethod: destroy
     * Nullify references to prevent circular references and memory leaks.
     */
    destroy:function() {
        this.layer  = null;
        this.bounds = null;
        this.size = null;
        this.position = null;
        
        if (this.eventListeners) {
            this.events.un(this.eventListeners);
        }
        this.events.destroy();
        this.eventListeners = null;
        this.events = null;
    },
    
    /**
     * Method: draw
     * Clear whatever is currently in the tile, then return whether or not 
     *     it should actually be re-drawn. This is an example implementation
     *     that can be overridden by subclasses. The minimum thing to do here
     *     is to call <clear> and return the result from <shouldDraw>.
     *
     * Parameters:
     * force - {Boolean} If true, the tile will not be cleared and no beforedraw
     *     event will be fired. This is used for drawing tiles asynchronously
     *     after drawing has been cancelled by returning false from a beforedraw
     *     listener.
     * 
     * Returns:
     * {Boolean} Whether or not the tile should actually be drawn. Returns null
     *     if a beforedraw listener returned false.
     */
    draw: function(force) {
        if (!force) {
            //clear tile's contents and mark as not drawn
            this.clear();
        }
        var draw = this.shouldDraw();
        if (draw && !force && this.events.triggerEvent("beforedraw") === false) {
            draw = null;
        }
        return draw;
    },
    
    /**
     * Method: shouldDraw
     * Return whether or not the tile should actually be (re-)drawn. The only
     * case where we *wouldn't* want to draw the tile is if the tile is outside
     * its layer's maxExtent
     * 
     * Returns:
     * {Boolean} Whether or not the tile should actually be drawn.
     */
    shouldDraw: function() {        
        var withinMaxExtent = false,
            maxExtent = this.layer.maxExtent;
        if (maxExtent) {
            var map = this.layer.map;
            var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent();
            if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) {
                withinMaxExtent = true;
            }
        }
        
        return withinMaxExtent || this.layer.displayOutsideMaxExtent;
    },
    
    /**
     * Method: setBounds
     * Sets the bounds on this instance
     *
     * Parameters:
     * bounds {<OpenLayers.Bounds>}
     */
    setBounds: function(bounds) {
        bounds = bounds.clone();
        if (this.layer.map.baseLayer.wrapDateLine) {
            var worldExtent = this.layer.map.getMaxExtent(),
                tolerance = this.layer.map.getResolution();
            bounds = bounds.wrapDateLine(worldExtent, {
                leftTolerance: tolerance,
                rightTolerance: tolerance
            });
        }
        this.bounds = bounds;
    },
    
    /** 
     * Method: moveTo
     * Reposition the tile.
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * position - {<OpenLayers.Pixel>}
     * redraw - {Boolean} Call draw method on tile after moving.
     *     Default is true
     */
    moveTo: function (bounds, position, redraw) {
        if (redraw == null) {
            redraw = true;
        }

        this.setBounds(bounds);
        this.position = position.clone();
        if (redraw) {
            this.draw();
        }
    },

    /** 
     * Method: clear
     * Clear the tile of any bounds/position-related data so that it can 
     *     be reused in a new location.
     */
    clear: function(draw) {
        // to be extended by subclasses
    },
    
    CLASS_NAME: "OpenLayers.Tile"
});
/* ======================================================================
    OpenLayers/Tile/Image.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Tile.js
 * @requires OpenLayers/Animation.js
 * @requires OpenLayers/Util.js
 */

/**
 * Class: OpenLayers.Tile.Image
 * Instances of OpenLayers.Tile.Image are used to manage the image tiles
 * used by various layers.  Create a new image tile with the
 * <OpenLayers.Tile.Image> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Tile>
 */
OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {

    /**
     * APIProperty: events
     * {<OpenLayers.Events>} An events object that handles all 
     *     events on the tile.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * tile.events.register(type, obj, listener);
     * (end)
     *
     * Supported event types (in addition to the <OpenLayers.Tile> events):
     * beforeload - Triggered before an image is prepared for loading, when the
     *     url for the image is known already. Listeners may call <setImage> on
     *     the tile instance. If they do so, that image will be used and no new
     *     one will be created.
     */

    /** 
     * APIProperty: url
     * {String} The URL of the image being requested. No default. Filled in by
     * layer.getURL() function. May be modified by loadstart listeners.
     */
    url: null,
    
    /** 
     * Property: imgDiv
     * {HTMLImageElement} The image for this tile.
     */
    imgDiv: null,
    
    /**
     * Property: frame
     * {DOMElement} The image element is appended to the frame.  Any gutter on
     * the image will be hidden behind the frame. If no gutter is set,
     * this will be null.
     */ 
    frame: null, 

    /** 
     * Property: imageReloadAttempts
     * {Integer} Attempts to load the image.
     */
    imageReloadAttempts: null,
    
    /**
     * Property: layerAlphaHack
     * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
     */
    layerAlphaHack: null,
    
    /**
     * Property: asyncRequestId
     * {Integer} ID of an request to see if request is still valid. This is a
     * number which increments by 1 for each asynchronous request.
     */
    asyncRequestId: null,
    
    /**
     * APIProperty: maxGetUrlLength
     * {Number} If set, requests that would result in GET urls with more
     * characters than the number provided will be made using form-encoded
     * HTTP POST. It is good practice to avoid urls that are longer than 2048
     * characters.
     *
     * Caution:
     * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
     * Opera versions do not fully support this option. On all browsers,
     * transition effects are not supported if POST requests are used.
     */
    maxGetUrlLength: null,

    /**
     * Property: canvasContext
     * {CanvasRenderingContext2D} A canvas context associated with
     * the tile image.
     */
    canvasContext: null,
    
    /**
     * APIProperty: crossOriginKeyword
     * The value of the crossorigin keyword to use when loading images. This is
     * only relevant when using <getCanvasContext> for tiles from remote
     * origins and should be set to either 'anonymous' or 'use-credentials'
     * for servers that send Access-Control-Allow-Origin headers with their
     * tiles.
     */
    crossOriginKeyword: 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.
     */

    /** 
     * Constructor: OpenLayers.Tile.Image
     * Constructor for a new <OpenLayers.Tile.Image> instance.
     * 
     * Parameters:
     * layer - {<OpenLayers.Layer>} layer that the tile will go in.
     * position - {<OpenLayers.Pixel>}
     * bounds - {<OpenLayers.Bounds>}
     * url - {<String>} Deprecated. Remove me in 3.0.
     * size - {<OpenLayers.Size>}
     * options - {Object}
     */   
    initialize: function(layer, position, bounds, url, size, options) {
        OpenLayers.Tile.prototype.initialize.apply(this, arguments);

        this.url = url; //deprecated remove me
        
        this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();

        if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
            // only create frame if it's needed
            this.frame = document.createElement("div");
            this.frame.style.position = "absolute";
            this.frame.style.overflow = "hidden";
        }
        if (this.maxGetUrlLength != null) {
            OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
        }
    },
    
    /** 
     * APIMethod: destroy
     * nullify references to prevent circular references and memory leaks
     */
    destroy: function() {
        if (this.imgDiv)  {
            this.clear();
            this.imgDiv = null;
            this.frame = null;
        }
        // don't handle async requests any more
        this.asyncRequestId = null;
        OpenLayers.Tile.prototype.destroy.apply(this, arguments);
    },
    
    /**
     * Method: draw
     * Check that a tile should be drawn, and draw it.
     * 
     * Returns:
     * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
     *     false.
     */
    draw: function() {
        var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
        if (shouldDraw) {
            // The layer's reproject option is deprecated.
            if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
                // getBoundsFromBaseLayer is defined in deprecated.js.
                this.bounds = this.getBoundsFromBaseLayer(this.position);
            }
            if (this.isLoading) {
                //if we're already loading, send 'reload' instead of 'loadstart'.
                this._loadEvent = "reload";
            } else {
                this.isLoading = true;
                this._loadEvent = "loadstart";
            }
            this.renderTile();
            this.positionTile();
        } else if (shouldDraw === false) {
            this.unload();
        }
        return shouldDraw;
    },
    
    /**
     * Method: renderTile
     * Internal function to actually initialize the image tile,
     *     position it correctly, and set its url.
     */
    renderTile: function() {
        if (this.layer.async) {
            // Asynchronous image requests call the asynchronous getURL method
            // on the layer to fetch an image that covers 'this.bounds'.
            var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
            this.layer.getURLasync(this.bounds, function(url) {
                if (id == this.asyncRequestId) {
                    this.url = url;
                    this.initImage();
                }
            }, this);
        } else {
            // synchronous image requests get the url immediately.
            this.url = this.layer.getURL(this.bounds);
            this.initImage();
        }
    },

    /**
     * Method: positionTile
     * Using the properties currenty set on the layer, position the tile correctly.
     * This method is used both by the async and non-async versions of the Tile.Image
     * code.
     */
    positionTile: function() {
        var style = this.getTile().style,
            size = this.frame ? this.size :
                this.layer.getImageSize(this.bounds),
            ratio = 1;
        if (this.layer instanceof OpenLayers.Layer.Grid) {
            ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
        }
        style.left = this.position.x + "px";
        style.top = this.position.y + "px";
        style.width = Math.round(ratio * size.w) + "px";
        style.height = Math.round(ratio * size.h) + "px";
    },

    /** 
     * Method: clear
     * Remove the tile from the DOM, clear it of any image related data so that
     * it can be reused in a new location.
     */
    clear: function() {
        OpenLayers.Tile.prototype.clear.apply(this, arguments);
        var img = this.imgDiv;
        if (img) {
            var tile = this.getTile();
            if (tile.parentNode === this.layer.div) {
                this.layer.div.removeChild(tile);
            }
            this.setImgSrc();
            if (this.layerAlphaHack === true) {
                img.style.filter = "";
            }
            OpenLayers.Element.removeClass(img, "olImageLoadError");
        }
        this.canvasContext = null;
    },
    
    /**
     * Method: getImage
     * Returns or creates and returns the tile image.
     */
    getImage: function() {
        if (!this.imgDiv) {
            this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);

            var style = this.imgDiv.style;
            if (this.frame) {
                var left = 0, top = 0;
                if (this.layer.gutter) {
                    left = this.layer.gutter / this.layer.tileSize.w * 100;
                    top = this.layer.gutter / this.layer.tileSize.h * 100;
                }
                style.left = -left + "%";
                style.top = -top + "%";
                style.width = (2 * left + 100) + "%";
                style.height = (2 * top + 100) + "%";
            }
            style.visibility = "hidden";
            style.opacity = 0;
            if (this.layer.opacity < 1) {
                style.filter = 'alpha(opacity=' +
                               (this.layer.opacity * 100) +
                               ')';
            }
            style.position = "absolute";
            if (this.layerAlphaHack) {
                // move the image out of sight
                style.paddingTop = style.height;
                style.height = "0";
                style.width = "100%";
            }
            if (this.frame) {
                this.frame.appendChild(this.imgDiv);
            }
        }

        return this.imgDiv;
    },
    
    /**
     * APIMethod: setImage
     * Sets the image element for this tile. This method should only be called
     * from beforeload listeners.
     *
     * Parameters
     * img - {HTMLImageElement} The image to use for this tile.
     */
    setImage: function(img) {
        this.imgDiv = img;
    },

    /**
     * Method: initImage
     * Creates the content for the frame on the tile.
     */
    initImage: function() {
        if (!this.url && !this.imgDiv) {
            // fast path out - if there is no tile url and no previous image
            this.isLoading = false;
            return;
        }
        this.events.triggerEvent('beforeload');
        this.layer.div.appendChild(this.getTile());
        this.events.triggerEvent(this._loadEvent);
        var img = this.getImage();
        var src = img.getAttribute('src') || '';
        if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
            this._loadTimeout = window.setTimeout(
                OpenLayers.Function.bind(this.onImageLoad, this), 0
            );
        } else {
            this.stopLoading();
            if (this.crossOriginKeyword) {
                img.removeAttribute("crossorigin");
            }
            OpenLayers.Event.observe(img, "load",
                OpenLayers.Function.bind(this.onImageLoad, this)
            );
            OpenLayers.Event.observe(img, "error",
                OpenLayers.Function.bind(this.onImageError, this)
            );
            this.imageReloadAttempts = 0;
            this.setImgSrc(this.url);
        }
    },
    
    /**
     * Method: setImgSrc
     * Sets the source for the tile image
     *
     * Parameters:
     * url - {String} or undefined to hide the image
     */
    setImgSrc: function(url) {
        var img = this.imgDiv;
        if (url) {
            img.style.visibility = 'hidden';
            img.style.opacity = 0;
            // don't set crossOrigin if the url is a data URL
            if (this.crossOriginKeyword) {
                if (url.substr(0, 5) !== 'data:') {
                    img.setAttribute("crossorigin", this.crossOriginKeyword);
                } else {
                    img.removeAttribute("crossorigin");
                }
            }
            img.src = url;
        } else {
            // Remove reference to the image, and leave it to the browser's
            // caching and garbage collection.
            this.stopLoading();
            this.imgDiv = null;
            if (img.parentNode) {
                img.parentNode.removeChild(img);
            }
        }
    },
    
    /**
     * Method: getTile
     * Get the tile's markup.
     *
     * Returns:
     * {DOMElement} The tile's markup
     */
    getTile: function() {
        return this.frame ? this.frame : this.getImage();
    },

    /**
     * Method: createBackBuffer
     * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
     * of the tile's markup, because we want to avoid the reloading of the
     * image. So we clone the frame, and steal the image from the tile.
     *
     * Returns:
     * {DOMElement} The markup, or undefined if the tile has no image
     * or if it's currently loading.
     */
    createBackBuffer: function() {
        if (!this.imgDiv || this.isLoading) {
            return;
        }
        var backBuffer;
        if (this.frame) {
            backBuffer = this.frame.cloneNode(false);
            backBuffer.appendChild(this.imgDiv);
        } else {
            backBuffer = this.imgDiv;
        }
        this.imgDiv = null;
        return backBuffer;
    },

    /**
     * Method: onImageLoad
     * Handler for the image onload event
     */
    onImageLoad: function() {
        var img = this.imgDiv;
        this.stopLoading();
        img.style.visibility = 'inherit';
        img.style.opacity = this.layer.opacity;
        this.isLoading = false;
        this.canvasContext = null;
        this.events.triggerEvent("loadend");

        if (this.layerAlphaHack === true) {
            img.style.filter =
                "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
                img.src + "', sizingMethod='scale')";
        }
    },
    
    /**
     * Method: onImageError
     * Handler for the image onerror event
     */
    onImageError: function() {
        var img = this.imgDiv;
        if (img.src != null) {
            this.imageReloadAttempts++;
            if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
                this.setImgSrc(this.layer.getURL(this.bounds));
            } else {
                OpenLayers.Element.addClass(img, "olImageLoadError");
                this.events.triggerEvent("loaderror");
                this.onImageLoad();
            }
        }
    },
    
    /**
     * Method: stopLoading
     * Stops a loading sequence so <onImageLoad> won't be executed.
     */
    stopLoading: function() {
        OpenLayers.Event.stopObservingElement(this.imgDiv);
        window.clearTimeout(this._loadTimeout);
        delete this._loadTimeout;
    },

    /**
     * APIMethod: getCanvasContext
     * Returns a canvas context associated with the tile image (with
     * the image drawn on it).
     * Returns undefined if the browser does not support canvas, if
     * the tile has no image or if it's currently loading.
     *
     * The function returns a canvas context instance but the
     * underlying canvas is still available in the 'canvas' property:
     * (code)
     * var context = tile.getCanvasContext();
     * if (context) {
     *     var data = context.canvas.toDataURL('image/jpeg');
     * }
     * (end)
     *
     * Returns:
     * {Boolean}
     */
    getCanvasContext: function() {
        if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
            if (!this.canvasContext) {
                var canvas = document.createElement("canvas");
                canvas.width = this.size.w;
                canvas.height = this.size.h;
                this.canvasContext = canvas.getContext("2d");
                this.canvasContext.drawImage(this.imgDiv, 0, 0);
            }
            return this.canvasContext;
        }
    },

    CLASS_NAME: "OpenLayers.Tile.Image"

});

/** 
 * Constant: OpenLayers.Tile.Image.IMAGE
 * {HTMLImageElement} The image for a tile.
 */
OpenLayers.Tile.Image.IMAGE = (function() {
    var img = new Image();
    img.className = "olTileImage";
    // avoid image gallery menu in IE6
    img.galleryImg = "no";
    return img;
}());

/* ======================================================================
    OpenLayers/Layer/Grid.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Layer/HTTPRequest.js
 * @requires OpenLayers/Tile/Image.js
 */

/**
 * Class: OpenLayers.Layer.Grid
 * Base class for layers that use a lattice of tiles.  Create a new grid
 * layer with the <OpenLayers.Layer.Grid> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Layer.HTTPRequest>
 */
OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
    
    /**
     * APIProperty: tileSize
     * {<OpenLayers.Size>}
     */
    tileSize: null,

    /**
     * Property: tileOriginCorner
     * {String} If the <tileOrigin> property is not provided, the tile origin 
     *     will be derived from the layer's <maxExtent>.  The corner of the 
     *     <maxExtent> used is determined by this property.  Acceptable values
     *     are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
     *     (bottom right).  Default is "bl".
     */
    tileOriginCorner: "bl",
    
    /**
     * APIProperty: tileOrigin
     * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
     *     If provided, requests for tiles at all resolutions will be aligned
     *     with this location (no tiles shall overlap this location).  If
     *     not provided, the grid of tiles will be aligned with the layer's
     *     <maxExtent>.  Default is ``null``.
     */
    tileOrigin: null,
    
    /** APIProperty: tileOptions
     *  {Object} optional configuration options for <OpenLayers.Tile> instances
     *  created by this Layer, if supported by the tile class.
     */
    tileOptions: null,

    /**
     * APIProperty: tileClass
     * {<OpenLayers.Tile>} The tile class to use for this layer.
     *     Defaults is OpenLayers.Tile.Image.
     */
    tileClass: OpenLayers.Tile.Image,
    
    /**
     * Property: grid
     * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is 
     *     an array of tiles.
     */
    grid: null,

    /**
     * APIProperty: singleTile
     * {Boolean} Moves the layer into single-tile mode, meaning that one tile 
     *     will be loaded. The tile's size will be determined by the 'ratio'
     *     property. When the tile is dragged such that it does not cover the 
     *     entire viewport, it is reloaded.
     */
    singleTile: false,

    /** APIProperty: ratio
     *  {Float} Used only when in single-tile mode, this specifies the 
     *          ratio of the size of the single tile to the size of the map.
     *          Default value is 1.5.
     */
    ratio: 1.5,

    /**
     * APIProperty: buffer
     * {Integer} Used only when in gridded mode, this specifies the number of 
     *           extra rows and colums of tiles on each side which will
     *           surround the minimum grid tiles to cover the map.
     *           For very slow loading layers, a larger value may increase
     *           performance somewhat when dragging, but will increase bandwidth
     *           use significantly. 
     */
    buffer: 0,

    /**
     * APIProperty: transitionEffect
     * {String} The transition effect to use when the map is zoomed.
     * Two posible values:
     *
     * "resize" - Existing tiles are resized on zoom to provide a visual
     *     effect of the zoom having taken place immediately.  As the
     *     new tiles become available, they are drawn on top of the
     *     resized tiles (this is the default setting).
     * "map-resize" - Existing tiles are resized on zoom and placed below the
     *     base layer.  New tiles for the base layer will cover existing tiles.
     *     This setting is recommended when having an overlay duplicated during
     *     the transition is undesirable (e.g. street labels or big transparent
     *     fills). 
     * null - No transition effect.
     *
     * Using "resize" on non-opaque layers can cause undesired visual
     * effects.  Set transitionEffect to null in this case.
     */
    transitionEffect: "resize",

    /**
     * APIProperty: numLoadingTiles
     * {Integer} How many tiles are still loading?
     */
    numLoadingTiles: 0,

    /**
     * Property: serverResolutions
     * {Array(Number}} This property is documented in subclasses as
     *     an API property.
     */
    serverResolutions: null,

    /**
     * Property: loading
     * {Boolean} Indicates if tiles are being loaded.
     */
    loading: false,
    
    /**
     * Property: backBuffer
     * {DOMElement} The back buffer.
     */
    backBuffer: null,

    /**
     * Property: gridResolution
     * {Number} The resolution of the current grid. Used for backbuffer and
     *     client zoom. This property is updated every time the grid is
     *     initialized.
     */
    gridResolution: null,

    /**
     * Property: backBufferResolution
     * {Number} The resolution of the current back buffer. This property is
     *     updated each time a back buffer is created.
     */
    backBufferResolution: null,

    /**
     * Property: backBufferLonLat
     * {Object} The top-left corner of the current back buffer. Includes lon
     *     and lat properties. This object is updated each time a back buffer
     *     is created.
     */
    backBufferLonLat: null,

    /**
     * Property: backBufferTimerId
     * {Number} The id of the back buffer timer. This timer is used to
     *     delay the removal of the back buffer, thereby preventing
     *     flash effects caused by tile animation.
     */
    backBufferTimerId: null,

    /**
     * APIProperty: removeBackBufferDelay
     * {Number} Delay for removing the backbuffer when all tiles have finished
     *     loading. Can be set to 0 when no css opacity transitions for the
     *     olTileImage class are used. Default is 0 for <singleTile> layers,
     *     2500 for tiled layers. See <className> for more information on
     *     tile animation.
     */
    removeBackBufferDelay: null,

    /**
     * APIProperty: className
     * {String} Name of the class added to the layer div. If not set in the
     *     options passed to the constructor then className defaults to
     *     "olLayerGridSingleTile" for single tile layers (see <singleTile>),
     *     and "olLayerGrid" for non single tile layers.
     *
     * Note:
     *
     * The displaying of tiles is not animated by default for single tile
     *     layers - OpenLayers' default theme (style.css) includes this:
     * (code)
     * .olLayerGrid .olTileImage {
     *     -webkit-transition: opacity 0.2s linear;
     *     -moz-transition: opacity 0.2s linear;
     *     -o-transition: opacity 0.2s linear;
     *     transition: opacity 0.2s linear;
     *  }
     * (end)
     * To animate tile displaying for any grid layer the following
     *     CSS rule can be used:
     * (code)
     * .olTileImage {
     *     -webkit-transition: opacity 0.2s linear;
     *     -moz-transition: opacity 0.2s linear;
     *     -o-transition: opacity 0.2s linear;
     *     transition: opacity 0.2s linear;
     * }
     * (end)
     * In that case, to avoid flash effects, <removeBackBufferDelay>
     *     should not be zero.
     */
    className: null,
    
    /**
     * 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 event types:
     * addtile - Triggered when a tile is added to this layer. Listeners receive
     *     an object as first argument, which has a tile property that
     *     references the tile that has been added.
     * tileloadstart - Triggered when a tile starts loading. Listeners receive
     *     an object as first argument, which has a tile property that
     *     references the tile that starts loading.
     * tileloaded - Triggered when each new tile is
     *     loaded, as a means of progress update to listeners.
     *     listeners can access 'numLoadingTiles' if they wish to keep
     *     track of the loading progress. Listeners are called with an object
     *     with a 'tile' property as first argument, making the loaded tile
     *     available to the listener, and an 'aborted' property, which will be
     *     true when loading was aborted and no tile data is available.
     * tileerror - Triggered before the tileloaded event (i.e. when the tile is
     *     still hidden) if a tile failed to load. Listeners receive an object
     *     as first argument, which has a tile property that references the
     *     tile that could not be loaded.
     * retile - Triggered when the layer recreates its tile grid.
     */

    /**
     * Property: gridLayout
     * {Object} Object containing properties tilelon, tilelat, startcol,
     * startrow
     */
    gridLayout: null,
    
    /**
     * Property: rowSign
     * {Number} 1 for grids starting at the top, -1 for grids starting at the
     * bottom. This is used for several grid index and offset calculations.
     */
    rowSign: null,

    /**
     * Property: transitionendEvents
     * {Array} Event names for transitionend
     */
    transitionendEvents: [
        'transitionend', 'webkitTransitionEnd', 'otransitionend',
        'oTransitionEnd'
    ],

    /**
     * Constructor: OpenLayers.Layer.Grid
     * Create a new grid layer
     *
     * Parameters:
     * name - {String}
     * url - {String}
     * params - {Object}
     * options - {Object} Hashtable of extra options to tag onto the layer
     */
    initialize: function(name, url, params, options) {
        OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, 
                                                                arguments);
        this.grid = [];
        this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this);

        this.initProperties();

        this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1;
    },

    /**
     * Method: initProperties
     * Set any properties that depend on the value of singleTile.
     * Currently sets removeBackBufferDelay and className
     */
    initProperties: function() {
        if (this.options.removeBackBufferDelay === undefined) {
            this.removeBackBufferDelay = this.singleTile ? 0 : 2500;
        }

        if (this.options.className === undefined) {
            this.className = this.singleTile ? 'olLayerGridSingleTile' :
                                               'olLayerGrid';
        }
    },

    /**
     * Method: setMap
     *
     * Parameters:
     * map - {<OpenLayers.Map>} The map.
     */
    setMap: function(map) {
        OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map);
        OpenLayers.Element.addClass(this.div, this.className);
    },

    /**
     * Method: removeMap
     * Called when the layer is removed from the map.
     *
     * Parameters:
     * map - {<OpenLayers.Map>} The map.
     */
    removeMap: function(map) {
        this.removeBackBuffer();
    },

    /**
     * APIMethod: destroy
     * Deconstruct the layer and clear the grid.
     */
    destroy: function() {
        this.removeBackBuffer();
        this.clearGrid();

        this.grid = null;
        this.tileSize = null;
        OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); 
    },

    /**
     * APIMethod: mergeNewParams
     * Refetches tiles with new params merged, keeping a backbuffer. Each
     * loading new tile will have a css class of '.olTileReplacing'. If a
     * stylesheet applies a 'display: none' style to that class, any fade-in
     * transition will not apply, and backbuffers for each tile will be removed
     * as soon as the tile is loaded.
     * 
     * Parameters:
     * newParams - {Object}
     *
     * Returns:
     * redrawn: {Boolean} whether the layer was actually redrawn.
     */

    /**
     * Method: clearGrid
     * Go through and remove all tiles from the grid, calling
     *    destroy() on each of them to kill circular references
     */
    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.destroyTile(tile);
                }
            }
            this.grid = [];
            this.gridResolution = null;
            this.gridLayout = null;
        }
    },

   /**
    * APIMethod: addOptions
    * 
    * Parameters:
    * newOptions - {Object}
    * reinitialize - {Boolean} If set to true, and if resolution options of the
    *     current baseLayer were changed, the map will be recentered to make
    *     sure that it is displayed with a valid resolution, and a
    *     changebaselayer event will be triggered.
    */
    addOptions: function (newOptions, reinitialize) {
        var singleTileChanged = newOptions.singleTile !== undefined && 
            newOptions.singleTile !== this.singleTile;
        OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
        if (this.map && singleTileChanged) {
            this.initProperties();
            this.clearGrid();
            this.tileSize = this.options.tileSize;
            this.setTileSize();
            this.moveTo(null, true);
        }
    },
    
    /**
     * APIMethod: clone
     * Create a clone of this layer
     *
     * Parameters:
     * obj - {Object} Is this ever used?
     * 
     * Returns:
     * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
     */
    clone: function (obj) {
        
        if (obj == null) {
            obj = new OpenLayers.Layer.Grid(this.name,
                                            this.url,
                                            this.params,
                                            this.getOptions());
        }

        //get all additions from superclasses
        obj = OpenLayers.Layer.HTTPRequest.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 = [];
        obj.gridResolution = null;
        // same for backbuffer
        obj.backBuffer = null;
        obj.backBufferTimerId = null;
        obj.loading = false;
        obj.numLoadingTiles = 0;

        return obj;
    },    

    /**
     * Method: moveTo
     * This function is called whenever the map is moved. All the moving
     * of actual 'tiles' is done by the map, but moveTo's role is to accept
     * a bounds and make sure the data that that bounds requires is pre-loaded.
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * zoomChanged - {Boolean}
     * dragging - {Boolean}
     */
    moveTo:function(bounds, zoomChanged, dragging) {

        OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);

        bounds = bounds || this.map.getExtent();

        if (bounds != null) {
             
            // if grid is empty or zoom has changed, we *must* re-tile
            var forceReTile = !this.grid.length || zoomChanged;
            
            // total bounds of the tiles
            var tilesBounds = this.getTilesBounds();            

            // the new map resolution
            var resolution = this.map.getResolution();

            // the server-supported resolution for the new map resolution
            var serverResolution = this.getServerResolution(resolution);

            if (this.singleTile) {
                
                // We want to redraw whenever even the slightest part of the 
                //  current bounds is not contained by our tile.
                //  (thus, we do not specify partial -- its default is false)

                if ( forceReTile ||
                     (!dragging && !tilesBounds.containsBounds(bounds))) {

                    // In single tile mode with no transition effect, we insert
                    // a non-scaled backbuffer when the layer is moved. But if
                    // a zoom occurs right after a move, i.e. before the new
                    // image is received, we need to remove the backbuffer, or
                    // an ill-positioned image will be visible during the zoom
                    // transition.

                    if(zoomChanged && this.transitionEffect !== 'resize') {
                        this.removeBackBuffer();
                    }

                    if(!zoomChanged || this.transitionEffect === 'resize') {
                        this.applyBackBuffer(resolution);
                    }

                    this.initSingleTile(bounds);
                }
            } else {

                // if the bounds have changed such that they are not even 
                // *partially* contained by our tiles (e.g. when user has 
                // programmatically panned to the other side of the earth on
                // zoom level 18), then moveGriddedTiles could potentially have
                // to run through thousands of cycles, so we want to reTile
                // instead (thus, partial true).  
                forceReTile = forceReTile ||
                    !tilesBounds.intersectsBounds(bounds, {
                        worldBounds: this.map.baseLayer.wrapDateLine &&
                            this.map.getMaxExtent()
                    });

                if(forceReTile) {
                    if(zoomChanged && (this.transitionEffect === 'resize' ||
                                          this.gridResolution === resolution)) {
                        this.applyBackBuffer(resolution);
                    }
                    this.initGriddedTiles(bounds);
                } else {
                    this.moveGriddedTiles();
                }
            }
        }
    },

    /**
     * Method: getTileData
     * Given a map location, retrieve a tile and the pixel offset within that
     *     tile corresponding to the location.  If there is not an existing 
     *     tile in the grid that covers the given location, null will be 
     *     returned.
     *
     * Parameters:
     * loc - {<OpenLayers.LonLat>} map location
     *
     * Returns:
     * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
     *     i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
     *     offset from top left).
     */
    getTileData: function(loc) {
        var data = null,
            x = loc.lon,
            y = loc.lat,
            numRows = this.grid.length;

        if (this.map && numRows) {
            var res = this.map.getResolution(),
                tileWidth = this.tileSize.w,
                tileHeight = this.tileSize.h,
                bounds = this.grid[0][0].bounds,
                left = bounds.left,
                top = bounds.top;

            if (x < left) {
                // deal with multiple worlds
                if (this.map.baseLayer.wrapDateLine) {
                    var worldWidth = this.map.getMaxExtent().getWidth();
                    var worldsAway = Math.ceil((left - x) / worldWidth);
                    x += worldWidth * worldsAway;
                }
            }
            // tile distance to location (fractional number of tiles);
            var dtx = (x - left) / (res * tileWidth);
            var dty = (top - y) / (res * tileHeight);
            // index of tile in grid
            var col = Math.floor(dtx);
            var row = Math.floor(dty);
            if (row >= 0 && row < numRows) {
                var tile = this.grid[row][col];
                if (tile) {
                    data = {
                        tile: tile,
                        // pixel index within tile
                        i: Math.floor((dtx - col) * tileWidth),
                        j: Math.floor((dty - row) * tileHeight)
                    };                    
                }
            }
        }
        return data;
    },
    
    /**
     * Method: destroyTile
     *
     * Parameters:
     * tile - {<OpenLayers.Tile>}
     */
    destroyTile: function(tile) {
        this.removeTileMonitoringHooks(tile);
        tile.destroy();
    },

    /**
     * Method: getServerResolution
     * Return the closest server-supported resolution.
     *
     * Parameters:
     * resolution - {Number} The base resolution. If undefined the
     *     map resolution is used.
     *
     * Returns:
     * {Number} The closest server resolution value.
     */
    getServerResolution: function(resolution) {
        var distance = Number.POSITIVE_INFINITY;
        resolution = resolution || this.map.getResolution();
        if(this.serverResolutions &&
           OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
            var i, newDistance, newResolution, serverResolution;
            for(i=this.serverResolutions.length-1; i>= 0; i--) {
                newResolution = this.serverResolutions[i];
                newDistance = Math.abs(newResolution - resolution);
                if (newDistance > distance) {
                    break;
                }
                distance = newDistance;
                serverResolution = newResolution;
            }
            resolution = serverResolution;
        }
        return resolution;
    },

    /**
     * Method: getServerZoom
     * Return the zoom value corresponding to the best matching server
     * resolution, taking into account <serverResolutions> and <zoomOffset>.
     *
     * Returns:
     * {Number} The closest server supported zoom. This is not the map zoom
     *     level, but an index of the server's resolutions array.
     */
    getServerZoom: function() {
        var resolution = this.getServerResolution();
        return this.serverResolutions ?
            OpenLayers.Util.indexOf(this.serverResolutions, resolution) :
            this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0);
    },

    /**
     * Method: applyBackBuffer
     * Create, insert, scale and position a back buffer for the layer.
     *
     * Parameters:
     * resolution - {Number} The resolution to transition to.
     */
    applyBackBuffer: function(resolution) {
        if(this.backBufferTimerId !== null) {
            this.removeBackBuffer();
        }
        var backBuffer = this.backBuffer;
        if(!backBuffer) {
            backBuffer = this.createBackBuffer();
            if(!backBuffer) {
                return;
            }
            if (resolution === this.gridResolution) {
                this.div.insertBefore(backBuffer, this.div.firstChild);
            } else {
                this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div);
            }
            this.backBuffer = backBuffer;

            // set some information in the instance for subsequent
            // calls to applyBackBuffer where the same back buffer
            // is reused
            var topLeftTileBounds = this.grid[0][0].bounds;
            this.backBufferLonLat = {
                lon: topLeftTileBounds.left,
                lat: topLeftTileBounds.top
            };
            this.backBufferResolution = this.gridResolution;
        }
        
        var ratio = this.backBufferResolution / resolution;

        // scale the tiles inside the back buffer
        var tiles = backBuffer.childNodes, tile;
        for (var i=tiles.length-1; i>=0; --i) {
            tile = tiles[i];
            tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px';
            tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px';
            tile.style.width = Math.round(ratio * tile._w) + 'px';
            tile.style.height = Math.round(ratio * tile._h) + 'px';
        }

        // and position it (based on the grid's top-left corner)
        var position = this.getViewPortPxFromLonLat(
                this.backBufferLonLat, resolution);
        var leftOffset = this.map.layerContainerOriginPx.x;
        var topOffset = this.map.layerContainerOriginPx.y;
        backBuffer.style.left = Math.round(position.x - leftOffset) + 'px';
        backBuffer.style.top = Math.round(position.y - topOffset) + 'px';
    },

    /**
     * Method: createBackBuffer
     * Create a back buffer.
     *
     * Returns:
     * {DOMElement} The DOM element for the back buffer, undefined if the
     * grid isn't initialized yet.
     */
    createBackBuffer: function() {
        var backBuffer;
        if(this.grid.length > 0) {
            backBuffer = document.createElement('div');
            backBuffer.id = this.div.id + '_bb';
            backBuffer.className = 'olBackBuffer';
            backBuffer.style.position = 'absolute';
            var map = this.map;
            backBuffer.style.zIndex = this.transitionEffect === 'resize' ?
                    this.getZIndex() - 1 :
                    // 'map-resize':
                    map.Z_INDEX_BASE.BaseLayer -
                            (map.getNumLayers() - map.getLayerIndex(this));
            for(var i=0, lenI=this.grid.length; i<lenI; i++) {
                for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
                    var tile = this.grid[i][j],
                        markup = this.grid[i][j].createBackBuffer();
                    if (markup) {
                        markup._i = i;
                        markup._j = j;
                        markup._w = tile.size.w;
                        markup._h = tile.size.h;
                        markup.id = tile.id + '_bb';
                        backBuffer.appendChild(markup);
                    }
                }
            }
        }
        return backBuffer;
    },

    /**
     * Method: removeBackBuffer
     * Remove back buffer from DOM.
     */
    removeBackBuffer: function() {
        if (this._transitionElement) {
            for (var i=this.transitionendEvents.length-1; i>=0; --i) {
                OpenLayers.Event.stopObserving(this._transitionElement,
                    this.transitionendEvents[i], this._removeBackBuffer);
            }
            delete this._transitionElement;
        }
        if(this.backBuffer) {
            if (this.backBuffer.parentNode) {
                this.backBuffer.parentNode.removeChild(this.backBuffer);
            }
            this.backBuffer = null;
            this.backBufferResolution = null;
            if(this.backBufferTimerId !== null) {
                window.clearTimeout(this.backBufferTimerId);
                this.backBufferTimerId = null;
            }
        }
    },

    /**
     * Method: moveByPx
     * Move the layer based on pixel vector.
     *
     * Parameters:
     * dx - {Number}
     * dy - {Number}
     */
    moveByPx: function(dx, dy) {
        if (!this.singleTile) {
            this.moveGriddedTiles();
        }
    },

    /**
     * APIMethod: setTileSize
     * Check if we are in singleTile mode and if so, set the size as a ratio
     *     of the map size (as specified by the layer's 'ratio' property).
     * 
     * Parameters:
     * size - {<OpenLayers.Size>}
     */
    setTileSize: function(size) { 
        if (this.singleTile) {
            size = this.map.getSize();
            size.h = parseInt(size.h * this.ratio, 10);
            size.w = parseInt(size.w * this.ratio, 10);
        } 
        OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
    },

    /**
     * APIMethod: getTilesBounds
     * Return the bounds of the tile grid.
     *
     * Returns:
     * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
     *     currently loaded tiles (including those partially or not at all seen 
     *     onscreen).
     */
    getTilesBounds: function() {    
        var bounds = null; 
        
        var length = this.grid.length;
        if (length) {
            var bottomLeftTileBounds = this.grid[length - 1][0].bounds,
                width = this.grid[0].length * bottomLeftTileBounds.getWidth(),
                height = this.grid.length * bottomLeftTileBounds.getHeight();
            
            bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left, 
                                           bottomLeftTileBounds.bottom,
                                           bottomLeftTileBounds.left + width, 
                                           bottomLeftTileBounds.bottom + height);
        }   
        return bounds;
    },

    /**
     * Method: initSingleTile
     * 
     * Parameters: 
     * bounds - {<OpenLayers.Bounds>}
     */
    initSingleTile: function(bounds) {
        this.events.triggerEvent("retile");

        //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));
  
        var px = this.map.getLayerPxFromLonLat({
            lon: tileBounds.left,
            lat: tileBounds.top
        });

        if (!this.grid.length) {
            this.grid[0] = [];
        }

        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);
        }           
        
        //remove all but our single tile
        this.removeExcessTiles(1,1);

        // store the resolution of the grid
        this.gridResolution = this.getServerResolution();
    },

    /** 
     * Method: calculateGridLayout
     * Generate parameters for the grid layout.
     *
     * Parameters:
     * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
     *     object with a 'left' and 'top' properties.
     * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
     *     object with a 'lon' and 'lat' properties.
     * resolution - {Number}
     *
     * Returns:
     * {Object} Object containing properties tilelon, tilelat, startcol,
     * startrow
     */
    calculateGridLayout: function(bounds, origin, resolution) {
        var tilelon = resolution * this.tileSize.w;
        var tilelat = resolution * this.tileSize.h;
        
        var offsetlon = bounds.left - origin.lon;
        var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
        
        var rowSign = this.rowSign;

        var offsetlat = rowSign * (origin.lat - bounds.top + tilelat);  
        var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign;
        
        return { 
          tilelon: tilelon, tilelat: tilelat,
          startcol: tilecol, startrow: tilerow
        };

    },
    
    /**
     * Method: getTileOrigin
     * Determine the origin for aligning the grid of tiles.  If a <tileOrigin>
     *     property is supplied, that will be returned.  Otherwise, the origin
     *     will be derived from the layer's <maxExtent> property.  In this case,
     *     the tile origin will be the corner of the <maxExtent> given by the 
     *     <tileOriginCorner> property.
     *
     * Returns:
     * {<OpenLayers.LonLat>} The tile origin.
     */
    getTileOrigin: function() {
        var origin = this.tileOrigin;
        if (!origin) {
            var extent = this.getMaxExtent();
            var edges = ({
                "tl": ["left", "top"],
                "tr": ["right", "top"],
                "bl": ["left", "bottom"],
                "br": ["right", "bottom"]
            })[this.tileOriginCorner];
            origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
        }
        return origin;
    },

    /**
     * Method: getTileBoundsForGridIndex
     *
     * Parameters:
     * row - {Number} The row of the grid
     * col - {Number} The column of the grid
     *
     * Returns:
     * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
     */
    getTileBoundsForGridIndex: function(row, col) {
        var origin = this.getTileOrigin();
        var tileLayout = this.gridLayout;
        var tilelon = tileLayout.tilelon;
        var tilelat = tileLayout.tilelat;
        var startcol = tileLayout.startcol;
        var startrow = tileLayout.startrow;
        var rowSign = this.rowSign;
        return new OpenLayers.Bounds(
            origin.lon + (startcol + col) * tilelon,
            origin.lat - (startrow + row * rowSign) * tilelat * rowSign,
            origin.lon + (startcol + col + 1) * tilelon,
            origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign
        );
    },

    /**
     * Method: initGriddedTiles
     * 
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     */
    initGriddedTiles:function(bounds) {
        this.events.triggerEvent("retile");

        // work out mininum number of rows and columns; this is the number of
        // tiles required to cover the viewport plus at least one for panning

        var viewSize = this.map.getSize();
        
        var origin = this.getTileOrigin();
        var resolution = this.map.getResolution(),
            serverResolution = this.getServerResolution(),
            ratio = resolution / serverResolution,
            tileSize = {
                w: this.tileSize.w / ratio,
                h: this.tileSize.h / ratio
            };

        var minRows = Math.ceil(viewSize.h/tileSize.h) + 
                      2 * this.buffer + 1;
        var minCols = Math.ceil(viewSize.w/tileSize.w) +
                      2 * this.buffer + 1;

        var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution);
        this.gridLayout = tileLayout;
        
        var tilelon = tileLayout.tilelon;
        var tilelat = tileLayout.tilelat;
        
        var layerContainerDivLeft = this.map.layerContainerOriginPx.x;
        var layerContainerDivTop = this.map.layerContainerOriginPx.y;

        var tileBounds = this.getTileBoundsForGridIndex(0, 0);
        var startPx = this.map.getViewPortPxFromLonLat(
            new OpenLayers.LonLat(tileBounds.left, tileBounds.top)
        );
        startPx.x = Math.round(startPx.x) - layerContainerDivLeft;
        startPx.y = Math.round(startPx.y) - layerContainerDivTop;

        var tileData = [], center = this.map.getCenter();

        var rowidx = 0;
        do {
            var row = this.grid[rowidx];
            if (!row) {
                row = [];
                this.grid.push(row);
            }
            
            var colidx = 0;
            do {
                tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx);
                var px = startPx.clone();
                px.x = px.x + colidx * Math.round(tileSize.w);
                px.y = px.y + rowidx * Math.round(tileSize.h);
                var tile = row[colidx];
                if (!tile) {
                    tile = this.addTile(tileBounds, px);
                    this.addTileMonitoringHooks(tile);
                    row.push(tile);
                } else {
                    tile.moveTo(tileBounds, px, false);
                }
                var tileCenter = tileBounds.getCenterLonLat();
                tileData.push({
                    tile: tile,
                    distance: Math.pow(tileCenter.lon - center.lon, 2) +
                        Math.pow(tileCenter.lat - center.lat, 2)
                });
     
                colidx += 1;
            } while ((tileBounds.right <= bounds.right + tilelon * this.buffer)
                     || colidx < minCols);
             
            rowidx += 1;
        } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer)
                || rowidx < minRows);
        
        //shave off exceess rows and colums
        this.removeExcessTiles(rowidx, colidx);

        var resolution = this.getServerResolution();
        // store the resolution of the grid
        this.gridResolution = resolution;

        //now actually draw the tiles
        tileData.sort(function(a, b) {
            return a.distance - b.distance; 
        });
        for (var i=0, ii=tileData.length; i<ii; ++i) {
            tileData[i].tile.draw();
        }
    },

    /**
     * Method: getMaxExtent
     * Get this layer's maximum extent. (Implemented as a getter for
     *     potential specific implementations in sub-classes.)
     *
     * Returns:
     * {<OpenLayers.Bounds>}
     */
    getMaxExtent: function() {
        return this.maxExtent;
    },
    
    /**
     * APIMethod: addTile
     * Create a tile, initialize it, and add it to the layer div. 
     *
     * Parameters
     * bounds - {<OpenLayers.Bounds>}
     * position - {<OpenLayers.Pixel>}
     *
     * Returns:
     * {<OpenLayers.Tile>} The added OpenLayers.Tile
     */
    addTile: function(bounds, position) {
        var tile = new this.tileClass(
            this, position, bounds, null, this.tileSize, this.tileOptions
        );
        this.events.triggerEvent("addtile", {tile: tile});
        return tile;
    },
    
    /** 
     * 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 tiles.
     * 
     * Parameters: 
     * tile - {<OpenLayers.Tile>}
     */
    addTileMonitoringHooks: function(tile) {
        
        var replacingCls = 'olTileReplacing';

        tile.onLoadStart = function() {
            //if that was first tile then trigger a 'loadstart' on the layer
            if (this.loading === false) {
                this.loading = true;
                this.events.triggerEvent("loadstart");
            }
            this.events.triggerEvent("tileloadstart", {tile: tile});
            this.numLoadingTiles++;
            if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) {
                OpenLayers.Element.addClass(tile.getTile(), replacingCls);
            }
        };
      
        tile.onLoadEnd = function(evt) {
            this.numLoadingTiles--;
            var aborted = evt.type === 'unload';
            this.events.triggerEvent("tileloaded", {
                tile: tile,
                aborted: aborted
            });
            if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) {
                var tileDiv = tile.getTile();
                if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') {
                    var bufferTile = document.getElementById(tile.id + '_bb');
                    if (bufferTile) {
                        bufferTile.parentNode.removeChild(bufferTile);
                    }
                }
                OpenLayers.Element.removeClass(tileDiv, replacingCls);
            }
            //if that was the last tile, then trigger a 'loadend' on the layer
            if (this.numLoadingTiles === 0) {
                if (this.backBuffer) {
                    if (this.backBuffer.childNodes.length === 0) {
                        // no tiles transitioning, remove immediately
                        this.removeBackBuffer();
                    } else {
                        // wait until transition has ended or delay has passed
                        this._transitionElement = aborted ?
                            this.div.lastChild : tile.imgDiv;
                        var transitionendEvents = this.transitionendEvents;
                        for (var i=transitionendEvents.length-1; i>=0; --i) {
                            OpenLayers.Event.observe(this._transitionElement,
                                transitionendEvents[i],
                                this._removeBackBuffer);
                        }
                        // the removal of the back buffer is delayed to prevent
                        // flash effects due to the animation of tile displaying
                        this.backBufferTimerId = window.setTimeout(
                            this._removeBackBuffer, this.removeBackBufferDelay
                        );
                    }
                }
                this.loading = false;
                this.events.triggerEvent("loadend");
            }
        };
        
        tile.onLoadError = function() {
            this.events.triggerEvent("tileerror", {tile: tile});
        };
        
        tile.events.on({
            "loadstart": tile.onLoadStart,
            "loadend": tile.onLoadEnd,
            "unload": tile.onLoadEnd,
            "loaderror": tile.onLoadError,
            scope: this
        });
    },

    /** 
     * 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,
            "loaderror": tile.onLoadError,
            scope: this
        });
    },
    
    /**
     * Method: moveGriddedTiles
     */
    moveGriddedTiles: function() {
        var buffer = this.buffer + 1;
        while(true) {
            var tlTile = this.grid[0][0];
            var tlViewPort = {
                x: tlTile.position.x +
                    this.map.layerContainerOriginPx.x,
                y: tlTile.position.y +
                    this.map.layerContainerOriginPx.y
            };
            var ratio = this.getServerResolution() / this.map.getResolution();
            var tileSize = {
                w: Math.round(this.tileSize.w * ratio),
                h: Math.round(this.tileSize.h * ratio)
            };
            if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
                this.shiftColumn(true, tileSize);
            } else if (tlViewPort.x < -tileSize.w * buffer) {
                this.shiftColumn(false, tileSize);
            } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
                this.shiftRow(true, tileSize);
            } else if (tlViewPort.y < -tileSize.h * buffer) {
                this.shiftRow(false, tileSize);
            } else {
                break;
            }
        }
    },

    /**
     * Method: shiftRow
     * Shifty grid work
     *
     * Parameters:
     * prepend - {Boolean} if true, prepend to beginning.
     *                          if false, then append to end
     * tileSize - {Object} rendered tile size; object with w and h properties
     */
    shiftRow: function(prepend, tileSize) {
        var grid = this.grid;
        var rowIndex = prepend ? 0 : (grid.length - 1);
        var sign = prepend ? -1 : 1;
        var rowSign = this.rowSign;
        var tileLayout = this.gridLayout;
        tileLayout.startrow += sign * rowSign;

        var modelRow = grid[rowIndex];
        var row = grid[prepend ? 'pop' : 'shift']();
        for (var i=0, len=row.length; i<len; i++) {
            var tile = row[i];
            var position = modelRow[i].position.clone();
            position.y += tileSize.h * sign;
            tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
        }
        grid[prepend ? 'unshift' : 'push'](row);
    },

    /**
     * Method: shiftColumn
     * Shift grid work in the other dimension
     *
     * Parameters:
     * prepend - {Boolean} if true, prepend to beginning.
     *                          if false, then append to end
     * tileSize - {Object} rendered tile size; object with w and h properties
     */
    shiftColumn: function(prepend, tileSize) {
        var grid = this.grid;
        var colIndex = prepend ? 0 : (grid[0].length - 1);
        var sign = prepend ? -1 : 1;
        var tileLayout = this.gridLayout;
        tileLayout.startcol += sign;

        for (var i=0, len=grid.length; i<len; i++) {
            var row = grid[i];
            var position = row[colIndex].position.clone();
            var tile = row[prepend ? 'pop' : 'shift']();            
            position.x += tileSize.w * sign;
            tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
            row[prepend ? 'unshift' : 'push'](tile);
        }
    },

    /**
     * Method: removeExcessTiles
     * When the size of the map or the buffer changes, we may need to
     *     remove some excess rows and columns.
     * 
     * Parameters:
     * rows - {Integer} Maximum number of rows we want our grid to have.
     * columns - {Integer} Maximum number of columns we want our grid to have.
     */
    removeExcessTiles: function(rows, columns) {
        var i, l;
        
        // remove extra rows
        while (this.grid.length > rows) {
            var row = this.grid.pop();
            for (i=0, l=row.length; i<l; i++) {
                var tile = row[i];
                this.destroyTile(tile);
            }
        }
        
        // remove extra columns
        for (i=0, l=this.grid.length; i<l; i++) {
            while (this.grid[i].length > columns) {
                var row = this.grid[i];
                var tile = row.pop();
                this.destroyTile(tile);
            }
        }
    },

    /**
     * Method: onMapResize
     * For singleTile layers, this will set a new tile size according to the
     * dimensions of the map pane.
     */
    onMapResize: function() {
        if (this.singleTile) {
            this.clearGrid();
            this.setTileSize();
        }
    },
    
    /**
     * 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 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/Layer/XYZ.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Layer/Grid.js
 */

/** 
 * Class: OpenLayers.Layer.XYZ
 * The XYZ class is designed to make it easier for people who have tiles
 * arranged by a standard XYZ grid. 
 * 
 * Inherits from:
 *  - <OpenLayers.Layer.Grid>
 */
OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
    
    /**
     * APIProperty: isBaseLayer
     * Default is true, as this is designed to be a base tile source. 
     */
    isBaseLayer: true,
    
    /**
     * APIProperty: sphericalMercator
     * Whether the tile extents should be set to the defaults for 
     *    spherical mercator. Useful for things like OpenStreetMap.
     *    Default is false, except for the OSM subclass.
     */
    sphericalMercator: false,

    /**
     * APIProperty: zoomOffset
     * {Number} If your cache has more zoom levels than you want to provide
     *     access to with this layer, supply a zoomOffset.  This zoom offset
     *     is added to the current map zoom level to determine the level
     *     for a requested tile.  For example, if you supply a zoomOffset
     *     of 3, when the map is at the zoom 0, tiles will be requested from
     *     level 3 of your cache.  Default is 0 (assumes cache level and map
     *     zoom are equivalent).  Using <zoomOffset> is an alternative to
     *     setting <serverResolutions> if you only want to expose a subset
     *     of the server resolutions.
     */
    zoomOffset: 0,
    
    /**
     * APIProperty: serverResolutions
     * {Array} A list of all resolutions available on the server.  Only set this
     *     property if the map resolutions differ from the server. This
     *     property serves two purposes. (a) <serverResolutions> can include
     *     resolutions that the server supports and that you don't want to
     *     provide with this layer; you can also look at <zoomOffset>, which is
     *     an alternative to <serverResolutions> for that specific purpose.
     *     (b) The map can work with resolutions that aren't supported by
     *     the server, i.e. that aren't in <serverResolutions>. When the
     *     map is displayed in such a resolution data for the closest
     *     server-supported resolution is loaded and the layer div is
     *     stretched as necessary.
     */
    serverResolutions: null,

    /**
     * Constructor: OpenLayers.Layer.XYZ
     *
     * Parameters:
     * name - {String}
     * url - {String}
     * options - {Object} Hashtable of extra options to tag onto the layer
     */
    initialize: function(name, url, options) {
        if (options && options.sphericalMercator || this.sphericalMercator) {
            options = OpenLayers.Util.extend({
                projection: "EPSG:900913",
                numZoomLevels: 19
            }, options);
        }
        OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
            name || this.name, url || this.url, {}, options
        ]);
    },
    
    /**
     * APIMethod: clone
     * Create a clone of this layer
     *
     * Parameters:
     * obj - {Object} Is this ever used?
     * 
     * Returns:
     * {<OpenLayers.Layer.XYZ>} An exact clone of this OpenLayers.Layer.XYZ
     */
    clone: function (obj) {
        
        if (obj == null) {
            obj = new OpenLayers.Layer.XYZ(this.name,
                                            this.url,
                                            this.getOptions());
        }

        //get all additions from superclasses
        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);

        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 xyz = this.getXYZ(bounds);
        var url = this.url;
        if (OpenLayers.Util.isArray(url)) {
            var s = '' + xyz.x + xyz.y + xyz.z;
            url = this.selectUrl(s, url);
        }
        
        return OpenLayers.String.format(url, xyz);
    },
    
    /**
     * Method: getXYZ
     * Calculates x, y and z for the given bounds.
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     *
     * Returns:
     * {Object} - an object with x, y and z properties.
     */
    getXYZ: function(bounds) {
        var res = this.getServerResolution();
        var x = Math.round((bounds.left - this.maxExtent.left) /
            (res * this.tileSize.w));
        var y = Math.round((this.maxExtent.top - bounds.top) /
            (res * this.tileSize.h));
        var z = this.getServerZoom();

        if (this.wrapDateLine) {
            var limit = Math.pow(2, z);
            x = ((x % limit) + limit) % limit;
        }

        return {'x': x, 'y': y, 'z': z};
    },
    
    /* 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.maxExtent.left,
                                                this.maxExtent.bottom);
        }                                       
    },

    CLASS_NAME: "OpenLayers.Layer.XYZ"
});
/* ======================================================================
    OpenLayers/Layer/OSM.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Layer/XYZ.js
 */

/**
 * Class: OpenLayers.Layer.OSM
 * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap
 *    hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use
 *    a different layer instead, you need to provide a different
 *    URL to the constructor. Here's an example for using OpenCycleMap:
 * 
 * (code)
 *     new OpenLayers.Layer.OSM("OpenCycleMap", 
 *       ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
 *        "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
 *        "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); 
 * (end)
 *
 * Inherits from:
 *  - <OpenLayers.Layer.XYZ>
 */
OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {

    /**
     * APIProperty: name
     * {String} The layer name. Defaults to "OpenStreetMap" if the first
     * argument to the constructor is null or undefined.
     */
    name: "OpenStreetMap",

    /**
     * APIProperty: url
     * {String} The tileset URL scheme. Defaults to
     * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png
     * (the official OSM tileset) if the second argument to the constructor
     * is null or undefined. To use another tileset you can have something
     * like this:
     * (code)
     *     new OpenLayers.Layer.OSM("OpenCycleMap", 
     *       ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
     *        "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
     *        "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); 
     * (end)
     */
    url: [
        'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png',
        'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png',
        'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png'
    ],

    /**
     * Property: attribution
     * {String} The layer attribution.
     */
    attribution: "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",

    /**
     * Property: sphericalMercator
     * {Boolean}
     */
    sphericalMercator: true,

    /**
     * Property: wrapDateLine
     * {Boolean}
     */
    wrapDateLine: true,

    /** APIProperty: tileOptions
     *  {Object} optional configuration options for <OpenLayers.Tile> instances
     *  created by this Layer. Default is
     *
     *  (code)
     *  {crossOriginKeyword: 'anonymous'}
     *  (end)
     *
     *  When using OSM tilesets other than the default ones, it may be
     *  necessary to set this to
     *
     *  (code)
     *  {crossOriginKeyword: null}
     *  (end)
     *
     *  if the server does not send Access-Control-Allow-Origin headers.
     */
    tileOptions: null,

    /**
     * Constructor: OpenLayers.Layer.OSM
     *
     * Parameters:
     * name - {String} The layer name.
     * url - {String} The tileset URL scheme.
     * options - {Object} Configuration options for the layer. Any inherited
     *     layer option can be set in this object (e.g.
     *     <OpenLayers.Layer.Grid.buffer>).
     */
    initialize: function(name, url, options) {
        OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
        this.tileOptions = OpenLayers.Util.extend({
            crossOriginKeyword: 'anonymous'
        }, this.options && this.options.tileOptions);
    },

    /**
     * Method: clone
     */
    clone: function(obj) {
        if (obj == null) {
            obj = new OpenLayers.Layer.OSM(
                this.name, this.url, this.getOptions());
        }
        obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
        return obj;
    },

    CLASS_NAME: "OpenLayers.Layer.OSM"
});
/* ======================================================================
    OpenLayers/Renderer.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.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: root
     * {DOMElement}
     */
    root: 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,
    
    /**
     * Property: featureDx
     * {Number} Feature offset in x direction. Will be calculated for and
     * applied to the current feature while rendering (see
     * <calculateFeatureDx>).
     */
    featureDx: 0,
    
    /**
     * 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);
        OpenLayers.Util.extend(this, options);
    },
    
    /**
     * 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}
     *
     * 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) {
        this.extent = extent.clone();
        if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
            var ratio = extent.getWidth() / this.map.getExtent().getWidth(),
                extent = extent.scale(1 / ratio);
            this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio);
        }
        if (resolutionChanged) {
            this.resolution = null;
        }
        return true;
    },
    
    /**
     * 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:
     * {Float} 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) {
            var bounds = feature.geometry.getBounds();
            if(bounds) {
                var worldBounds;
                if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
                    worldBounds = this.map.getMaxExtent();
                }
                if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) {
                    style = {display: "none"};
                } else {
                    this.calculateFeatureDx(bounds, worldBounds);
                }
                var rendered = this.drawGeometry(feature.geometry, style, feature.id);
                if(style.display != "none" && style.label && rendered !== false) {

                    var location = feature.geometry.getCentroid(); 
                    if(style.labelXOffset || style.labelYOffset) {
                        var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
                        var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
                        var res = this.getResolution();
                        location.move(xOffset*res, yOffset*res);
                    }
                    this.drawText(feature.id, style, location);
                } else {
                    this.removeText(feature.id);
                }
                return rendered;
            }
        }
    },

    /**
     * Method: calculateFeatureDx
     * {Number} Calculates the feature offset in x direction. Looking at the
     * center of the feature bounds and the renderer extent, we calculate how
     * many world widths the two are away from each other. This distance is
     * used to shift the feature as close as possible to the center of the
     * current enderer extent, which ensures that the feature is visible in the
     * current viewport.
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>} Bounds of the feature
     * worldBounds - {<OpenLayers.Bounds>} Bounds of the world
     */
    calculateFeatureDx: function(bounds, worldBounds) {
        this.featureDx = 0;
        if (worldBounds) {
            var worldWidth = worldBounds.getWidth(),
                rendererCenterX = (this.extent.left + this.extent.right) / 2,
                featureCenterX = (bounds.left + bounds.right) / 2,
                worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth);
            this.featureDx = worldsAway * worldWidth;
        }
    },

    /** 
     * 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: drawText
     * Function for drawing text labels.
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * featureId - {String}
     * style -
     * location - {<OpenLayers.Geometry.Point>}
     */
    drawText: function(featureId, style, location) {},

    /**
     * Method: removeText
     * Function for removing text labels.
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * featureId - {String}
     */
    removeText: function(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 undefined.
     */
    getFeatureIdFromEvent: function(evt) {},
    
    /**
     * Method: eraseFeatures 
     * This is called by the layer to erase features
     * 
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>)} 
     */
    eraseFeatures: function(features) {
        if(!(OpenLayers.Util.isArray(features))) {
            features = [features];
        }
        for(var i=0, len=features.length; i<len; ++i) {
            var feature = features[i];
            this.eraseGeometry(feature.geometry, feature.id);
            this.removeText(feature.id);
        }
    },
    
    /**
     * Method: eraseGeometry
     * Remove a geometry from the renderer (by id).
     * virtual function.
     * 
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} 
     * featureId - {String}
     */
    eraseGeometry: function(geometry, featureId) {},
    
    /**
     * Method: moveRoot
     * moves this renderer's root to a (different) renderer.
     * To be implemented by subclasses that require a common renderer root for
     * feature selection.
     * 
     * Parameters:
     * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
     */
    moveRoot: function(renderer) {},

    /**
     * Method: getRenderLayerId
     * Gets the layer that this renderer's output appears on. If moveRoot was
     * used, this will be different from the id of the layer containing the
     * features rendered by this renderer.
     * 
     * Returns:
     * {String} the id of the output layer.
     */
    getRenderLayerId: function() {
        return this.container.id;
    },
    
    /**
     * Method: applyDefaultSymbolizer
     * 
     * Parameters:
     * symbolizer - {Object}
     * 
     * Returns:
     * {Object}
     */
    applyDefaultSymbolizer: function(symbolizer) {
        var result = OpenLayers.Util.extend({},
            OpenLayers.Renderer.defaultSymbolizer);
        if(symbolizer.stroke === false) {
            delete result.strokeWidth;
            delete result.strokeColor;
        }
        if(symbolizer.fill === false) {
            delete result.fillColor;
        }
        OpenLayers.Util.extend(result, symbolizer);
        return result;
    },

    CLASS_NAME: "OpenLayers.Renderer"
});

/**
 * Constant: OpenLayers.Renderer.defaultSymbolizer
 * {Object} Properties from this symbolizer will be applied to symbolizers
 *     with missing properties. This can also be used to set a global
 *     symbolizer default in OpenLayers. To be SLD 1.x compliant, add the
 *     following code before rendering any vector features:
 * (code)
 * OpenLayers.Renderer.defaultSymbolizer = {
 *     fillColor: "#808080",
 *     fillOpacity: 1,
 *     strokeColor: "#000000",
 *     strokeOpacity: 1,
 *     strokeWidth: 1,
 *     pointRadius: 3,
 *     graphicName: "square"
 * };
 * (end)
 */
OpenLayers.Renderer.defaultSymbolizer = {
    fillColor: "#000000",
    strokeColor: "#000000",
    strokeWidth: 2,
    fillOpacity: 1,
    strokeOpacity: 1,
    pointRadius: 0,
    labelAlign: 'cm'
};
    


/**
 * 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/Renderer/Canvas.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Renderer.js
 */

/**
 * Class: OpenLayers.Renderer.Canvas 
 * A renderer based on the 2D 'canvas' drawing element.
 * 
 * Inherits:
 *  - <OpenLayers.Renderer>
 */
OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
    
    /**
     * APIProperty: hitDetection
     * {Boolean} Allow for hit detection of features.  Default is true.
     */
    hitDetection: true,
    
    /**
     * Property: hitOverflow
     * {Number} The method for converting feature identifiers to color values
     *     supports 16777215 sequential values.  Two features cannot be 
     *     predictably detected if their identifiers differ by more than this
     *     value.  The hitOverflow allows for bigger numbers (but the 
     *     difference in values is still limited).
     */
    hitOverflow: 0,

    /**
     * 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: pendingRedraw
     * {Boolean} The renderer needs a redraw call to render features added while
     *     the renderer was locked.
     */
    pendingRedraw: false,
    
    /**
     * Property: cachedSymbolBounds
     * {Object} Internal cache of calculated symbol extents.
     */
    cachedSymbolBounds: {},
    
    /**
     * Constructor: OpenLayers.Renderer.Canvas
     *
     * Parameters:
     * containerID - {<String>}
     * options - {Object} Optional properties to be set on the renderer.
     */
    initialize: function(containerID, options) {
        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 = {};
        if (this.hitDetection) {
            this.hitCanvas = document.createElement("canvas");
            this.hitContext = this.hitCanvas.getContext("2d");
        }
    },
    
    /**
     * Method: setExtent
     * Set the visible part of the layer.
     *
     * 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() {
        OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
        // always redraw features
        return false;
    },
    
    /** 
     * 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>}
     * featureId - {String}
     */
    eraseGeometry: function(geometry, featureId) {
        this.eraseFeatures(this.features[featureId][0]);
    },

    /**
     * APIMethod: supported
     * 
     * Returns:
     * {Boolean} Whether or not the browser supports the renderer class
     */
    supported: function() {
        return OpenLayers.CANVAS_SUPPORTED;
    },    
    
    /**
     * 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();
        var root = this.root;
        root.style.width = size.w + "px";
        root.style.height = size.h + "px";
        root.width = size.w;
        root.height = size.h;
        this.resolution = null;
        if (this.hitDetection) {
            var hitCanvas = this.hitCanvas;
            hitCanvas.style.width = size.w + "px";
            hitCanvas.style.height = size.h + "px";
            hitCanvas.width = size.w;
            hitCanvas.height = size.h;
        }
    },
    
    /**
     * Method: drawFeature
     * Draw the feature. Stores the feature in the features list,
     * then redraws the layer. 
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} 
     * style - {<Object>} 
     *
     * Returns:
     * {Boolean} The feature has been drawn completely.  If the feature has no
     *     geometry, undefined will be returned.  If the feature is not rendered
     *     for other reasons, false will be returned.
     */
    drawFeature: function(feature, style) {
        var rendered;
        if (feature.geometry) {
            style = this.applyDefaultSymbolizer(style || feature.style);
            // don't render if display none or feature outside extent
            var bounds = feature.geometry.getBounds();

            var worldBounds;
            if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
                worldBounds = this.map.getMaxExtent();
            }

            var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds});

            rendered = (style.display !== "none") && !!bounds && intersects;
            if (rendered) {
                // keep track of what we have rendered for redraw
                this.features[feature.id] = [feature, style];
            }
            else {
                // remove from features tracked for redraw
                delete(this.features[feature.id]);
            }
            this.pendingRedraw = true;
        }
        if (this.pendingRedraw && !this.locked) {
            this.redraw();
            this.pendingRedraw = false;
        }
        return rendered;
    },

    /** 
     * Method: drawGeometry
     * Used when looping (in redraw) over the features; draws
     * the canvas. 
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>} 
     * style - {Object} 
     */
    drawGeometry: function(geometry, style, featureId) {
        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, featureId);
            }
            return;
        }
        switch (geometry.CLASS_NAME) {
            case "OpenLayers.Geometry.Point":
                this.drawPoint(geometry, style, featureId);
                break;
            case "OpenLayers.Geometry.LineString":
                this.drawLineString(geometry, style, featureId);
                break;
            case "OpenLayers.Geometry.LinearRing":
                this.drawLinearRing(geometry, style, featureId);
                break;
            case "OpenLayers.Geometry.Polygon":
                this.drawPolygon(geometry, style, featureId);
                break;
            default:
                break;
        }
    },

    /**
     * Method: drawExternalGraphic
     * Called to draw External graphics. 
     * 
     * Parameters: 
     * geometry - {<OpenLayers.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */ 
    drawExternalGraphic: function(geometry, style, featureId) {
        var img = new Image();

        var title = style.title || style.graphicTitle;        
        if (title) {
            img.title = title;           
        }

        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 onLoad = function() {
            if(!this.features[featureId]) {
                return;
            }
            var pt = this.getLocalXY(geometry);
            var p0 = pt[0];
            var p1 = pt[1];
            if(!isNaN(p0) && !isNaN(p1)) {
                var x = (p0 + xOffset) | 0;
                var y = (p1 + yOffset) | 0;
                var canvas = this.canvas;
                canvas.globalAlpha = opacity;
                var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
                    (OpenLayers.Renderer.Canvas.drawImageScaleFactor =
                        /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
                            // 320 is the screen width of the G1 phone, for
                            // which drawImage works out of the box.
                            320 / window.screen.width : 1
                    );
                canvas.drawImage(
                    img, x*factor, y*factor, width*factor, height*factor
                );
                if (this.hitDetection) {
                    this.setHitContextStyle("fill", featureId);
                    this.hitContext.fillRect(x, y, width, height);
                }
            }
        };

        img.onload = OpenLayers.Function.bind(onLoad, this);
        img.src = style.externalGraphic;
    },

    /**
     * Method: drawNamedSymbol
     * Called to draw Well Known Graphic Symbol Name. 
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * geometry - {<OpenLayers.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */ 
    drawNamedSymbol: function(geometry, style, featureId) {
        var x, y, cx, cy, i, symbolBounds, scaling, angle;
        var unscaledStrokeWidth;
        var deg2rad = Math.PI / 180.0;
        
        var symbol = OpenLayers.Renderer.symbol[style.graphicName];
         
        if (!symbol) {
            throw new Error(style.graphicName + ' is not a valid symbol name');
        }
        
        if (!symbol.length || symbol.length < 2) return;
        
        var pt = this.getLocalXY(geometry);
        var p0 = pt[0];
        var p1 = pt[1];
       
        if (isNaN(p0) || isNaN(p1)) return;
        
        // Use rounded line caps
        this.canvas.lineCap = "round";
        this.canvas.lineJoin = "round";
        
        if (this.hitDetection) {
            this.hitContext.lineCap = "round";
            this.hitContext.lineJoin = "round";
        }
        
        // Scale and rotate symbols, using precalculated bounds whenever possible.
        if (style.graphicName in this.cachedSymbolBounds) {
            symbolBounds = this.cachedSymbolBounds[style.graphicName];
        } else {
            symbolBounds = new OpenLayers.Bounds();
            for(i = 0; i < symbol.length; i+=2) {
                symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1]));
            }
            this.cachedSymbolBounds[style.graphicName] = symbolBounds;
        }
        
        // Push symbol scaling, translation and rotation onto the transformation stack in reverse order.
        // Don't forget to apply all canvas transformations to the hitContext canvas as well(!)
        this.canvas.save();
        if (this.hitDetection) { this.hitContext.save(); }
        
        // Step 3: place symbol at the desired location
        this.canvas.translate(p0,p1);
        if (this.hitDetection) { this.hitContext.translate(p0,p1); }
        
        // Step 2a. rotate the symbol if necessary
        angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined.
        if (!isNaN(angle)) {
            this.canvas.rotate(angle);
            if (this.hitDetection) { this.hitContext.rotate(angle); }
        }
                
        // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension.
        scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight());
        this.canvas.scale(scaling,scaling);
        if (this.hitDetection) { this.hitContext.scale(scaling,scaling); }
        
        // Step 1: center the symbol at the origin        
        cx = symbolBounds.getCenterLonLat().lon;
        cy = symbolBounds.getCenterLonLat().lat;
        this.canvas.translate(-cx,-cy);
        if (this.hitDetection) { this.hitContext.translate(-cx,-cy); }        

        // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!)
        // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore.
        unscaledStrokeWidth = style.strokeWidth;
        style.strokeWidth = unscaledStrokeWidth / scaling;
            
        if (style.fill !== false) {
            this.setCanvasStyle("fill", style);
            this.canvas.beginPath();
            for (i=0; i<symbol.length; i=i+2) {
                x = symbol[i];
                y = symbol[i+1];
                if (i == 0) this.canvas.moveTo(x,y);
                this.canvas.lineTo(x,y);
            }
            this.canvas.closePath();
            this.canvas.fill();

            if (this.hitDetection) {
                this.setHitContextStyle("fill", featureId, style);
                this.hitContext.beginPath();
                for (i=0; i<symbol.length; i=i+2) {
                    x = symbol[i];
                    y = symbol[i+1];
                    if (i == 0) this.canvas.moveTo(x,y);
                    this.hitContext.lineTo(x,y);
                }
                this.hitContext.closePath();
                this.hitContext.fill();
            }
        }  
        
        if (style.stroke !== false) {
            this.setCanvasStyle("stroke", style);
            this.canvas.beginPath();
            for (i=0; i<symbol.length; i=i+2) {
                x = symbol[i];
                y = symbol[i+1];
                if (i == 0) this.canvas.moveTo(x,y);
                this.canvas.lineTo(x,y);
            }
            this.canvas.closePath();
            this.canvas.stroke();
            
            
            if (this.hitDetection) {
                this.setHitContextStyle("stroke", featureId, style, scaling);
                this.hitContext.beginPath();
                for (i=0; i<symbol.length; i=i+2) {
                    x = symbol[i];
                    y = symbol[i+1];
                    if (i == 0) this.hitContext.moveTo(x,y);
                    this.hitContext.lineTo(x,y);
                }
                this.hitContext.closePath();
                this.hitContext.stroke();
            }
            
        }
        
        style.strokeWidth = unscaledStrokeWidth;
        this.canvas.restore();
        if (this.hitDetection) { this.hitContext.restore(); }
        this.setCanvasStyle("reset");  
    },

    /**
     * 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: featureIdToHex
     * Convert a feature ID string into an RGB hex string.
     *
     * Parameters:
     * featureId - {String} Feature id
     *
     * Returns:
     * {String} RGB hex string.
     */
    featureIdToHex: function(featureId) {
        var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
        if (id >= 16777216) {
            this.hitOverflow = id - 16777215;
            id = id % 16777216 + 1;
        }
        var hex = "000000" + id.toString(16);
        var len = hex.length;
        hex = "#" + hex.substring(len-6, len);
        return hex;
    },
    
    /**
     * Method: setHitContextStyle
     * Prepare the hit canvas for drawing by setting various global settings.
     *
     * Parameters:
     * type - {String} one of 'stroke', 'fill', or 'reset'
     * featureId - {String} The feature id.
     * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
     */
    setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) {
        var hex = this.featureIdToHex(featureId);
        if (type == "fill") {
            this.hitContext.globalAlpha = 1.0;
            this.hitContext.fillStyle = hex;
        } else if (type == "stroke") {  
            this.hitContext.globalAlpha = 1.0;
            this.hitContext.strokeStyle = hex;
            // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol 
            // on a transformed canvas, so the antialias width bump has to scale as well.
            if (typeof strokeScaling === "undefined") {
                this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
            } else {
                if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; }
            }
        } else {
            this.hitContext.globalAlpha = 0;
            this.hitContext.lineWidth = 1;
        }
    },

    /**
     * Method: drawPoint
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * geometry - {<OpenLayers.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */ 
    drawPoint: function(geometry, style, featureId) {
        if(style.graphic !== false) {
            if(style.externalGraphic) {
                this.drawExternalGraphic(geometry, style, featureId);
            } else if (style.graphicName && (style.graphicName != "circle")) {
                this.drawNamedSymbol(geometry, style, featureId);
            } else {
                var pt = this.getLocalXY(geometry);
                var p0 = pt[0];
                var p1 = pt[1];
                if(!isNaN(p0) && !isNaN(p1)) {
                    var twoPi = Math.PI*2;
                    var radius = style.pointRadius;
                    if(style.fill !== false) {
                        this.setCanvasStyle("fill", style);
                        this.canvas.beginPath();
                        this.canvas.arc(p0, p1, radius, 0, twoPi, true);
                        this.canvas.fill();
                        if (this.hitDetection) {
                            this.setHitContextStyle("fill", featureId, style);
                            this.hitContext.beginPath();
                            this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
                            this.hitContext.fill();
                        }
                    }

                    if(style.stroke !== false) {
                        this.setCanvasStyle("stroke", style);
                        this.canvas.beginPath();
                        this.canvas.arc(p0, p1, radius, 0, twoPi, true);
                        this.canvas.stroke();
                        if (this.hitDetection) {
                            this.setHitContextStyle("stroke", featureId, style);
                            this.hitContext.beginPath();
                            this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
                            this.hitContext.stroke();
                        }
                        this.setCanvasStyle("reset");
                    }
                }
            }
        }
    },
    
    /**
     * Method: drawLineString
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * geometry - {<OpenLayers.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */ 
    drawLineString: function(geometry, style, featureId) {
        style = OpenLayers.Util.applyDefaults({fill: false}, style);
        this.drawLinearRing(geometry, style, featureId);
    },    
    
    /**
     * Method: drawLinearRing
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * geometry - {<OpenLayers.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */ 
    drawLinearRing: function(geometry, style, featureId) {
        if (style.fill !== false) {
            this.setCanvasStyle("fill", style);
            this.renderPath(this.canvas, geometry, style, featureId, "fill");
            if (this.hitDetection) {
                this.setHitContextStyle("fill", featureId, style);
                this.renderPath(this.hitContext, geometry, style, featureId, "fill");
            }
        }
        if (style.stroke !== false) {
            this.setCanvasStyle("stroke", style);
            this.renderPath(this.canvas, geometry, style, featureId, "stroke");
            if (this.hitDetection) {
                this.setHitContextStyle("stroke", featureId, style);
                this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
            }
        }
        this.setCanvasStyle("reset");
    },
    
    /**
     * Method: renderPath
     * Render a path with stroke and optional fill.
     */
    renderPath: function(context, geometry, style, featureId, type) {
        var components = geometry.components;
        var len = components.length;
        context.beginPath();
        var start = this.getLocalXY(components[0]);
        var x = start[0];
        var y = start[1];
        if (!isNaN(x) && !isNaN(y)) {
            context.moveTo(start[0], start[1]);
            for (var i=1; i<len; ++i) {
                var pt = this.getLocalXY(components[i]);
                context.lineTo(pt[0], pt[1]);
            }
            if (type === "fill") {
                context.fill();
            } else {
                context.stroke();
            }
        }
    },
    
    /**
     * Method: drawPolygon
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * geometry - {<OpenLayers.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */ 
    drawPolygon: function(geometry, style, featureId) {
        var components = geometry.components;
        var len = components.length;
        this.drawLinearRing(components[0], style, featureId);
        // erase inner rings
        for (var i=1; i<len; ++i) {
            /** 
             * Note that this is overly agressive.  Here we punch holes through 
             * all previously rendered features on the same canvas.  A better 
             * solution for polygons with interior rings would be to draw the 
             * polygon on a sketch canvas first.  We could erase all holes 
             * there and then copy the drawing to the layer canvas. 
             * TODO: http://trac.osgeo.org/openlayers/ticket/3130 
             */
            this.canvas.globalCompositeOperation = "destination-out";
            if (this.hitDetection) {
                this.hitContext.globalCompositeOperation = "destination-out";
            }
            this.drawLinearRing(
                components[i], 
                OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
                featureId
            );
            this.canvas.globalCompositeOperation = "source-over";
            if (this.hitDetection) {
                this.hitContext.globalCompositeOperation = "source-over";
            }
            this.drawLinearRing(
                components[i], 
                OpenLayers.Util.applyDefaults({fill: false}, style),
                featureId
            );
        }
    },
    
    /**
     * Method: drawText
     * This method is only called by the renderer itself.
     *
     * Parameters:
     * location - {<OpenLayers.Point>}
     * style    - {Object}
     */
    drawText: function(location, style) {
        var pt = this.getLocalXY(location);

        this.setCanvasStyle("reset");
        this.canvas.fillStyle = style.fontColor;
        this.canvas.globalAlpha = style.fontOpacity || 1.0;
        var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
                         "normal", // "font-variant" not supported
                         style.fontWeight ? style.fontWeight : "normal",
                         style.fontSize ? style.fontSize : "1em",
                         style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
        var labelRows = style.label.split('\n');
        var numRows = labelRows.length;
        if (this.canvas.fillText) {
            // HTML5
            this.canvas.font = fontStyle;
            this.canvas.textAlign =
                OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
                "center";
            this.canvas.textBaseline =
                OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
                "middle";
            var vfactor =
                OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
            if (vfactor == null) {
                vfactor = -.5;
            }
            var lineHeight =
                this.canvas.measureText('Mg').height ||
                this.canvas.measureText('xx').width;
            pt[1] += lineHeight*vfactor*(numRows-1);
            for (var i = 0; i < numRows; i++) {
                if (style.labelOutlineWidth) {
                    this.canvas.save();
                    this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0;
                    this.canvas.strokeStyle = style.labelOutlineColor;
                    this.canvas.lineWidth = style.labelOutlineWidth;
                    this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1);
                    this.canvas.restore();
                }
                this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
            }
        } else if (this.canvas.mozDrawText) {
            // Mozilla pre-Gecko1.9.1 (<FF3.1)
            this.canvas.mozTextStyle = fontStyle;
            // No built-in text alignment, so we measure and adjust the position
            var hfactor =
                OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
            if (hfactor == null) {
                hfactor = -.5;
            }
            var vfactor =
                OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
            if (vfactor == null) {
                vfactor = -.5;
            }
            var lineHeight = this.canvas.mozMeasureText('xx');
            pt[1] += lineHeight*(1 + (vfactor*numRows));
            for (var i = 0; i < numRows; i++) {
                var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
                var y = pt[1] + (i*lineHeight);
                this.canvas.translate(x, y);
                this.canvas.mozDrawText(labelRows[i]);
                this.canvas.translate(-x, -y);
            }
        }
        this.setCanvasStyle("reset");
    },
    
    /**
     * 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 - this.featureDx) / resolution + (-extent.left / resolution));
        var y = ((extent.top / resolution) - point.y / resolution);
        return [x, y];
    },

    /**
     * Method: clear
     * Clear all vectors from the renderer.
     */    
    clear: function() {
        var height = this.root.height;
        var width = this.root.width;
        this.canvas.clearRect(0, 0, width, height);
        this.features = {};
        if (this.hitDetection) {
            this.hitContext.clearRect(0, 0, width, height);
        }
    },

    /**
     * Method: getFeatureIdFromEvent
     * Returns a feature id from an event on the renderer.  
     * 
     * Parameters:
     * evt - {<OpenLayers.Event>} 
     *
     * Returns:
     * {<OpenLayers.Feature.Vector} A feature or undefined.  This method returns a 
     *     feature instead of a feature id to avoid an unnecessary lookup on the
     *     layer.
     */
    getFeatureIdFromEvent: function(evt) {
        var featureId, feature;
        
        if (this.hitDetection && this.root.style.display !== "none") {
            // this dragging check should go in the feature handler
            if (!this.map.dragging) {
                var xy = evt.xy;
                var x = xy.x | 0;
                var y = xy.y | 0;
                var data = this.hitContext.getImageData(x, y, 1, 1).data;
                if (data[3] === 255) { // antialiased
                    var id = data[2] + (256 * (data[1] + (256 * data[0])));
                    if (id) {
                        featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
                        try {
                            feature = this.features[featureId][0];
                        } catch(err) {
                            // Because of antialiasing on the canvas, when the hit location is at a point where the edge of
                            // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
                            // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
                        }
                    }
                }
            }
        }
        return feature;
    },
    
    /**
     * 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(!(OpenLayers.Util.isArray(features))) {
            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) {
            var height = this.root.height;
            var width = this.root.width;
            this.canvas.clearRect(0, 0, width, height);
            if (this.hitDetection) {
                this.hitContext.clearRect(0, 0, width, height);
            }
            var labelMap = [];
            var feature, geometry, style;
            var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
            for (var id in this.features) {
                if (!this.features.hasOwnProperty(id)) { continue; }
                feature = this.features[id][0];
                geometry = feature.geometry;
                this.calculateFeatureDx(geometry.getBounds(), worldBounds);
                style = this.features[id][1];
                this.drawGeometry(geometry, style, feature.id);
                if(style.label) {
                    labelMap.push([feature, style]);
                }
            }
            var item;
            for (var i=0, len=labelMap.length; i<len; ++i) {
                item = labelMap[i];
                this.drawText(item[0].geometry.getCentroid(), item[1]);
            }
        }    
    },

    CLASS_NAME: "OpenLayers.Renderer.Canvas"
});

/**
 * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
 * {Object}
 */
OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
    "l": "left",
    "r": "right",
    "t": "top",
    "b": "bottom"
};

/**
 * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
 * {Object}
 */
OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
    "l": 0,
    "r": -1,
    "t": 0,
    "b": -1
};

/**
 * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
 * {Number} Scale factor to apply to the canvas drawImage arguments. This
 *     is always 1 except for Android 2.1 devices, to work around
 *     http://code.google.com/p/android/issues/detail?id=5141.
 */
OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
/* ======================================================================
    OpenLayers/Handler.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Events.js
 */

/**
 * Class: OpenLayers.Handler
 * Base class to construct a higher-level handler for event sequences.  All
 *     handlers have activate and deactivate methods.  In addition, they have
 *     methods named like browser events.  When a handler is activated, any
 *     additional methods named like a browser event is registered as a
 *     listener for the corresponding event.  When a handler is deactivated,
 *     those same methods are unregistered as event listeners.
 *
 * Handlers also typically have a callbacks object with keys named like
 *     the abstracted events or event sequences that they are in charge of
 *     handling.  The controls that wrap handlers define the methods that
 *     correspond to these abstract events - so instead of listening for
 *     individual browser events, they only listen for the abstract events
 *     defined by the handler.
 *     
 * Handlers are created by controls, which ultimately have the responsibility
 *     of making changes to the the state of the application.  Handlers
 *     themselves may make temporary changes, but in general are expected to
 *     return the application in the same state that they found it.
 */
OpenLayers.Handler = OpenLayers.Class({

    /**
     * Property: id
     * {String}
     */
    id: null,
        
    /**
     * APIProperty: 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.
     */
    control: null,

    /**
     * Property: map
     * {<OpenLayers.Map>}
     */
    map: null,

    /**
     * APIProperty: keyMask
     * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler
     *     constants to construct a keyMask.  The keyMask is used by
     *     <checkModifiers>.  If the keyMask matches the combination of keys
     *     down on an event, checkModifiers returns true.
     *
     * Example:
     * (code)
     *     // handler only responds if the Shift key is down
     *     handler.keyMask = OpenLayers.Handler.MOD_SHIFT;
     *
     *     // handler only responds if Ctrl-Shift is down
     *     handler.keyMask = OpenLayers.Handler.MOD_SHIFT |
     *                       OpenLayers.Handler.MOD_CTRL;
     * (end)
     */
    keyMask: null,

    /**
     * Property: active
     * {Boolean}
     */
    active: false,
    
    /**
     * Property: evt
     * {Event} This property references the last event handled by the handler.
     *     Note that this property is not part of the stable API.  Use of the
     *     evt property should be restricted to controls in the library
     *     or other applications that are willing to update with changes to
     *     the OpenLayers code.
     */
    evt: null,
    
    /**
     * Property: touch
     * {Boolean} Indicates the support of touch events. When touch events are 
     *     started touch will be true and all mouse related listeners will do 
     *     nothing.
     */
    touch: false,

    /**
     * Constructor: OpenLayers.Handler
     * Construct a 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.  If a map property
     *     is present in the options argument it will be used instead.
     * 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.Util.extend(this, options);
        this.control = control;
        this.callbacks = callbacks;

        var map = this.map || control.map;
        if (map) {
            this.setMap(map); 
        }
        
        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
    },
    
    /**
     * Method: setMap
     */
    setMap: function (map) {
        this.map = map;
    },

    /**
     * Method: checkModifiers
     * Check the keyMask on the handler.  If no <keyMask> is set, this always
     *     returns true.  If a <keyMask> is set and it matches the combination
     *     of keys down on an event, this returns true.
     *
     * Returns:
     * {Boolean} The keyMask matches the keys down on an event.
     */
    checkModifiers: function (evt) {
        if(this.keyMask == null) {
            return true;
        }
        /* calculate the keyboard modifier mask for this event */
        var keyModifiers =
            (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
            (evt.ctrlKey  ? OpenLayers.Handler.MOD_CTRL  : 0) |
            (evt.altKey   ? OpenLayers.Handler.MOD_ALT   : 0) |
            (evt.metaKey  ? OpenLayers.Handler.MOD_META  : 0);
    
        /* if it differs from the handler object's key mask,
           bail out of the event handler */
        return (keyModifiers == this.keyMask);
    },

    /**
     * APIMethod: activate
     * Turn on the handler.  Returns false if the handler was already active.
     * 
     * Returns: 
     * {Boolean} The handler was activated.
     */
    activate: function() {
        if(this.active) {
            return false;
        }
        // register for event handlers defined on this class.
        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;
    },
    
    /**
     * APIMethod: deactivate
     * Turn off the handler.  Returns false if the handler was already inactive.
     * 
     * Returns:
     * {Boolean} The handler was deactivated.
     */
    deactivate: function() {
        if(!this.active) {
            return false;
        }
        // unregister event handlers defined on this class.
        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.touch = false;
        this.active = false;
        return true;
    },

    /**
     * Method: startTouch
     * Start touch events, this method must be called by subclasses in 
     *     "touchstart" method. When touch events are started <touch> will be
     *     true and all mouse related listeners will do nothing.
     */
    startTouch: function() {
        if (!this.touch) {
            this.touch = true;
            var events = [
                "mousedown", "mouseup", "mousemove", "click", "dblclick",
                "mouseout"
            ];
            for (var i=0, len=events.length; i<len; i++) {
                if (this[events[i]]) {
                    this.unregister(events[i], this[events[i]]); 
                }
            } 
        }
    },

    /**
    * 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 (any type) with which to call 
    *     the callback (defined by the control).
    */
    callback: function (name, args) {
        if (name && this.callbacks[name]) {
            this.callbacks[name].apply(this.control, args);
        }
    },

    /**
    * Method: register
    * register an event on the map
    */
    register: function (name, method) {
        // TODO: deal with registerPriority in 3.0
        this.map.events.registerPriority(name, this, method);
        this.map.events.registerPriority(name, this, this.setEvent);
    },

    /**
    * Method: unregister
    * unregister an event from the map
    */
    unregister: function (name, method) {
        this.map.events.unregister(name, this, method);   
        this.map.events.unregister(name, this, this.setEvent);
    },
    
    /**
     * Method: setEvent
     * With each registered browser event, the handler sets its own evt
     *     property.  This property can be accessed by controls if needed
     *     to get more information about the event that the handler is
     *     processing.
     *
     * This allows modifier keys on the event to be checked (alt, shift, ctrl,
     *     and meta cannot be checked with the keyboard handler).  For a
     *     control to determine which modifier keys are associated with the
     *     event that a handler is currently processing, it should access
     *     (code)handler.evt.altKey || handler.evt.shiftKey ||
     *     handler.evt.ctrlKey || handler.evt.metaKey(end).
     *
     * Parameters:
     * evt - {Event} The browser event.
     */
    setEvent: function(evt) {
        this.evt = evt;
        return true;
    },

    /**
     * Method: destroy
     * Deconstruct the handler.
     */
    destroy: function () {
        // unregister event listeners
        this.deactivate();
        // eliminate circular references
        this.control = this.map = null;        
    },

    CLASS_NAME: "OpenLayers.Handler"
});

/**
 * Constant: OpenLayers.Handler.MOD_NONE
 * If set as the <keyMask>, <checkModifiers> returns false if any key is down.
 */
OpenLayers.Handler.MOD_NONE  = 0;

/**
 * Constant: OpenLayers.Handler.MOD_SHIFT
 * If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
 */
OpenLayers.Handler.MOD_SHIFT = 1;

/**
 * Constant: OpenLayers.Handler.MOD_CTRL
 * If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
 */
OpenLayers.Handler.MOD_CTRL  = 2;

/**
 * Constant: OpenLayers.Handler.MOD_ALT
 * If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
 */
OpenLayers.Handler.MOD_ALT   = 4;

/**
 * Constant: OpenLayers.Handler.MOD_META
 * If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
 */
OpenLayers.Handler.MOD_META  = 8;


/* ======================================================================
    OpenLayers/Handler/MouseWheel.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler.js
 */

/**
 * Class: OpenLayers.Handler.MouseWheel
 * Handler for wheel up/down events.
 * 
 * Inherits from:
 *  - <OpenLayers.Handler>
 */
OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, {
    /** 
     * Property: wheelListener 
     * {function} 
     */
    wheelListener: null,

    /**
     * Property: interval
     * {Integer} In order to increase server performance, an interval (in 
     *     milliseconds) can be set to reduce the number of up/down events 
     *     called. If set, a new up/down event will not be set until the 
     *     interval has passed. 
     *     Defaults to 0, meaning no interval. 
     */
    interval: 0,
    
    /**
     * Property: maxDelta
     * {Integer} Maximum delta to collect before breaking from the current
     *    interval. In cumulative mode, this also limits the maximum delta
     *    returned from the handler. Default is Number.POSITIVE_INFINITY.
     */
    maxDelta: Number.POSITIVE_INFINITY,
    
    /**
     * Property: delta
     * {Integer} When interval is set, delta collects the mousewheel z-deltas
     *     of the events that occur within the interval.
     *      See also the cumulative option
     */
    delta: 0,
    
    /**
     * Property: cumulative
     * {Boolean} When interval is set: true to collect all the mousewheel 
     *     z-deltas, false to only record the delta direction (positive or
     *     negative)
     */
    cumulative: true,
    
    /**
     * Constructor: OpenLayers.Handler.MouseWheel
     *
     * Parameters:
     * control - {<OpenLayers.Control>} 
     * 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 point geometry.
     * options - {Object} 
     */
    initialize: function(control, callbacks, options) {
        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
        this.wheelListener = OpenLayers.Function.bindAsEventListener(
            this.onWheelEvent, this
        );
    },

    /**
     * Method: destroy
     */    
    destroy: function() {
        OpenLayers.Handler.prototype.destroy.apply(this, arguments);
        this.wheelListener = 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){
        
        // make sure we have a map and check keyboard modifiers
        if (!this.map || !this.checkModifiers(e)) {
            return;
        }
        
        // Ride up the element's DOM hierarchy to determine if it or any of 
        //  its ancestors was: 
        //   * specifically marked as scrollable (CSS overflow property)
        //   * one of our layer divs or a div marked as scrollable
        //     ('olScrollable' CSS class)
        //   * the map div
        //
        var overScrollableDiv = false;
        var allowScroll = false;
        var overMapDiv = false;
        
        var elem = OpenLayers.Event.element(e);
        while((elem != null) && !overMapDiv && !overScrollableDiv) {

            if (!overScrollableDiv) {
                try {
                    var overflow;
                    if (elem.currentStyle) {
                        overflow = elem.currentStyle["overflow"];
                    } else {
                        var style = 
                            document.defaultView.getComputedStyle(elem, null);
                        overflow = style.getPropertyValue("overflow");
                    }
                    overScrollableDiv = ( overflow && 
                        (overflow == "auto") || (overflow == "scroll") );
                } catch(err) {
                    //sometimes when scrolling in a popup, this causes 
                    // obscure browser error
                }
            }

            if (!allowScroll) {
                allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable');
                if (!allowScroll) {
                    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)
                        var layer = this.map.layers[i];
                        if (elem == layer.div || elem == layer.pane) {
                            allowScroll = true;
                            break;
                        }
                    }
                }
            }
            overMapDiv = (elem == this.map.div);

            elem = elem.parentNode;
        }
        
        // Logic below is the following:
        //
        // If we are over a scrollable div or not over the map div:
        //  * do nothing (let the browser handle scrolling)
        //
        //    otherwise 
        // 
        //    If we are over the layer div or a 'olScrollable' div:
        //     * zoom/in out
        //     then
        //     * kill event (so as not to also scroll the page after zooming)
        //
        //       otherwise
        //
        //       Kill the event (dont scroll the page if we wheel over the 
        //        layerswitcher or the pan/zoom control)
        //
        if (!overScrollableDiv && overMapDiv) {
            if (allowScroll) {
                var delta = 0;
                
                if (e.wheelDelta) {
                    delta = e.wheelDelta;
                    if (delta % 160 === 0) {
                        // opera have steps of 160 instead of 120
                        delta = delta * 0.75;
                    }
                    delta = delta / 120;
                } else if (e.detail) {
                    // detail in Firefox on OS X is 1/3 of Windows
                    // so force delta 1 / -1
                    delta = - (e.detail / Math.abs(e.detail));
                }
                this.delta += delta;

                window.clearTimeout(this._timeoutId);
                if(this.interval && Math.abs(this.delta) < this.maxDelta) {
                    // store e because window.event might change during delay
                    var evt = OpenLayers.Util.extend({}, e);
                    this._timeoutId = window.setTimeout(
                        OpenLayers.Function.bind(function(){
                            this.wheelZoom(evt);
                        }, this),
                        this.interval
                    );
                } else {
                    this.wheelZoom(e);
                }
            }
            OpenLayers.Event.stop(e);
        }
    },

    /**
     * Method: wheelZoom
     * Given the wheel event, we carry out the appropriate zooming in or out,
     *     based on the 'wheelDelta' or 'detail' property of the event.
     * 
     * Parameters:
     * e - {Event}
     */
    wheelZoom: function(e) {
        var delta = this.delta;
        this.delta = 0;
        
        if (delta) {
            e.xy = this.map.events.getMousePosition(e);
            if (delta < 0) {
                this.callback("down",
                    [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]);
            } else {
                this.callback("up",
                    [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]);
            }
        }
    },
    
    /**
     * Method: activate 
     */
    activate: function (evt) {
        if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
            //register mousewheel events specifically on the window and document
            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;
        }
    },

    /**
     * Method: deactivate 
     */
    deactivate: function (evt) {
        if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
            // unregister mousewheel events specifically on the window and document
            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/Format/JSON.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * 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,

    /**
     * Property: nativeJSON
     * {Boolean} Does the browser support native json?
     */
    nativeJSON: (function() {
        return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function");
    })(),

    /**
     * Constructor: OpenLayers.Format.JSON
     * Create a new parser for JSON.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */

    /**
     * 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) {
        var object;
        if (this.nativeJSON) {
            object = JSON.parse(json, filter);
        } else try {
            /**
             * 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.
             */
            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.
                 */
                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);
                }
            }
        } catch(e) {
            // Fall through if the regexp test fails.
        }

        if(this.keepData) {
            this.data = object;
        }

        return object;
    },

    /**
     * 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]) {
            try {
                json = (!this.pretty && this.nativeJSON) ?
                    JSON.stringify(value) :
                    this.serialize[type].apply(this, [value]);
            } catch(err) {
                OpenLayers.Console.error("Trouble serializing: " + err);
            }
        }
        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/GML/v2.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/GML/Base.js
 */

/**
 * Class: OpenLayers.Format.GML.v2
 * Parses GML version 2.
 *
 * Inherits from:
 *  - <OpenLayers.Format.GML.Base>
 */
OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
    
    /**
     * Property: schemaLocation
     * {String} Schema location for a particular minor version.
     */
    schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",

    /**
     * Constructor: OpenLayers.Format.GML.v2
     * Create a parser for GML v2.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     *
     * Valid options properties:
     * featureType - {String} Local (without prefix) feature typeName (required).
     * featureNS - {String} Feature namespace (required).
     * geometryName - {String} Geometry element name.
     */
    initialize: function(options) {
        OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
    },

    /**
     * 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: {
        "gml": OpenLayers.Util.applyDefaults({
            "outerBoundaryIs": function(node, container) {
                var obj = {};
                this.readChildNodes(node, obj);
                container.outer = obj.components[0];
            },
            "innerBoundaryIs": function(node, container) {
                var obj = {};
                this.readChildNodes(node, obj);
                container.inner.push(obj.components[0]);
            },
            "Box": function(node, container) {
                var obj = {};
                this.readChildNodes(node, obj);
                if(!container.components) {
                    container.components = [];
                }
                var min = obj.points[0];
                var max = obj.points[1];
                container.components.push(
                    new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
                );
            }
        }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
        "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
        "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
    },

    /**
     * Method: write
     *
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
     *     An array of features or a single feature.
     *
     * Returns:
     * {String} Given an array of features, a doc with a gml:featureMembers
     *     element will be returned.  Given a single feature, a doc with a
     *     gml:featureMember element will be returned.
     */
    write: function(features) {
        var name;
        if(OpenLayers.Util.isArray(features)) {
            // GML2 only has abstract feature collections
            // wfs provides a feature collection from a well-known schema
            name = "wfs:FeatureCollection";
        } else {
            name = "gml:featureMember";
        }
        var root = this.writeNode(name, features);
        this.setAttributeNS(
            root, this.namespaces["xsi"],
            "xsi:schemaLocation", this.schemaLocation
        );

        return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
    },

    /**
     * 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: {
        "gml": OpenLayers.Util.applyDefaults({
            "Point": function(geometry) {
                var node = this.createElementNSPlus("gml:Point");
                this.writeNode("coordinates", [geometry], node);
                return node;
            },
            "coordinates": function(points) {
                var numPoints = points.length;
                var parts = new Array(numPoints);
                var point;
                for(var i=0; i<numPoints; ++i) {
                    point = points[i];
                    if(this.xy) {
                        parts[i] = point.x + "," + point.y;
                    } else {
                        parts[i] = point.y + "," + point.x;
                    }
                    if(point.z != undefined) { // allow null or undefined
                        parts[i] += "," + point.z;
                    }
                }
                return this.createElementNSPlus("gml:coordinates", {
                    attributes: {
                        decimal: ".", cs: ",", ts: " "
                    },
                    value: (numPoints == 1) ? parts[0] : parts.join(" ")
                });
            },
            "LineString": function(geometry) {
                var node = this.createElementNSPlus("gml:LineString");
                this.writeNode("coordinates", geometry.components, node);
                return node;
            },
            "Polygon": function(geometry) {
                var node = this.createElementNSPlus("gml:Polygon");
                this.writeNode("outerBoundaryIs", geometry.components[0], node);
                for(var i=1; i<geometry.components.length; ++i) {
                    this.writeNode(
                        "innerBoundaryIs", geometry.components[i], node
                    );
                }
                return node;
            },
            "outerBoundaryIs": function(ring) {
                var node = this.createElementNSPlus("gml:outerBoundaryIs");
                this.writeNode("LinearRing", ring, node);
                return node;
            },
            "innerBoundaryIs": function(ring) {
                var node = this.createElementNSPlus("gml:innerBoundaryIs");
                this.writeNode("LinearRing", ring, node);
                return node;
            },
            "LinearRing": function(ring) {
                var node = this.createElementNSPlus("gml:LinearRing");
                this.writeNode("coordinates", ring.components, node);
                return node;
            },
            "Box": function(bounds) {
                var node = this.createElementNSPlus("gml:Box");
                this.writeNode("coordinates", [
                    {x: bounds.left, y: bounds.bottom},
                    {x: bounds.right, y: bounds.top}
                ], node);
                // srsName attribute is optional for gml:Box
                if(this.srsName) {
                    node.setAttribute("srsName", this.srsName);
                }
                return node;
            }
        }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
        "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
        "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
    },
    
    CLASS_NAME: "OpenLayers.Format.GML.v2" 

});
/* ======================================================================
    OpenLayers/Format/Filter/v1_0_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/GML/v2.js
 * @requires OpenLayers/Format/Filter/v1.js
 */

/**
 * Class: OpenLayers.Format.Filter.v1_0_0
 * Write ogc:Filter version 1.0.0.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.GML.v2>
 *  - <OpenLayers.Format.Filter.v1>
 */
OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
    OpenLayers.Format.GML.v2, 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.GML.v2.prototype.initialize.apply(
            this, [options]
        );
    },

    /**
     * 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": OpenLayers.Util.applyDefaults({
            "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);
            },
            "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);
            }
        }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
        "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
        "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"]        
    },

    /**
     * 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": OpenLayers.Util.applyDefaults({
            "PropertyIsEqualTo": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsNotEqualTo": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
                // no ogc:expression handling for PropertyName for now
                this.writeNode("PropertyName", filter, node);
                // handle Literals or Functions for now
                this.writeOgcExpression(filter.value, node);
                return node;
            },
            "PropertyIsLike": function(filter) {
                var node = this.createElementNSPlus("ogc:PropertyIsLike", {
                    attributes: {
                        wildCard: "*", singleChar: ".", escape: "!"
                    }
                });
                // no ogc:expression handling for now
                this.writeNode("PropertyName", filter, node);
                // convert regex string to ogc string
                this.writeNode("Literal", filter.regex2value(), node);
                return node;
            },
            "BBOX": function(filter) {
                var node = this.createElementNSPlus("ogc:BBOX");
                // PropertyName is mandatory in 1.0.0, but e.g. GeoServer also
                // accepts filters without it. When this is used with
                // OpenLayers.Protocol.WFS, OpenLayers.Format.WFST will set a
                // missing filter.property to the geometryName that is
                // configured with the protocol, which defaults to "the_geom".
                // So the only way to omit this mandatory property is to not
                // set the property on the filter and to set the geometryName
                // on the WFS protocol to null. The latter also happens when
                // the protocol is configured without a geometryName and a
                // featureNS.
                filter.property && this.writeNode("PropertyName", filter, node);
                var box = this.writeNode("gml:Box", filter.value, node);
                if(filter.projection) {
                    box.setAttribute("srsName", filter.projection);
                }
                return node;
            }
        }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
        "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
        "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"]
    },

    /**
     * Method: writeSpatial
     *
     * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
     *
     * Parameters:
     * filter - {<OpenLayers.Filter.Spatial>} The filter.
     * name - {String} Name of the generated XML element.
     *
     * Returns:
     * {DOMElement} The created XML element.
     */
    writeSpatial: function(filter, name) {
        var node = this.createElementNSPlus("ogc:"+name);
        this.writeNode("PropertyName", filter, node);
        if(filter.value instanceof OpenLayers.Filter.Function) {
            this.writeNode("Function", filter.value, node);
        } else {
        var child;
        if(filter.value instanceof OpenLayers.Geometry) {
            child = this.writeNode("feature:_geometry", filter.value).firstChild;
        } else {
            child = this.writeNode("gml:Box", filter.value);
        }
        if(filter.projection) {
            child.setAttribute("srsName", filter.projection);
        }
        node.appendChild(child);
        }
        return node;
    },


    CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0" 

});
/* ======================================================================
    OpenLayers/Format/WFST/v1_0_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/WFST/v1.js
 * @requires OpenLayers/Format/Filter/v1_0_0.js
 */

/**
 * Class: OpenLayers.Format.WFST.v1_0_0
 * A format for creating WFS v1.0.0 transactions.  Create a new instance with the
 *     <OpenLayers.Format.WFST.v1_0_0> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Format.Filter.v1_0_0>
 *  - <OpenLayers.Format.WFST.v1>
 */
OpenLayers.Format.WFST.v1_0_0 = OpenLayers.Class(
    OpenLayers.Format.Filter.v1_0_0, OpenLayers.Format.WFST.v1, {
    
    /**
     * Property: version
     * {String} WFS version number.
     */
    version: "1.0.0",

    /**
     * APIProperty: srsNameInQuery
     * {Boolean} If true the reference system is passed in Query requests
     *     via the "srsName" attribute to the "wfs:Query" element, this
     *     property defaults to false as it isn't WFS 1.0.0 compliant.
     */
    srsNameInQuery: false,
    
    /**
     * Property: schemaLocations
     * {Object} Properties are namespace aliases, values are schema locations.
     */
    schemaLocations: {
        "wfs": "http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"
    },

    /**
     * Constructor: OpenLayers.Format.WFST.v1_0_0
     * A class for parsing and generating WFS v1.0.0 transactions.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     *
     * Valid options properties:
     * featureType - {String} Local (without prefix) feature typeName (required).
     * featureNS - {String} Feature namespace (optional).
     * featurePrefix - {String} Feature namespace alias (optional - only used
     *     if featureNS is provided).  Default is 'feature'.
     * geometryName - {String} Name of geometry attribute.  Default is 'the_geom'.
     */
    initialize: function(options) {
        OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]);
        OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
    },
    
    /**
     * Method: readNode
     * Shorthand for applying one of the named readers given the node
     *     namespace and local name.  Readers take two args (node, obj) and
     *     generally extend or modify the second.
     *
     * Parameters:
     * node - {DOMElement} The node to be read (required).
     * obj - {Object} The object to be modified (optional).
     * first - {Boolean} Should be set to true for the first node read. This
     *     is usually the readNode call in the read method. Without this being
     *     set, auto-configured properties will stick on subsequent reads.
     *
     * Returns:
     * {Object} The input object, modified (or a new one if none was provided).
     */
    readNode: function(node, obj, first) {
        // Not the superclass, only the mixin classes inherit from
        // Format.GML.v2. We need this because we don't want to get readNode
        // from the superclass's superclass, which is OpenLayers.Format.XML.
        return OpenLayers.Format.GML.v2.prototype.readNode.apply(this, arguments);
    },
    
    /**
     * 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: {
        "wfs": OpenLayers.Util.applyDefaults({
            "WFS_TransactionResponse": function(node, obj) {
                obj.insertIds = [];
                obj.success = false;
                this.readChildNodes(node, obj);
            },
            "InsertResult": function(node, container) {
                var obj = {fids: []};
                this.readChildNodes(node, obj);
                container.insertIds = container.insertIds.concat(obj.fids);
            },
            "TransactionResult": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "Status": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "SUCCESS": function(node, obj) {
                obj.success = true;
            }
        }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
        "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
        "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"],
        "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]
    },

    /**
     * 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: {
        "wfs": OpenLayers.Util.applyDefaults({
            "Query": function(options) {
                options = OpenLayers.Util.extend({
                    featureNS: this.featureNS,
                    featurePrefix: this.featurePrefix,
                    featureType: this.featureType,
                    srsName: this.srsName,
                    srsNameInQuery: this.srsNameInQuery
                }, options);
                var prefix = options.featurePrefix;
                var node = this.createElementNSPlus("wfs:Query", {
                    attributes: {
                        typeName: (prefix ? prefix + ":" : "") +
                            options.featureType
                    }
                });
                if(options.srsNameInQuery && options.srsName) {
                    node.setAttribute("srsName", options.srsName);
                }
                if(options.featureNS) {
                    node.setAttribute("xmlns:" + prefix, options.featureNS);
                }
                if(options.propertyNames) {
                    for(var i=0,len = options.propertyNames.length; i<len; i++) {
                        this.writeNode(
                            "ogc:PropertyName", 
                            {property: options.propertyNames[i]},
                            node
                        );
                    }
                }
                if(options.filter) {
                    this.setFilterProperty(options.filter);
                    this.writeNode("ogc:Filter", options.filter, node);
                }
                return node;
            }
        }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
        "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
        "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"],
        "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]
    },
   
    CLASS_NAME: "OpenLayers.Format.WFST.v1_0_0" 
});
/* ======================================================================
    OpenLayers/Renderer/Elements.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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.clear();
    },
    
    /**
     * 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.
        return this.getNextElement(rightIndex);
    },
    
    /**
     * 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;
        }
    },

    /**
     * APIMethod: getNextElement
     * Get the next element in the order stack.
     * 
     * Parameters:
     * index - {Integer} The index of the current node in this.order.
     * 
     * Returns:
     * {DOMElement} the node following the index passed in, or
     *     null.
     */
    getNextElement: function(index) {
        var nextIndex = index + 1;
        if (nextIndex < this.order.length) {
            var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
            if (nextElement == undefined) {
                nextElement = this.getNextElement(nextIndex);
            }
            return nextElement;
        } else {
            return null;
        } 
    },
    
    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 result = nextNode._boundsBottom - newNode._boundsBottom;
            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: vectorRoot
     * {DOMElement}
     */
    vectorRoot: null,

    /**
     * Property: textRoot
     * {DOMElement}
     */
    textRoot: null,

    /**
     * Property: xmlns
     * {String}
     */    
    xmlns: null,
    
    /**
     * Property: xOffset
     * {Number} Offset to apply to the renderer viewport translation in x
     * direction. If the renderer extent's center is on the right of the
     * dateline (i.e. exceeds the world bounds), we shift the viewport to the
     * left by one world width. This avoids that features disappear from the
     * map viewport. Because our dateline handling logic in other places
     * ensures that extents crossing the dateline always have a center
     * exceeding the world bounds on the left, we need this offset to make sure
     * that the same is true for the renderer extent in pixel space as well.
     */
    xOffset: 0,
    
    /**
     * Property: rightOfDateLine
     * {Boolean} Keeps track of the location of the map extent relative to the
     * date line. The <setExtent> method compares this value (which is the one
     * from the previous <setExtent> call) with the current position of the map
     * extent relative to the date line and updates the xOffset when the extent
     * has moved from one side of the date line to the other.
     */
    
    /**
     * 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",
    
    /**
     * Constant: LABEL_ID_SUFFIX
     * {String}
     */
    LABEL_ID_SUFFIX: "_label",
    
    /**
     * Constant: LABEL_OUTLINE_SUFFIX
     * {String}
     */
    LABEL_OUTLINE_SUFFIX: "_outline",

    /**
     * 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("_root");
        this.vectorRoot = this.createRoot("_vroot");
        this.textRoot = this.createRoot("_troot");
        
        this.root.appendChild(this.vectorRoot);
        this.root.appendChild(this.textRoot);
        
        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() {
        var child;
        var root = this.vectorRoot;
        if (root) {
            while (child = root.firstChild) {
                root.removeChild(child);
            }
        }
        root = this.textRoot;
        if (root) {
            while (child = root.firstChild) {
                root.removeChild(child);
            }
        }
        if (this.indexer) {
            this.indexer.clear();
        }
    },
    
    /**
     * Method: setExtent
     * Set the visible part of the layer.
     *
     * 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) {
        var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
        var resolution = this.getResolution();
        if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
            var rightOfDateLine,
                ratio = extent.getWidth() / this.map.getExtent().getWidth(),
                extent = extent.scale(1 / ratio),
                world = this.map.getMaxExtent();
            if (world.right > extent.left && world.right < extent.right) {
                rightOfDateLine = true;
            } else if (world.left > extent.left && world.left < extent.right) {
                rightOfDateLine = false;
            }
            if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
                coordSysUnchanged = false;
                this.xOffset = rightOfDateLine === true ?
                    world.getWidth() / resolution : 0;
            }
            this.rightOfDateLine = rightOfDateLine;
        }
        return coordSysUnchanged;
    },

    /** 
     * 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 = this.drawGeometry(
                    geometry.components[i], style, featureId) && rendered;
            }
            return rendered;
        }

        rendered = false;
        var removeBackground = false;
        if (style.display != "none") {
            if (style.backgroundGraphic) {
                this.redrawBackgroundNode(geometry.id, geometry, style,
                    featureId);
            } else {
                removeBackground = true;
            }
            rendered = this.redrawNode(geometry.id, geometry, style,
                featureId);
        }
        if (rendered == false) {
            var node = document.getElementById(geometry.id);
            if (node) {
                if (node._style.backgroundGraphic) {
                    removeBackground = true;
                }
                node.parentNode.removeChild(node);
            }
        }
        if (removeBackground) {
            var node = document.getElementById(
                geometry.id + this.BACKGROUND_ID_SUFFIX);
            if (node) {
                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) {
        style = this.applyDefaultSymbolizer(style);
        // Get the node if it's already on the map.
        var node = this.nodeFactory(id, this.getNodeType(geometry, style));
        
        // Set the data for the node, then draw it.
        node._featureId = featureId;
        node._boundsBottom = geometry.getBounds().bottom;
        node._geometryClass = geometry.CLASS_NAME;
        node._style = style;

        var drawResult = this.drawGeometryNode(node, geometry, style);
        if(drawResult === false) {
            return false;
        }
         
        node = 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.
        if (this.indexer) {
            var insert = this.indexer.insert(node);
            if (insert) {
                this.vectorRoot.insertBefore(node, insert);
            } else {
                this.vectorRoot.appendChild(node);
            }
        } else {
            // if there's no indexer, simply append the node to root,
            // but only if the node is a new one
            if (node.parentNode !== this.vectorRoot){ 
                this.vectorRoot.appendChild(node);
            }
        }
        
        this.postDraw(node);
        
        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;
        backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
        backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
        
        // 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;

        var options = {
            'isFilled': style.fill === undefined ?
                true :
                style.fill,
            'isStroked': style.stroke === undefined ?
                !!style.strokeWidth :
                style.stroke
        };
        var drawn;
        switch (geometry.CLASS_NAME) {
            case "OpenLayers.Geometry.Point":
                if(style.graphic === false) {
                    options.isFilled = false;
                    options.isStroked = false;
                }
                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.Rectangle":
                drawn = this.drawRectangle(node, geometry);
                break;
            default:
                break;
        }

        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: removeText
     * Removes a label
     * 
     * Parameters:
     * featureId - {String}
     */
    removeText: function(featureId) {
        var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
        if (label) {
            this.textRoot.removeChild(label);
        }
        var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
        if (outline) {
            this.textRoot.removeChild(outline);
        }
    },

    /**
     * Method: getFeatureIdFromEvent
     * 
     * Parameters:
     * evt - {Object} An <OpenLayers.Event> object
     *
     * Returns:
     * {String} A feature id or undefined.
     */
    getFeatureIdFromEvent: function(evt) {
        var target = evt.target;
        var useElement = target && target.correspondingUseElement;
        var node = useElement ? useElement : (target || evt.srcElement);
        return node._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>}
     * featureId - {String}
     */
    eraseGeometry: function(geometry, featureId) {
        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], featureId);
            }
        } 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);
                }
                
                if (element._style.backgroundGraphic) {
                    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: nodeTypeCompare
     * 
     * Parameters:
     * node - {DOMElement}
     * type - {String} Kind of node
     * 
     * Returns:
     * {Boolean} Whether or not the specified node is of the specified type
     *     This function must be overridden by subclasses.
     */
    nodeTypeCompare: function(node, type) {},
    
    /** 
     * 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: moveRoot
     * moves this renderer's root to a different renderer.
     * 
     * Parameters:
     * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
     */
    moveRoot: function(renderer) {
        var root = this.root;
        if(renderer.root.parentNode == this.rendererRoot) {
            root = renderer.root;
        }
        root.parentNode.removeChild(root);
        renderer.rendererRoot.appendChild(root);
    },
    
    /**
     * Method: getRenderLayerId
     * Gets the layer that this renderer's output appears on. If moveRoot was
     * used, this will be different from the id of the layer containing the
     * features rendered by this renderer.
     * 
     * Returns:
     * {String} the id of the output layer.
     */
    getRenderLayerId: function() {
        return this.root.parentNode.parentNode.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"
});

/* ======================================================================
    OpenLayers/Strategy/Fixed.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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, {
    
    /**
     * APIProperty: preload
     * {Boolean} Load data before layer made visible. Enabling this may result
     *   in considerable overhead if your application loads many data layers
     *   that are not visible by default. Default is false.
     */
    preload: false,

    /**
     * Constructor: OpenLayers.Strategy.Fixed
     * Create a new Fixed strategy.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     */

    /**
     * Method: activate
     * Activate the strategy: load data or add listener to load when visible
     *
     * Returns:
     * {Boolean} True if the strategy was successfully activated or false if
     *      the strategy was already active.
     */
    activate: function() {
        var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
        if(activated) {
            this.layer.events.on({
                "refresh": this.load,
                scope: this
            });
            if(this.layer.visibility == true || this.preload) {
                this.load();
            } else {
                this.layer.events.on({
                    "visibilitychanged": this.load,
                    scope: this
                });
            }
        }
        return activated;
    },
    
    /**
     * Method: deactivate
     * Deactivate the strategy.  Undo what is done in <activate>.
     * 
     * Returns:
     * {Boolean} The strategy was successfully deactivated.
     */
    deactivate: function() {
        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
        if(deactivated) {
            this.layer.events.un({
                "refresh": this.load,
                "visibilitychanged": this.load,
                scope: this
            });
        }
        return deactivated;
    },

    /**
     * Method: load
     * Tells protocol to load data and unhooks the visibilitychanged event
     *
     * Parameters:
     * options - {Object} options to pass to protocol read.
     */
    load: function(options) {
        var layer = this.layer;
        layer.events.triggerEvent("loadstart", {filter: layer.filter});
        layer.protocol.read(OpenLayers.Util.applyDefaults({
            callback: this.merge,
            filter: layer.filter,
            scope: this
        }, options));
        layer.events.un({
            "visibilitychanged": this.load,
            scope: this
        });
    },

    /**
     * Method: merge
     * Add all features to the layer.
     *     If the layer projection differs from the map projection, features
     *     will be transformed from the layer projection to the map projection.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>} The response object passed
     *      by the protocol.
     */
    merge: function(resp) {
        var layer = this.layer;
        layer.destroyFeatures();
        var features = resp.features;
        if (features && features.length > 0) {
            var remote = layer.projection;
            var local = layer.map.getProjectionObject();
            if(!local.equals(remote)) {
                var geom;
                for(var i=0, len=features.length; i<len; ++i) {
                    geom = features[i].geometry;
                    if(geom) {
                        geom.transform(remote, local);
                    }
                }
            }
            layer.addFeatures(features);
        }
        layer.events.triggerEvent("loadend", {response: resp});
    },

    CLASS_NAME: "OpenLayers.Strategy.Fixed"
});
/* ======================================================================
    OpenLayers/Format/WFSCapabilities.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML/VersionedOGC.js
 */

/**
 * Class: OpenLayers.Format.WFSCapabilities
 * Read WFS Capabilities.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.XML.VersionedOGC>
 */
OpenLayers.Format.WFSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
    
    /**
     * APIProperty: defaultVersion
     * {String} Version number to assume if none found.  Default is "1.1.0".
     */
    defaultVersion: "1.1.0",

    /**
     * Constructor: OpenLayers.Format.WFSCapabilities
     * Create a new parser for WFS capabilities.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */

    /**
     * APIMethod: 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.
     */
    
    CLASS_NAME: "OpenLayers.Format.WFSCapabilities" 

});
/* ======================================================================
    OpenLayers/Format/WFSCapabilities/v1.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/WFSCapabilities.js
 */

/**
 * Class: OpenLayers.Format.WFSCapabilities.v1
 * Abstract class not to be instantiated directly.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
OpenLayers.Format.WFSCapabilities.v1 = OpenLayers.Class(
    OpenLayers.Format.XML, {

    /**
     * Property: namespaces
     * {Object} Mapping of namespace aliases to namespace URIs.
     */
    namespaces: {
        wfs: "http://www.opengis.net/wfs",
        xlink: "http://www.w3.org/1999/xlink",
        xsi: "http://www.w3.org/2001/XMLSchema-instance",
        ows: "http://www.opengis.net/ows"
    },


    /**
     * APIProperty: errorProperty
     * {String} Which property of the returned object to check for in order to
     * determine whether or not parsing has failed. In the case that the
     * errorProperty is undefined on the returned object, the document will be
     * run through an OGCExceptionReport parser.
     */
    errorProperty: "featureTypeList",

    /**
     * Property: defaultPrefix
     */
    defaultPrefix: "wfs",
    
    /**
     * Constructor: OpenLayers.Format.WFSCapabilities.v1_1
     * Create an instance of one of the subclasses.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */

    /**
     * APIMethod: 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 raw = data;
        if(data && data.nodeType == 9) {
            data = data.documentElement;
        }
        var capabilities = {};
        this.readNode(data, capabilities);
        return capabilities;
    },

    /**
     * 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: {
        "wfs": {
            "WFS_Capabilities": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "FeatureTypeList": function(node, request) {
                request.featureTypeList = {
                    featureTypes: []
                };
                this.readChildNodes(node, request.featureTypeList);
            },
            "FeatureType": function(node, featureTypeList) {
                var featureType = {};
                this.readChildNodes(node, featureType);
                featureTypeList.featureTypes.push(featureType);
            },
            "Name": function(node, obj) {
                var name = this.getChildValue(node);
                if(name) {
                    var parts = name.split(":");
                    obj.name = parts.pop();
                    if(parts.length > 0) {
                        obj.featureNS = this.lookupNamespaceURI(node, parts[0]);
                    }
                }
            },
            "Title": function(node, obj) {
                var title = this.getChildValue(node);
                if(title) {
                    obj.title = title;
                }
            },
            "Abstract": function(node, obj) {
                var abst = this.getChildValue(node);
                if(abst) {
                    obj["abstract"] = abst;
                }
            }
        }
    },

    CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1" 

});
/* ======================================================================
    OpenLayers/Format/WFSCapabilities/v1_1_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/WFSCapabilities/v1.js
 * @requires OpenLayers/Format/OWSCommon/v1.js
 */

/**
 * Class: OpenLayers.Format.WFSCapabilities/v1_1_0
 * Read WFS Capabilities version 1.1.0.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.WFSCapabilities>
 */
OpenLayers.Format.WFSCapabilities.v1_1_0 = OpenLayers.Class(
    OpenLayers.Format.WFSCapabilities.v1, {

    /**
     * Property: regExes
     * Compiled regular expressions for manipulating strings.
     */
    regExes: {
        trimSpace: (/^\s*|\s*$/g),
        removeSpace: (/\s*/g),
        splitSpace: (/\s+/),
        trimComma: (/\s*,\s*/g)
    },
    
    /**
     * Constructor: OpenLayers.Format.WFSCapabilities.v1_1_0
     * Create a new parser for WFS capabilities version 1.1.0.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */

    /**
     * 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: {
        "wfs": OpenLayers.Util.applyDefaults({
            "DefaultSRS": function(node, obj) {
                var defaultSRS = this.getChildValue(node);
                if (defaultSRS) {
                    obj.srs = defaultSRS;
                }
            }
        }, OpenLayers.Format.WFSCapabilities.v1.prototype.readers["wfs"]),
        "ows": OpenLayers.Format.OWSCommon.v1.prototype.readers.ows
    },

    CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1_1_0" 

});
/* ======================================================================
    OpenLayers/Protocol.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.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>} The format used by this protocol.
     */
    format: null,
    
    /**
     * Property: options
     * {Object} 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,
   
    /**
     * Property: defaultFilter
     * {<OpenLayers.Filter>} Optional default filter to read requests
     */
    defaultFilter: null,
    
    /**
     * 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;
    },

    /**
     * Method: mergeWithDefaultFilter
     * Merge filter passed to the read method with the default one
     *
     * Parameters:
     * filter - {<OpenLayers.Filter>}
     */
    mergeWithDefaultFilter: function(filter) {
        var merged;
        if (filter && this.defaultFilter) {
            merged = new OpenLayers.Filter.Logical({
                type: OpenLayers.Filter.Logical.AND,
                filters: [this.defaultFilter, filter]
            });
        } else {
            merged = filter || this.defaultFilter || undefined;
        }
        return merged;
    },

    /**
     * APIMethod: destroy
     * Clean up the protocol.
     */
    destroy: function() {
        this.options = null;
        this.format = null;
    },
    
    /**
     * APIMethod: 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(options) {
        options = options || {};
        options.filter = this.mergeWithDefaultFilter(options.filter);
    },
    
    
    /**
     * APIMethod: 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() {
    },
    
    /**
     * APIMethod: 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() {
    },
    
    /**
     * APIMethod: 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() {
    },

    /**
     * APIMethod: 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() {
    },

    /**
     * Method: abort
     * Abort an ongoing request.
     *
     * Parameters:
     * response - {<OpenLayers.Protocol.Response>}
     */
    abort: function(response) {
    },
   
    /**
     * Method: createCallback
     * Returns a function that applies the given public method with resp and
     *     options arguments.
     *
     * Parameters:
     * method - {Function} The method to be applied by the callback.
     * response - {<OpenLayers.Protocol.Response>} The protocol response object.
     * options - {Object} Options sent to the protocol method
     */
    createCallback: function(method, response, options) {
        return OpenLayers.Function.bind(function() {
            method.apply(this, [response, options]);
        }, this);
    },
   
    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. Depending on the 
     * protocol's read payload, either features or data will be populated.
     */
    features: null,

    /**
     * Property: data
     * {Object}
     * The data returned in the response by the server. Depending on the 
     * protocol's read payload, either features or data will be populated.
     */
    data: 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,

    /**
     * Property: error
     * {Object} The error object in case a service exception was encountered.
     */
    error: 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/Protocol/WFS.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Protocol.js
 */

/**
 * Class: OpenLayers.Protocol.WFS
 * Used to create a versioned WFS protocol.  Default version is 1.0.0.
 *
 * Returns:
 * {<OpenLayers.Protocol>} A WFS protocol of the given version.
 *
 * Example:
 * (code)
 *     var protocol = new OpenLayers.Protocol.WFS({
 *         version: "1.1.0",
 *         url:  "http://demo.opengeo.org/geoserver/wfs",
 *         featureType: "tasmania_roads",
 *         featureNS: "http://www.openplans.org/topp",
 *         geometryName: "the_geom"
 *     });
 * (end)
 *
 * See the protocols for specific WFS versions for more detail.
 */
OpenLayers.Protocol.WFS = function(options) {
    options = OpenLayers.Util.applyDefaults(
        options, OpenLayers.Protocol.WFS.DEFAULTS
    );
    var cls = OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g, "_")];
    if(!cls) {
        throw "Unsupported WFS version: " + options.version;
    }
    return new cls(options);
};

/**
 * Function: fromWMSLayer
 * Convenience function to create a WFS protocol from a WMS layer.  This makes
 *     the assumption that a WFS requests can be issued at the same URL as
 *     WMS requests and that a WFS featureType exists with the same name as the
 *     WMS layer.
 *     
 * This function is designed to auto-configure <url>, <featureType>,
 *     <featurePrefix> and <srsName> for WFS <version> 1.1.0. Note that
 *     srsName matching with the WMS layer will not work with WFS 1.0.0.
 * 
 * Parameters:
 * layer - {<OpenLayers.Layer.WMS>} WMS layer that has a matching WFS
 *     FeatureType at the same server url with the same typename.
 * options - {Object} Default properties to be set on the protocol.
 *
 * Returns:
 * {<OpenLayers.Protocol.WFS>}
 */
OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) {
    var typeName, featurePrefix;
    var param = layer.params["LAYERS"];
    var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":");
    if(parts.length > 1) {
        featurePrefix = parts[0];
    }
    typeName = parts.pop();
    var protocolOptions = {
        url: layer.url,
        featureType: typeName,
        featurePrefix: featurePrefix,
        srsName: layer.projection && layer.projection.getCode() ||
                 layer.map && layer.map.getProjectionObject().getCode(),
        version: "1.1.0"
    };
    return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(
        options, protocolOptions
    ));
};

/**
 * Constant: OpenLayers.Protocol.WFS.DEFAULTS
 */
OpenLayers.Protocol.WFS.DEFAULTS = {
    "version": "1.0.0"
};
/* ======================================================================
    OpenLayers/Layer/Markers.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/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,
    
    /** 
     * APIProperty: 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.
     * 
     * Parameters:
     * 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 < 1) {
            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);
            marker.erase();
        }
    },

    /**
     * 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 {
            if (!marker.isDrawn()) {
                var markerImg = marker.draw(px);
                this.div.appendChild(markerImg);
            } else if(marker.icon) {
                marker.icon.moveTo(px);
            }
        }
    },
    
    /** 
     * 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/Protocol/WFS/v1.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Protocol/WFS.js
 */

/**
 * Class: OpenLayers.Protocol.WFS.v1
 * Abstract class for for v1.0.0 and v1.1.0 protocol.
 *
 * Inherits from:
 *  - <OpenLayers.Protocol>
 */
OpenLayers.Protocol.WFS.v1 = OpenLayers.Class(OpenLayers.Protocol, {
    
    /**
     * Property: version
     * {String} WFS version number.
     */
    version: null,
    
    /**
     * Property: srsName
     * {String} Name of spatial reference system.  Default is "EPSG:4326".
     */
    srsName: "EPSG:4326",
    
    /**
     * Property: featureType
     * {String} Local feature typeName.
     */
    featureType: null,
    
    /**
     * Property: featureNS
     * {String} Feature namespace.
     */
    featureNS: null,
    
    /**
     * Property: geometryName
     * {String} Name of the geometry attribute for features.  Default is
     *     "the_geom" for WFS <version> 1.0, and null for higher versions.
     */
    geometryName: "the_geom",

    /**
     * Property: maxFeatures
     * {Integer} Optional maximum number of features to retrieve.
     */
    
    /**
     * Property: schema
     * {String} Optional schema location that will be included in the
     *     schemaLocation attribute value.  Note that the feature type schema
     *     is required for a strict XML validator (on transactions with an
     *     insert for example), but is *not* required by the WFS specification
     *     (since the server is supposed to know about feature type schemas).
     */
    schema: null,

    /**
     * Property: featurePrefix
     * {String} Namespace alias for feature type.  Default is "feature".
     */
    featurePrefix: "feature",
    
    /**
     * Property: formatOptions
     * {Object} Optional options for the format.  If a format is not provided,
     *     this property can be used to extend the default format options.
     */
    formatOptions: null,

    /** 
     * Property: readFormat 
     * {<OpenLayers.Format>} For WFS requests it is possible to get a  
     *     different output format than GML. In that case, we cannot parse  
     *     the response with the default format (WFST) and we need a different 
     *     format for reading. 
     */ 
    readFormat: null,
    
    /**
     * Property: readOptions
     * {Object} Optional object to pass to format's read.
     */
    readOptions: null,
    
    /**
     * Constructor: OpenLayers.Protocol.WFS
     * A class for giving layers WFS protocol.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     *
     * Valid options properties:
     * url - {String} URL to send requests to (required).
     * featureType - {String} Local (without prefix) feature typeName (required).
     * featureNS - {String} Feature namespace (required, but can be autodetected
     *     during the first query if GML is used as readFormat and
     *     featurePrefix is provided and matches the prefix used by the server
     *     for this featureType).
     * featurePrefix - {String} Feature namespace alias (optional - only used
     *     for writing if featureNS is provided).  Default is 'feature'.
     * geometryName - {String} Name of geometry attribute.  The default is
     *     'the_geom' for WFS <version> 1.0, and null for higher versions. If
     *     null, it will be set to the name of the first geometry found in the
     *     first read operation.
     * multi - {Boolean} If set to true, geometries will be casted to Multi
     *     geometries before they are written in a transaction. No casting will
     *     be done when reading features.
     */
    initialize: function(options) {
        OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
        if(!options.format) {
            this.format = OpenLayers.Format.WFST(OpenLayers.Util.extend({
                version: this.version,
                featureType: this.featureType,
                featureNS: this.featureNS,
                featurePrefix: this.featurePrefix,
                geometryName: this.geometryName,
                srsName: this.srsName,
                schema: this.schema
            }, this.formatOptions));
        }
        if (!options.geometryName && parseFloat(this.format.version) > 1.0) {
            this.setGeometryName(null);
        }
    },
    
    /**
     * APIMethod: destroy
     * Clean up the protocol.
     */
    destroy: function() {
        if(this.options && !this.options.format) {
            this.format.destroy();
        }
        this.format = null;
        OpenLayers.Protocol.prototype.destroy.apply(this);
    },

    /**
     * APIMethod: read
     * Construct a request for reading new features.  Since WFS splits the
     *     basic CRUD operations into GetFeature requests (for read) and
     *     Transactions (for all others), this method does not make use of the
     *     format's read method (that is only about reading transaction
     *     responses).
     *
     * Parameters:
     * options - {Object} Options for the read operation, in addition to the
     *     options set on the instance (options set here will take precedence).
     *
     * To use a configured protocol to get e.g. a WFS hit count, applications
     * could do the following:
     *
     * (code)
     * protocol.read({
     *     readOptions: {output: "object"},
     *     resultType: "hits",
     *     maxFeatures: null,
     *     callback: function(resp) {
     *         // process resp.numberOfFeatures here
     *     }
     * });
     * (end)
     *
     * To use a configured protocol to use WFS paging (if supported by the
     * server), applications could do the following:
     *
     * (code)
     * protocol.read({
     *     startIndex: 0,
     *     count: 50
     * });
     * (end)
     *
     * To limit the attributes returned by the GetFeature request, applications
     * can use the propertyNames option to specify the properties to include in
     * the response:
     *
     * (code)
     * protocol.read({
     *     propertyNames: ["DURATION", "INTENSITY"]
     * });
     * (end)
     */
    read: function(options) {
        OpenLayers.Protocol.prototype.read.apply(this, arguments);
        options = OpenLayers.Util.extend({}, options);
        OpenLayers.Util.applyDefaults(options, this.options || {});
        var response = new OpenLayers.Protocol.Response({requestType: "read"});
        
        var data = OpenLayers.Format.XML.prototype.write.apply(
            this.format, [this.format.writeNode("wfs:GetFeature", options)]
        );

        response.priv = OpenLayers.Request.POST({
            url: options.url,
            callback: this.createCallback(this.handleRead, response, options),
            params: options.params,
            headers: options.headers,
            data: data
        });

        return response;
    },

    /**
     * APIMethod: setFeatureType
     * Change the feature type on the fly.
     *
     * Parameters:
     * featureType - {String} Local (without prefix) feature typeName.
     */
    setFeatureType: function(featureType) {
        this.featureType = featureType;
        this.format.featureType = featureType;
    },
 
    /**
     * APIMethod: setGeometryName
     * Sets the geometryName option after instantiation.
     *
     * Parameters:
     * geometryName - {String} Name of geometry attribute.
     */
    setGeometryName: function(geometryName) {
        this.geometryName = geometryName;
        this.format.geometryName = geometryName;
    },
    
    /**
     * Method: handleRead
     * Deal with response from the read request.
     *
     * Parameters:
     * response - {<OpenLayers.Protocol.Response>} The response object to pass
     *     to the user callback.
     * options - {Object} The user options passed to the read call.
     */
    handleRead: function(response, options) {
        options = OpenLayers.Util.extend({}, options);
        OpenLayers.Util.applyDefaults(options, this.options);

        if(options.callback) {
            var request = response.priv;
            if(request.status >= 200 && request.status < 300) {
                // success
                var result = this.parseResponse(request, options.readOptions);
                if (result && result.success !== false) { 
                    if (options.readOptions && options.readOptions.output == "object") {
                        OpenLayers.Util.extend(response, result);
                    } else {
                        response.features = result;
                    }
                    response.code = OpenLayers.Protocol.Response.SUCCESS;
                } else {
                    // failure (service exception)
                    response.code = OpenLayers.Protocol.Response.FAILURE;
                    response.error = result;
                }
            } else {
                // failure
                response.code = OpenLayers.Protocol.Response.FAILURE;
            }
            options.callback.call(options.scope, response);
        }
    },

    /**
     * Method: parseResponse
     * Read HTTP response body and return features
     *
     * Parameters:
     * request - {XMLHttpRequest} The request object
     * options - {Object} Optional object to pass to format's read
     *
     * Returns:
     * {Object} or {Array({<OpenLayers.Feature.Vector>})} or
     *     {<OpenLayers.Feature.Vector>} 
     * An object with a features property, an array of features or a single 
     * feature.
     */
    parseResponse: function(request, options) {
        var doc = request.responseXML;
        if(!doc || !doc.documentElement) {
            doc = request.responseText;
        }
        if(!doc || doc.length <= 0) {
            return null;
        }
        var result = (this.readFormat !== null) ? this.readFormat.read(doc) : 
            this.format.read(doc, options);
        if (!this.featureNS) {
            var format = this.readFormat || this.format;
            this.featureNS = format.featureNS;
            // no need to auto-configure again on subsequent reads
            format.autoConfig = false;
            if (!this.geometryName) {
                this.setGeometryName(format.geometryName);
            }
        }
        return result;
    },

    /**
     * Method: commit
     * Given a list of feature, assemble a batch request for update, create,
     *     and delete transactions.  A commit call on the prototype amounts
     *     to writing a WFS transaction - so the write method on the format
     *     is used.
     *
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>)}
     * options - {Object}
     *
     * Valid options properties:
     * nativeElements - {Array({Object})} Array of objects with information for writing
     * out <Native> elements, these objects have vendorId, safeToIgnore and
     * value properties. The <Native> element is intended to allow access to 
     * vendor specific capabilities of any particular web feature server or 
     * datastore.
     *
     * Returns:
     * {<OpenLayers.Protocol.Response>} A response object with a features
     *     property containing any insertIds and a priv property referencing
     *     the XMLHttpRequest object.
     */
    commit: function(features, options) {

        options = OpenLayers.Util.extend({}, options);
        OpenLayers.Util.applyDefaults(options, this.options);
        
        var response = new OpenLayers.Protocol.Response({
            requestType: "commit",
            reqFeatures: features
        });
        response.priv = OpenLayers.Request.POST({
            url: options.url,
            headers: options.headers,
            data: this.format.write(features, options),
            callback: this.createCallback(this.handleCommit, response, options)
        });
        
        return response;
    },
    
    /**
     * Method: handleCommit
     * Called when the commit request returns.
     * 
     * Parameters:
     * response - {<OpenLayers.Protocol.Response>} The response object to pass
     *     to the user callback.
     * options - {Object} The user options passed to the commit call.
     */
    handleCommit: function(response, options) {
        if(options.callback) {
            var request = response.priv;

            // ensure that we have an xml doc
            var data = request.responseXML;
            if(!data || !data.documentElement) {
                data = request.responseText;
            }
            
            var obj = this.format.read(data) || {};
            
            response.insertIds = obj.insertIds || [];
            if (obj.success) {
                response.code = OpenLayers.Protocol.Response.SUCCESS;
            } else {
                response.code = OpenLayers.Protocol.Response.FAILURE;
                response.error = obj;
            }
            options.callback.call(options.scope, response);
        }
    },
    
    /**
     * Method: filterDelete
     * Send a request that deletes all features by their filter.
     * 
     * Parameters:
     * filter - {<OpenLayers.Filter>} filter
     */
    filterDelete: function(filter, options) {
        options = OpenLayers.Util.extend({}, options);
        OpenLayers.Util.applyDefaults(options, this.options);    
        
        var response = new OpenLayers.Protocol.Response({
            requestType: "commit"
        });    
        
        var root = this.format.createElementNSPlus("wfs:Transaction", {
            attributes: {
                service: "WFS",
                version: this.version
            }
        });
        
        var deleteNode = this.format.createElementNSPlus("wfs:Delete", {
            attributes: {
                typeName: (options.featureNS ? this.featurePrefix + ":" : "") +
                    options.featureType
            }
        });       
        
        if(options.featureNS) {
            deleteNode.setAttribute("xmlns:" + this.featurePrefix, options.featureNS);
        }
        var filterNode = this.format.writeNode("ogc:Filter", filter);
        
        deleteNode.appendChild(filterNode);
        
        root.appendChild(deleteNode);
        
        var data = OpenLayers.Format.XML.prototype.write.apply(
            this.format, [root]
        );
        
        return OpenLayers.Request.POST({
            url: this.url,
            callback : options.callback || function(){},
            data: data
        });   
        
    },

    /**
     * Method: abort
     * Abort an ongoing request, the response object passed to
     * this method must come from this protocol (as a result
     * of a read, or commit operation).
     *
     * Parameters:
     * response - {<OpenLayers.Protocol.Response>}
     */
    abort: function(response) {
        if (response) {
            response.priv.abort();
        }
    },
  
    CLASS_NAME: "OpenLayers.Protocol.WFS.v1" 
});
/* ======================================================================
    OpenLayers/Handler/Point.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Handler.js
 * @requires OpenLayers/Geometry/Point.js
 */

/**
 * Class: OpenLayers.Handler.Point
 * Handler to draw a point on the map. Point is displayed on activation,
 *     moves on mouse move, and is finished on mouse up. The handler triggers
 *     callbacks for 'done', 'cancel', and 'modify'. The modify callback is
 *     called with each change in the sketch and will receive the latest point
 *     drawn.  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,
    
    /**
     * APIProperty: multi
     * {Boolean} Cast features to multi-part geometries before passing to the
     *     layer.  Default is false.
     */
    multi: false,
    
    /**
     * APIProperty: citeCompliant
     * {Boolean} If set to true, coordinates of features drawn in a map extent
     * crossing the date line won't exceed the world bounds. Default is false.
     */
    citeCompliant: false,
    
    /**
     * Property: mouseDown
     * {Boolean} The mouse is down
     */
    mouseDown: false,

    /**
     * Property: stoppedDown
     * {Boolean} Indicate whether the last mousedown stopped the event
     * propagation.
     */
    stoppedDown: null,

    /**
     * Property: lastDown
     * {<OpenLayers.Pixel>} Location of the last mouse down
     */
    lastDown: null,

    /**
     * Property: lastUp
     * {<OpenLayers.Pixel>}
     */
    lastUp: null,

    /**
     * APIProperty: persist
     * {Boolean} Leave the feature rendered until destroyFeature is called.
     *     Default is false.  If set to true, the feature remains rendered until
     *     destroyFeature is called, typically by deactivating the handler or
     *     starting another drawing.
     */
    persist: false,

    /**
     * APIProperty: stopDown
     * {Boolean} Stop event propagation on mousedown. Must be false to
     *     allow "pan while drawing". Defaults to false.
     */
    stopDown: false,

    /**
     * APIPropery: stopUp
     * {Boolean} Stop event propagation on mouse. Must be false to
     *     allow "pan while dragging". Defaults to fase.
     */
    stopUp: false,

    /**
     * Property: layerOptions
     * {Object} Any optional properties to be set on the sketch layer.
     */
    layerOptions: null,
    
    /**
     * APIProperty: pixelTolerance
     * {Number} Maximum number of pixels between down and up (mousedown
     *     and mouseup, or touchstart and touchend) for the handler to
     *     add a new point. If set to an integer value, if the
     *     displacement between down and up is great to this value
     *     no point will be added. Default value is 5.
     */
    pixelTolerance: 5,

    /**
     * Property: lastTouchPx
     * {<OpenLayers.Pixel>} The last pixel used to know the distance between
     * two touches (for double touch).
     */
    lastTouchPx: 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 properties whose values are
     *     functions.  Various callbacks described below.
     * options - {Object} An optional object with properties to be set on the
     *           handler
     *
     * Named callbacks:
     * create - Called when a sketch is first created.  Callback called with
     *     the creation point geometry and sketch feature.
     * modify - Called with each move of a vertex with the vertex (point)
     *     geometry and the sketch feature.
     * done - Called when the point drawing is finished.  The callback will
     *     recieve a single argument, the point geometry.
     * cancel - Called when the handler is deactivated while drawing.  The
     *     cancel callback will receive a geometry.
     */
    initialize: function(control, callbacks, options) {
        if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
            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 = OpenLayers.Util.extend({
            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: OpenLayers.Function.True,
            wrapDateLine: this.citeCompliant
        }, this.layerOptions);
        this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
        this.map.addLayer(this.layer);
        return true;
    },
    
    /**
     * Method: createFeature
     * Add temporary features
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
     */
    createFeature: function(pixel) {
        var lonlat = this.layer.getLonLatFromViewPortPx(pixel); 
        var geometry = new OpenLayers.Geometry.Point(
            lonlat.lon, lonlat.lat
        );
        this.point = new OpenLayers.Feature.Vector(geometry);
        this.callback("create", [this.point.geometry, this.point]);
        this.point.geometry.clearBounds();
        this.layer.addFeatures([this.point], {silent: true});
    },

    /**
     * APIMethod: deactivate
     * turn off the handler
     */
    deactivate: function() {
        if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
            return false;
        }
        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.destroyFeature(true);
            this.layer.destroy(false);
        }
        this.layer = null;
        return true;
    },
    
    /**
     * Method: destroyFeature
     * Destroy the temporary geometries
     *
     * Parameters:
     * force - {Boolean} Destroy even if persist is true.
     */
    destroyFeature: function(force) {
        if(this.layer && (force || !this.persist)) {
            this.layer.destroyFeatures();
        }
        this.point = null;
    },

    /**
     * Method: destroyPersistedFeature
     * Destroy the persisted feature.
     */
    destroyPersistedFeature: function() {
        var layer = this.layer;
        if(layer && layer.features.length > 1) {
            this.layer.features[0].destroy();
        }
    },

    /**
     * Method: finalize
     * Finish the geometry and call the "done" callback.
     *
     * Parameters:
     * cancel - {Boolean} Call cancel instead of done callback.  Default
     *          is false.
     */
    finalize: function(cancel) {
        var key = cancel ? "cancel" : "done";
        this.mouseDown = false;
        this.lastDown = null;
        this.lastUp = null;
        this.lastTouchPx = null;
        this.callback(key, [this.geometryClone()]);
        this.destroyFeature(cancel);
    },

    /**
     * APIMethod: cancel
     * Finish the geometry and call the "cancel" callback.
     */
    cancel: function() {
        this.finalize(true);
    },

    /**
     * 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: modifyFeature
     * Modify the existing geometry given a pixel location.
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
     */
    modifyFeature: function(pixel) {
        if(!this.point) {
            this.createFeature(pixel);
        }
        var lonlat = this.layer.getLonLatFromViewPortPx(pixel); 
        this.point.geometry.x = lonlat.lon;
        this.point.geometry.y = lonlat.lat;
        this.callback("modify", [this.point.geometry, this.point, false]);
        this.point.geometry.clearBounds();
        this.drawFeature();
    },

    /**
     * Method: drawFeature
     * Render features on the temporary layer.
     */
    drawFeature: function() {
        this.layer.drawFeature(this.point, this.style);
    },
    
    /**
     * Method: getGeometry
     * Return the sketch geometry.  If <multi> is true, this will return
     *     a multi-part geometry.
     *
     * Returns:
     * {<OpenLayers.Geometry.Point>}
     */
    getGeometry: function() {
        var geometry = this.point && this.point.geometry;
        if(geometry && this.multi) {
            geometry = new OpenLayers.Geometry.MultiPoint([geometry]);
        }
        return geometry;
    },

    /**
     * Method: geometryClone
     * Return a clone of the relevant geometry.
     *
     * Returns:
     * {<OpenLayers.Geometry>}
     */
    geometryClone: function() {
        var geom = this.getGeometry();
        return geom && geom.clone();
    },

    /**
     * Method: mousedown
     * Handle mousedown.
     * 
     * Parameters:
     * evt - {Event} The browser event
     *
     * Returns: 
     * {Boolean} Allow event propagation
     */
    mousedown: function(evt) {
        return this.down(evt);
    },

    /**
     * Method: touchstart
     * Handle touchstart.
     * 
     * Parameters:
     * evt - {Event} The browser event
     *
     * Returns: 
     * {Boolean} Allow event propagation
     */
    touchstart: function(evt) {
        this.startTouch();
        this.lastTouchPx = evt.xy;
        return this.down(evt);
    },

    /**
     * Method: mousemove
     * Handle mousemove.
     * 
     * Parameters:
     * evt - {Event} The browser event
     *
     * Returns: 
     * {Boolean} Allow event propagation
     */
    mousemove: function(evt) {
        return this.move(evt);
    },

    /**
     * Method: touchmove
     * Handle touchmove.
     * 
     * Parameters:
     * evt - {Event} The browser event
     *
     * Returns: 
     * {Boolean} Allow event propagation
     */
    touchmove: function(evt) {
        this.lastTouchPx = evt.xy;
        return this.move(evt);
    },

    /**
     * Method: mouseup
     * Handle mouseup.
     * 
     * Parameters:
     * evt - {Event} The browser event
     *
     * Returns: 
     * {Boolean} Allow event propagation
     */
    mouseup: function(evt) {
        return this.up(evt);
    },

    /**
     * Method: touchend
     * Handle touchend.
     * 
     * Parameters:
     * evt - {Event} The browser event
     *
     * Returns: 
     * {Boolean} Allow event propagation
     */
    touchend: function(evt) {
        evt.xy = this.lastTouchPx;
        return this.up(evt);
    },
  
    /**
     * Method: down
     * Handle mousedown and touchstart.  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
     */
    down: function(evt) {
        this.mouseDown = true;
        this.lastDown = evt.xy;
        if(!this.touch) { // no point displayed until up on touch devices
            this.modifyFeature(evt.xy);
        }
        this.stoppedDown = this.stopDown;
        return !this.stopDown;
    },

    /**
     * Method: move
     * Handle mousemove and touchmove.  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
     */
    move: function (evt) {
        if(!this.touch // no point displayed until up on touch devices
           && (!this.mouseDown || this.stoppedDown)) {
            this.modifyFeature(evt.xy);
        }
        return true;
    },

    /**
     * Method: up
     * Handle mouseup and touchend.  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
     */
    up: function (evt) {
        this.mouseDown = false;
        this.stoppedDown = this.stopDown;

        // check keyboard modifiers
        if(!this.checkModifiers(evt)) {
            return true;
        }
        // ignore double-clicks
        if (this.lastUp && this.lastUp.equals(evt.xy)) {
            return true;
        }
        if (this.lastDown && this.passesTolerance(this.lastDown, evt.xy,
                                                  this.pixelTolerance)) {
            if (this.touch) {
                this.modifyFeature(evt.xy);
            }
            if(this.persist) {
                this.destroyPersistedFeature();
            }
            this.lastUp = evt.xy;
            this.finalize();
            return !this.stopUp;
        } else {
            return true;
        }
    },

    /**
     * Method: mouseout
     * Handle mouse out.  For better user experience reset mouseDown
     * and stoppedDown when the mouse leaves the map viewport.
     *
     * Parameters:
     * evt - {Event} The browser event
     */
    mouseout: function(evt) {
        if(OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
            this.stoppedDown = this.stopDown;
            this.mouseDown = false;
        }
    },

    /**
     * Method: passesTolerance
     * Determine whether the event is within the optional pixel tolerance.
     *
     * Returns:
     * {Boolean} The event is within the pixel tolerance (if specified).
     */
    passesTolerance: function(pixel1, pixel2, tolerance) {
        var passes = true;

        if (tolerance != null && pixel1 && pixel2) {
            var dist = pixel1.distanceTo(pixel2);
            if (dist > tolerance) {
                passes = false;
            }
        }
        return passes;
    },
    
    CLASS_NAME: "OpenLayers.Handler.Point"
});
/* ======================================================================
    OpenLayers/Handler/Path.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/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,

    /**
     * APIProperty: maxVertices
     * {Number} The maximum number of vertices which can be drawn by this
     * handler. When the number of vertices reaches maxVertices, the
     * geometry is automatically finalized. Default is null.
     */
    maxVertices: null,

    /**
     * Property: doubleTouchTolerance
     * {Number} Maximum number of pixels between two touches for
     *     the gesture to be considered a "finalize feature" action.
     *     Default is 20.
     */
    doubleTouchTolerance: 20,

    /**
     * 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',

    /**
     * Property: timerId
     * {Integer} The timer used to test the double touch.
     */
    timerId: null,

    /**
     * Property: redoStack
     * {Array} Stack containing points removed with <undo>.
     */
    redoStack: null,

    /**
     * Constructor: OpenLayers.Handler.Path
     * Create a new path hander
     *
     * Parameters:
     * control - {<OpenLayers.Control>} The control that owns this handler
     * callbacks - {Object} An object with a properties whose values are
     *     functions.  Various callbacks described below.
     * options - {Object} An optional object with properties to be set on the
     *           handler
     *
     * Named callbacks:
     * create - Called when a sketch is first created.  Callback called with
     *     the creation point geometry and sketch feature.
     * modify - Called with each move of a vertex with the vertex (point)
     *     geometry and the sketch feature.
     * point - Called as each point is added.  Receives the new point geometry.
     * done - Called when the point drawing is finished.  The callback will
     *     recieve a single argument, the linestring geometry.
     * cancel - Called when the handler is deactivated while drawing.  The
     *     cancel callback will receive a geometry.
     */

    /**
     * Method: createFeature
     * Add temporary geometries
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
     *     feature.
     */
    createFeature: function(pixel) {
        var lonlat = this.layer.getLonLatFromViewPortPx(pixel); 
        var geometry = new OpenLayers.Geometry.Point(
            lonlat.lon, lonlat.lat
        );
        this.point = new OpenLayers.Feature.Vector(geometry);
        this.line = new OpenLayers.Feature.Vector(
            new OpenLayers.Geometry.LineString([this.point.geometry])
        );
        this.callback("create", [this.point.geometry, this.getSketch()]);
        this.point.geometry.clearBounds();
        this.layer.addFeatures([this.line, this.point], {silent: true});
    },
        
    /**
     * Method: destroyFeature
     * Destroy temporary geometries
     *
     * Parameters:
     * force - {Boolean} Destroy even if persist is true.
     */
    destroyFeature: function(force) {
        OpenLayers.Handler.Point.prototype.destroyFeature.call(
            this, force);
        this.line = null;
    },

    /**
     * Method: destroyPersistedFeature
     * Destroy the persisted feature.
     */
    destroyPersistedFeature: function() {
        var layer = this.layer;
        if(layer && layer.features.length > 2) {
            this.layer.features[0].destroy();
        }
    },

    /**
     * Method: removePoint
     * Destroy the temporary point.
     */
    removePoint: function() {
        if(this.point) {
            this.layer.removeFeatures([this.point]);
        }
    },
    
    /**
     * Method: addPoint
     * Add point to geometry.  Send the point index to override
     * the behavior of LinearRing that disregards adding duplicate points.
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
     */
    addPoint: function(pixel) {
        this.layer.removeFeatures([this.point]);
        var lonlat = this.layer.getLonLatFromViewPortPx(pixel); 
        this.point = new OpenLayers.Feature.Vector(
            new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
        );
        this.line.geometry.addComponent(
            this.point.geometry, this.line.geometry.components.length
        );
        this.layer.addFeatures([this.point]);
        this.callback("point", [this.point.geometry, this.getGeometry()]);
        this.callback("modify", [this.point.geometry, this.getSketch()]);
        this.drawFeature();
        delete this.redoStack;
    },
    
    /**
     * Method: insertXY
     * Insert a point in the current sketch given x & y coordinates.  The new
     *     point is inserted immediately before the most recently drawn point.
     *
     * Parameters:
     * x - {Number} The x-coordinate of the point.
     * y - {Number} The y-coordinate of the point.
     */
    insertXY: function(x, y) {
        this.line.geometry.addComponent(
            new OpenLayers.Geometry.Point(x, y), 
            this.getCurrentPointIndex()
        );
        this.drawFeature();
        delete this.redoStack;
    },

    /**
     * Method: insertDeltaXY
     * Insert a point given offsets from the previously inserted point.
     *
     * Parameters:
     * dx - {Number} The x-coordinate offset of the point.
     * dy - {Number} The y-coordinate offset of the point.
     */
    insertDeltaXY: function(dx, dy) {
        var previousIndex = this.getCurrentPointIndex() - 1;
        var p0 = this.line.geometry.components[previousIndex];
        if (p0 && !isNaN(p0.x) && !isNaN(p0.y)) {
            this.insertXY(p0.x + dx, p0.y + dy);
        }
    },

    /**
     * Method: insertDirectionLength
     * Insert a point in the current sketch given a direction and a length.
     *
     * Parameters:
     * direction - {Number} Degrees clockwise from the positive x-axis.
     * length - {Number} Distance from the previously drawn point.
     */
    insertDirectionLength: function(direction, length) {
        direction *= Math.PI / 180;
        var dx = length * Math.cos(direction);
        var dy = length * Math.sin(direction);
        this.insertDeltaXY(dx, dy);
    },

    /**
     * Method: insertDeflectionLength
     * Insert a point in the current sketch given a deflection and a length.
     *     The deflection should be degrees clockwise from the previously 
     *     digitized segment.
     *
     * Parameters:
     * deflection - {Number} Degrees clockwise from the previous segment.
     * length - {Number} Distance from the previously drawn point.
     */
    insertDeflectionLength: function(deflection, length) {
        var previousIndex = this.getCurrentPointIndex() - 1;
        if (previousIndex > 0) {
            var p1 = this.line.geometry.components[previousIndex];
            var p0 = this.line.geometry.components[previousIndex-1];
            var theta = Math.atan2(p1.y - p0.y, p1.x - p0.x);
            this.insertDirectionLength(
                (theta * 180 / Math.PI) + deflection, length
            );
        }
    },

    /**
     * Method: getCurrentPointIndex
     * 
     * Returns:
     * {Number} The index of the most recently drawn point.
     */
    getCurrentPointIndex: function() {
        return this.line.geometry.components.length - 1;
    },
    
    
    /**
     * Method: undo
     * Remove the most recently added point in the sketch geometry.
     *
     * Returns: 
     * {Boolean} A point was removed.
     */
    undo: function() {
        var geometry = this.line.geometry;
        var components = geometry.components;
        var index = this.getCurrentPointIndex() - 1;
        var target = components[index];
        var undone = geometry.removeComponent(target);
        if (undone) {
            // On touch devices, set the current ("mouse location") point to
            // match the last digitized point.
            if (this.touch && index > 0) {
                components = geometry.components; // safety
                var lastpt = components[index - 1];
                var curptidx = this.getCurrentPointIndex();
                var curpt = components[curptidx];
                curpt.x = lastpt.x;
                curpt.y = lastpt.y;
            }
            if (!this.redoStack) {
                this.redoStack = [];
            }
            this.redoStack.push(target);
            this.drawFeature();
        }
        return undone;
    },
    
    /**
     * Method: redo
     * Reinsert the most recently removed point resulting from an <undo> call.
     *     The undo stack is deleted whenever a point is added by other means.
     *
     * Returns: 
     * {Boolean} A point was added.
     */
    redo: function() {
        var target = this.redoStack && this.redoStack.pop();
        if (target) {
            this.line.geometry.addComponent(target, this.getCurrentPointIndex());
            this.drawFeature();
        }
        return !!target;
    },
    
    /**
     * 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
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} The updated pixel location for the latest
     *     point.
     * drawing - {Boolean} Indicate if we're currently drawing.
     */
    modifyFeature: function(pixel, drawing) {
        if(!this.line) {
            this.createFeature(pixel);
        }
        var lonlat = this.layer.getLonLatFromViewPortPx(pixel); 
        this.point.geometry.x = lonlat.lon;
        this.point.geometry.y = lonlat.lat;
        this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
        this.point.geometry.clearBounds();
        this.drawFeature();
    },

    /**
     * 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: getSketch
     * Return the sketch feature.
     *
     * Returns:
     * {<OpenLayers.Feature.Vector>}
     */
    getSketch: function() {
        return this.line;
    },

    /**
     * Method: getGeometry
     * Return the sketch geometry.  If <multi> is true, this will return
     *     a multi-part geometry.
     *
     * Returns:
     * {<OpenLayers.Geometry.LineString>}
     */
    getGeometry: function() {
        var geometry = this.line && this.line.geometry;
        if(geometry && this.multi) {
            geometry = new OpenLayers.Geometry.MultiLineString([geometry]);
        }
        return geometry;
    },

    /**
     * method: touchstart
     * handle touchstart.
     *
     * parameters:
     * evt - {event} the browser event
     *
     * returns:
     * {boolean} allow event propagation
     */
    touchstart: function(evt) {
        if (this.timerId &&
            this.passesTolerance(this.lastTouchPx, evt.xy,
                                 this.doubleTouchTolerance)) {
            // double-tap, finalize the geometry
            this.finishGeometry();
            window.clearTimeout(this.timerId);
            this.timerId = null;
            return false;
        } else {
            if (this.timerId) {
                window.clearTimeout(this.timerId);
                this.timerId = null;
            }
            this.timerId = window.setTimeout(
                OpenLayers.Function.bind(function() {
                    this.timerId = null;
                }, this), 300);
            return OpenLayers.Handler.Point.prototype.touchstart.call(this, evt);
        }
    },

    /**
     * Method: down
     * Handle mousedown and touchstart.  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
     */
    down: function(evt) {
        var stopDown = this.stopDown;
        if(this.freehandMode(evt)) {
            stopDown = true;
            if (this.touch) {
                this.modifyFeature(evt.xy, !!this.lastUp);
                OpenLayers.Event.stop(evt);
            }
        }
        if (!this.touch && (!this.lastDown ||
                            !this.passesTolerance(this.lastDown, evt.xy,
                                                  this.pixelTolerance))) {
            this.modifyFeature(evt.xy, !!this.lastUp);
        }
        this.mouseDown = true;
        this.lastDown = evt.xy;
        this.stoppedDown = stopDown;
        return !stopDown;
    },

    /**
     * Method: move
     * Handle mousemove and touchmove.  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
     */
    move: function (evt) {
        if(this.stoppedDown && this.freehandMode(evt)) {
            if(this.persist) {
                this.destroyPersistedFeature();
            }
            if(this.maxVertices && this.line &&
                    this.line.geometry.components.length === this.maxVertices) {
                this.removePoint();
                this.finalize();
            } else {
                this.addPoint(evt.xy);
            }
            return false;
        }
        if (!this.touch && (!this.mouseDown || this.stoppedDown)) {
            this.modifyFeature(evt.xy, !!this.lastUp);
        }
        return true;
    },
    
    /**
     * Method: up
     * Handle mouseup and touchend.  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
     */
    up: function (evt) {
        if (this.mouseDown && (!this.lastUp || !this.lastUp.equals(evt.xy))) {
            if(this.stoppedDown && this.freehandMode(evt)) {
                if (this.persist) {
                    this.destroyPersistedFeature();
                }
                this.removePoint();
                this.finalize();
            } else {
                if (this.passesTolerance(this.lastDown, evt.xy,
                                         this.pixelTolerance)) {
                    if (this.touch) {
                        this.modifyFeature(evt.xy);
                    }
                    if(this.lastUp == null && this.persist) {
                        this.destroyPersistedFeature();
                    }
                    this.addPoint(evt.xy);
                    this.lastUp = evt.xy;
                    if(this.line.geometry.components.length === this.maxVertices + 1) {
                        this.finishGeometry();
                    }
                }
            }
        }
        this.stoppedDown = this.stopDown;
        this.mouseDown = false;
        return !this.stopUp;
    },

    /**
     * APIMethod: finishGeometry
     * Finish the geometry and send it back to the control.
     */
    finishGeometry: function() {
        var index = this.line.geometry.components.length - 1;
        this.line.geometry.removeComponent(this.line.geometry.components[index]);
        this.removePoint();
        this.finalize();
    },
  
    /**
     * Method: dblclick 
     * Handle double-clicks.
     * 
     * Parameters:
     * evt - {Event} The browser event
     *
     * Returns: 
     * {Boolean} Allow event propagation
     */
    dblclick: function(evt) {
        if(!this.freehandMode(evt)) {
            this.finishGeometry();
        }
        return false;
    },

    CLASS_NAME: "OpenLayers.Handler.Path"
});
/* ======================================================================
    OpenLayers/Control.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.Control
 * Controls affect the display or behavior of the map. They allow everything
 * from panning and zooming to displaying a scale indicator. Controls by 
 * default are added to the map they are contained within however it is
 * possible to add a control to an external div by passing the div in the
 * options parameter.
 * 
 * Example:
 * The following example shows how to add many of the common controls
 * to a map.
 * 
 * > var map = new OpenLayers.Map('map', { controls: [] });
 * >
 * > map.addControl(new OpenLayers.Control.PanZoomBar());
 * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
 * > map.addControl(new OpenLayers.Control.Permalink());
 * > map.addControl(new OpenLayers.Control.Permalink('permalink'));
 * > map.addControl(new OpenLayers.Control.MousePosition());
 * > map.addControl(new OpenLayers.Control.OverviewMap());
 * > map.addControl(new OpenLayers.Control.KeyboardDefaults());
 *
 * The next code fragment is a quick example of how to intercept 
 * shift-mouse click to display the extent of the bounding box
 * dragged out by the user.  Usually controls are not created
 * in exactly this manner.  See the source for a more complete 
 * example:
 *
 * > var control = new OpenLayers.Control();
 * > OpenLayers.Util.extend(control, {
 * >     draw: function () {
 * >         // this Handler.Box will intercept the shift-mousedown
 * >         // before Control.MouseDefault gets to see it
 * >         this.box = new OpenLayers.Handler.Box( control, 
 * >             {"done": this.notice},
 * >             {keyMask: OpenLayers.Handler.MOD_SHIFT});
 * >         this.box.activate();
 * >     },
 * >
 * >     notice: function (bounds) {
 * >         OpenLayers.Console.userError(bounds);
 * >     }
 * > }); 
 * > map.addControl(control);
 * 
 */
OpenLayers.Control = OpenLayers.Class({

    /** 
     * Property: id 
     * {String} 
     */
    id: null,
    
    /** 
     * Property: map 
     * {<OpenLayers.Map>} this gets set in the addControl() function in
     * OpenLayers.Map 
     */
    map: null,

    /** 
     * APIProperty: div 
     * {DOMElement} The element that contains the control, if not present the 
     *     control is placed inside the map.
     */
    div: null,

    /** 
     * APIProperty: type 
     * {Number} Controls can have a 'type'. The type determines the type of
     * interactions which are possible with them when they are placed in an
     * <OpenLayers.Control.Panel>. 
     */
    type: null, 

    /** 
     * Property: allowSelection
     * {Boolean} By default, controls do not allow selection, because
     * it may interfere with map dragging. If this is true, OpenLayers
     * will not prevent selection of the control.
     * Default is false.
     */
    allowSelection: false,  

    /** 
     * Property: displayClass 
     * {string}  This property is used for CSS related to the drawing of the
     * Control. 
     */
    displayClass: "",
    
    /**
    * APIProperty: title  
    * {string}  This property is used for showing a tooltip over the  
    * Control.  
    */ 
    title: "",

    /**
     * APIProperty: autoActivate
     * {Boolean} Activate the control when it is added to a map.  Default is
     *     false.
     */
    autoActivate: false,

    /** 
     * APIProperty: active 
     * {Boolean} The control is active (read-only).  Use <activate> and 
     *     <deactivate> to change control state.
     */
    active: null,

    /**
     * Property: handlerOptions
     * {Object} Used to set non-default properties on the control's handler
     */
    handlerOptions: null,

    /** 
     * Property: handler 
     * {<OpenLayers.Handler>} null
     */
    handler: null,

    /**
     * APIProperty: eventListeners
     * {Object} If set as an option at construction, the eventListeners
     *     object will be registered with <OpenLayers.Events.on>.  Object
     *     structure must be a listeners object as shown in the example for
     *     the events.on method.
     */
    eventListeners: null,

    /** 
     * APIProperty: events
     * {<OpenLayers.Events>} Events instance for listeners and triggering
     *     control specific events.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * control.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 control.events.object (a reference
     *      to the control).
     * element - {DOMElement} A reference to control.events.element (which
     *      will be null unless documented otherwise).
     *
     * Supported map event types:
     * activate - Triggered when activated.
     * deactivate - Triggered when deactivated.
     */
    events: null,

    /**
     * Constructor: OpenLayers.Control
     * Create an OpenLayers Control.  The options passed as a parameter
     * directly extend the control.  For example passing the following:
     * 
     * > var control = new OpenLayers.Control({div: myDiv});
     *
     * Overrides the default div attribute value of null.
     * 
     * Parameters:
     * options - {Object} 
     */
    initialize: function (options) {
        // We do this before the extend so that instances can override
        // className in options.
        this.displayClass = 
            this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
        
        OpenLayers.Util.extend(this, options);
        
        this.events = new OpenLayers.Events(this);
        if(this.eventListeners instanceof Object) {
            this.events.on(this.eventListeners);
        }
        if (this.id == null) {
            this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
        }
    },

    /**
     * Method: destroy
     * The destroy method is used to perform any clean up before the control
     * is dereferenced.  Typically this is where event listeners are removed
     * to prevent memory leaks.
     */
    destroy: function () {
        if(this.events) {
            if(this.eventListeners) {
                this.events.un(this.eventListeners);
            }
            this.events.destroy();
            this.events = null;
        }
        this.eventListeners = null;

        // eliminate circular references
        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();
                }
            }
            this.handlers = null;
        }
        if (this.map) {
            this.map.removeControl(this);
            this.map = null;
        }
        this.div = null;
    },

    /** 
     * Method: setMap
     * Set the map property for the control. 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) {
        this.map = map;
        if (this.handler) {
            this.handler.setMap(map);
        }
    },
  
    /**
     * Method: draw
     * The draw method is called when the control is ready to be displayed
     * on the page.  If a div has not been created one is created.  Controls
     * with a visual component will almost always want to override this method 
     * to customize the look of control. 
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>} The top-left pixel position of the control
     *      or null.
     *
     * Returns:
     * {DOMElement} A reference to the DIV DOMElement containing the control
     */
    draw: function (px) {
        if (this.div == null) {
            this.div = OpenLayers.Util.createDiv(this.id);
            this.div.className = this.displayClass;
            if (!this.allowSelection) {
                this.div.className += " olControlNoSelect";
                this.div.setAttribute("unselectable", "on", 0);
                this.div.onselectstart = OpenLayers.Function.False; 
            }    
            if (this.title != "") {
                this.div.title = this.title;
            }
        }
        if (px != null) {
            this.position = px.clone();
        }
        this.moveTo(this.position);
        return this.div;
    },

    /**
     * Method: moveTo
     * Sets the left and top style attributes to the passed in pixel 
     * coordinates.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     */
    moveTo: function (px) {
        if ((px != null) && (this.div != null)) {
            this.div.style.left = px.x + "px";
            this.div.style.top = px.y + "px";
        }
    },

    /**
     * APIMethod: activate
     * Explicitly activates a control and it's associated
     * handler if one has been set.  Controls can be
     * deactivated by calling the deactivate() method.
     * 
     * Returns:
     * {Boolean}  True if the control was successfully activated or
     *            false if the control was already active.
     */
    activate: function () {
        if (this.active) {
            return false;
        }
        if (this.handler) {
            this.handler.activate();
        }
        this.active = true;
        if(this.map) {
            OpenLayers.Element.addClass(
                this.map.viewPortDiv,
                this.displayClass.replace(/ /g, "") + "Active"
            );
        }
        this.events.triggerEvent("activate");
        return true;
    },
    
    /**
     * APIMethod: deactivate
     * Deactivates a control and it's associated handler if any.  The exact
     * effect of this depends on the control itself.
     * 
     * Returns:
     * {Boolean} True if the control was effectively deactivated or false
     *           if the control was already inactive.
     */
    deactivate: function () {
        if (this.active) {
            if (this.handler) {
                this.handler.deactivate();
            }
            this.active = false;
            if(this.map) {
                OpenLayers.Element.removeClass(
                    this.map.viewPortDiv,
                    this.displayClass.replace(/ /g, "") + "Active"
                );
            }
            this.events.triggerEvent("deactivate");
            return true;
        }
        return false;
    },

    CLASS_NAME: "OpenLayers.Control"
});

/**
 * Constant: OpenLayers.Control.TYPE_BUTTON
 */
OpenLayers.Control.TYPE_BUTTON = 1;

/**
 * Constant: OpenLayers.Control.TYPE_TOGGLE
 */
OpenLayers.Control.TYPE_TOGGLE = 2;

/**
 * Constant: OpenLayers.Control.TYPE_TOOL
 */
OpenLayers.Control.TYPE_TOOL   = 3;
/* ======================================================================
    OpenLayers/Handler/Drag.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler.js
 */

/**
 * Class: OpenLayers.Handler.Drag
 * The drag handler is used to deal with sequences of browser events related
 *     to dragging.  The handler is used by controls that want to know when
 *     a drag sequence begins, when a drag is happening, and when it has
 *     finished.
 *
 * Controls that use the drag handler typically construct it with callbacks
 *     for 'down', 'move', and 'done'.  Callbacks for these keys are called
 *     when the drag begins, with each move, and when the drag is done.  In
 *     addition, controls can have callbacks keyed to 'up' and 'out' if they
 *     care to differentiate between the types of events that correspond with
 *     the end of a drag sequence.  If no drag actually occurs (no mouse move)
 *     the 'down' and 'up' callbacks will be called, but not the 'done'
 *     callback.
 *
 * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Handler>
 */
OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, {
  
    /** 
     * Property: started
     * {Boolean} When a mousedown or touchstart event is received, we want to
     * record it, but not set 'dragging' until the mouse moves after starting.
     */
    started: false,

    /**
     * Property: stopDown
     * {Boolean} Stop propagation of mousedown events from getting to listeners
     *     on the same element.  Default is true.
     */
    stopDown: true,

    /** 
     * Property: dragging 
     * {Boolean} 
     */
    dragging: false,

    /** 
     * Property: last
     * {<OpenLayers.Pixel>} The last pixel location of the drag.
     */
    last: null,

    /** 
     * Property: start
     * {<OpenLayers.Pixel>} The first pixel location of the drag.
     */
    start: null,

    /**
     * Property: lastMoveEvt
     * {Object} The last mousemove event that occurred. Used to
     *     position the map correctly when our "delay drag"
     *     timeout expired.
     */
    lastMoveEvt: null,

    /**
     * Property: oldOnselectstart
     * {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,
    
    /**
     * APIProperty: documentDrag
     * {Boolean} If set to true, the handler will also handle mouse moves when
     *     the cursor has moved out of the map viewport. Default is false.
     */
    documentDrag: false,
    
    /**
     * Property: documentEvents
     * {Boolean} Are we currently observing document events?
     */
    documentEvents: null,

    /**
     * Constructor: OpenLayers.Handler.Drag
     * Returns OpenLayers.Handler.Drag
     * 
     * 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 'move' and 'done' are supported. You can also speficy
     *     callbacks for 'down', 'up', and 'out' to respond to those events.
     * options - {Object} 
     */
    initialize: function(control, callbacks, options) {
        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
        
        if (this.documentDrag === true) {
            var me = this;
            this._docMove = function(evt) {
                me.mousemove({
                    xy: {x: evt.clientX, y: evt.clientY},
                    element: document
                });
            };
            this._docUp = function(evt) {
                me.mouseup({xy: {x: evt.clientX, y: evt.clientY}});
            };
        }
    },

    
    /**
     * Method: dragstart
     * This private method is factorized from mousedown and touchstart methods
     *
     * Parameters:
     * evt - {Event} The event
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    dragstart: function (evt) {
        var propagate = true;
        this.dragging = false;
        if (this.checkModifiers(evt) &&
               (OpenLayers.Event.isLeftClick(evt) ||
                OpenLayers.Event.isSingleTouch(evt))) {
            this.started = true;
            this.start = evt.xy;
            this.last = evt.xy;
            OpenLayers.Element.addClass(
                this.map.viewPortDiv, "olDragDown"
            );
            this.down(evt);
            this.callback("down", [evt.xy]);

            // prevent document dragging
            OpenLayers.Event.preventDefault(evt);

            if(!this.oldOnselectstart) {
                this.oldOnselectstart = document.onselectstart ?
                    document.onselectstart : OpenLayers.Function.True;
            }
            document.onselectstart = OpenLayers.Function.False;

            propagate = !this.stopDown;
        } else {
            this.started = false;
            this.start = null;
            this.last = null;
        }
        return propagate;
    },

    /**
     * Method: dragmove
     * This private method is factorized from mousemove and touchmove methods
     *
     * Parameters:
     * evt - {Event} The event
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    dragmove: function (evt) {
        this.lastMoveEvt = evt;
        if (this.started && !this.timeoutId && (evt.xy.x != this.last.x ||
                                                evt.xy.y != this.last.y)) {
            if(this.documentDrag === true && this.documentEvents) {
                if(evt.element === document) {
                    this.adjustXY(evt);
                    // do setEvent manually because the documentEvents are not
                    // registered with the map
                    this.setEvent(evt);
                } else {
                    this.removeDocumentEvents();
                }
            }
            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 = OpenLayers.Function.False;
            }
            this.last = evt.xy;
        }
        return true;
    },

    /**
     * Method: dragend
     * This private method is factorized from mouseup and touchend methods
     *
     * Parameters:
     * evt - {Event} The event
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    dragend: function (evt) {
        if (this.started) {
            if(this.documentDrag === true && this.documentEvents) {
                this.adjustXY(evt);
                this.removeDocumentEvents();
            }
            var dragged = (this.start != this.last);
            this.started = false;
            this.dragging = false;
            OpenLayers.Element.removeClass(
                this.map.viewPortDiv, "olDragDown"
            );
            this.up(evt);
            this.callback("up", [evt.xy]);
            if(dragged) {
                this.callback("done", [evt.xy]);
            }
            document.onselectstart = this.oldOnselectstart;
        }
        return true;
    },

    /**
     * The four methods below (down, move, up, and out) are used by subclasses
     *     to do their own processing related to these mouse events.
     */

    /**
     * Method: down
     * This method is called during the handling of the mouse down event.
     *     Subclasses can do their own processing here.
     *
     * Parameters:
     * evt - {Event} The mouse down event
     */
    down: function(evt) {
    },

    /**
     * Method: move
     * This method is called during the handling of the mouse move event.
     *     Subclasses can do their own processing here.
     *
     * Parameters:
     * evt - {Event} The mouse move event
     *
     */
    move: function(evt) {
    },

    /**
     * Method: up
     * This method is called during the handling of the mouse up event.
     *     Subclasses can do their own processing here.
     *
     * Parameters:
     * evt - {Event} The mouse up event
     */
    up: function(evt) {
    },

    /**
     * Method: out
     * This method is called during the handling of the mouse out event.
     *     Subclasses can do their own processing here.
     *
     * Parameters:
     * evt - {Event} The mouse out event
     */
    out: function(evt) {
    },

    /**
     * The methods below are part of the magic of event handling.  Because
     *     they are named like browser events, they are registered as listeners
     *     for the events they represent.
     */

    /**
     * Method: mousedown
     * Handle mousedown events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    mousedown: function(evt) {
        return this.dragstart(evt);
    },

    /**
     * Method: touchstart
     * Handle touchstart events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    touchstart: function(evt) {
        this.startTouch();
        return this.dragstart(evt);
    },

    /**
     * Method: mousemove
     * Handle mousemove events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    mousemove: function(evt) {
        return this.dragmove(evt);
    },

    /**
     * Method: touchmove
     * Handle touchmove events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    touchmove: function(evt) {
        return this.dragmove(evt);
    },

    /**
     * Method: removeTimeout
     * Private. Called by mousemove() to remove the drag timeout.
     */
    removeTimeout: function() {
        this.timeoutId = null;
        // if timeout expires while we're still dragging (mouseup
        // hasn't occurred) then call mousemove to move to the
        // correct position
        if(this.dragging) {
            this.mousemove(this.lastMoveEvt);
        }
    },

    /**
     * Method: mouseup
     * Handle mouseup events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    mouseup: function(evt) {
        return this.dragend(evt);
    },

    /**
     * Method: touchend
     * Handle touchend events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    touchend: function(evt) {
        // override evt.xy with last position since touchend does not have
        // any touch position
        evt.xy = this.last;
        return this.dragend(evt);
    },

    /**
     * Method: mouseout
     * Handle mouseout events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    mouseout: function (evt) {
        if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
            if(this.documentDrag === true) {
                this.addDocumentEvents();
            } else {
                var dragged = (this.start != this.last);
                this.started = false; 
                this.dragging = false;
                OpenLayers.Element.removeClass(
                    this.map.viewPortDiv, "olDragDown"
                );
                this.out(evt);
                this.callback("out", []);
                if(dragged) {
                    this.callback("done", [evt.xy]);
                }
                if(document.onselectstart) {
                    document.onselectstart = this.oldOnselectstart;
                }
            }
        }
        return true;
    },

    /**
     * Method: click
     * The drag handler captures the click event.  If something else registers
     *     for clicks on the same element, its listener will not be called 
     *     after a drag.
     * 
     * Parameters: 
     * evt - {Event} 
     * 
     * Returns:
     * {Boolean} Let the event propagate.
     */
    click: function (evt) {
        // let the click event propagate only if the mouse moved
        return (this.start == this.last);
    },

    /**
     * Method: activate
     * Activate the handler.
     * 
     * Returns:
     * {Boolean} The handler was successfully activated.
     */
    activate: function() {
        var activated = false;
        if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
            this.dragging = false;
            activated = true;
        }
        return activated;
    },

    /**
     * Method: 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.started = false;
            this.dragging = false;
            this.start = null;
            this.last = null;
            deactivated = true;
            OpenLayers.Element.removeClass(
                this.map.viewPortDiv, "olDragDown"
            );
        }
        return deactivated;
    },
    
    /**
     * Method: adjustXY
     * Converts event coordinates that are relative to the document body to
     * ones that are relative to the map viewport. The latter is the default in
     * OpenLayers.
     * 
     * Parameters:
     * evt - {Object}
     */
    adjustXY: function(evt) {
        var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv);
        evt.xy.x -= pos[0];
        evt.xy.y -= pos[1];
    },
    
    /**
     * Method: addDocumentEvents
     * Start observing document events when documentDrag is true and the mouse
     * cursor leaves the map viewport while dragging.
     */
    addDocumentEvents: function() {
        OpenLayers.Element.addClass(document.body, "olDragDown");
        this.documentEvents = true;
        OpenLayers.Event.observe(document, "mousemove", this._docMove);
        OpenLayers.Event.observe(document, "mouseup", this._docUp);
    },
    
    /**
     * Method: removeDocumentEvents
     * Stops observing document events when documentDrag is true and the mouse
     * cursor re-enters the map viewport while dragging.
     */
    removeDocumentEvents: function() {
        OpenLayers.Element.removeClass(document.body, "olDragDown");
        this.documentEvents = false;
        OpenLayers.Event.stopObserving(document, "mousemove", this._docMove);
        OpenLayers.Event.stopObserving(document, "mouseup", this._docUp);
    },

    CLASS_NAME: "OpenLayers.Handler.Drag"
});
/* ======================================================================
    OpenLayers/Handler/Box.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler.js
 * @requires OpenLayers/Handler/Drag.js
 */

/**
 * Class: OpenLayers.Handler.Box
 * Handler for dragging a rectangle across the map.  Box is displayed 
 * on mouse down, moves on mouse move, and is finished on mouse up.
 *
 * Inherits from:
 *  - <OpenLayers.Handler> 
 */
OpenLayers.Handler.Box = OpenLayers.Class(OpenLayers.Handler, {

    /** 
     * Property: dragHandler 
     * {<OpenLayers.Handler.Drag>} 
     */
    dragHandler: null,

    /**
     * APIProperty: boxDivClassName
     * {String} The CSS class to use for drawing the box. Default is
     *     olHandlerBoxZoomBox
     */
    boxDivClassName: 'olHandlerBoxZoomBox',
    
    /**
     * Property: boxOffsets
     * {Object} Caches box offsets from css. This is used by the getBoxOffsets
     * method.
     */
    boxOffsets: null,

    /**
     * Constructor: OpenLayers.Handler.Box
     *
     * Parameters:
     * control - {<OpenLayers.Control>} 
     * callbacks - {Object} An object with a properties whose values are
     *     functions.  Various callbacks described below.
     * options - {Object} 
     *
     * Named callbacks:
     * start - Called when the box drag operation starts.
     * done - Called when the box drag operation is finished.
     *     The callback should expect to receive a single argument, the box 
     *     bounds or a pixel. If the box dragging didn't span more than a 5 
     *     pixel distance, a pixel will be returned instead of a bounds object.
     */
    initialize: function(control, callbacks, options) {
        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
        this.dragHandler = new OpenLayers.Handler.Drag(
            this, 
            {
                down: this.startBox, 
                move: this.moveBox, 
                out: this.removeBox,
                up: this.endBox
            }, 
            {keyMask: this.keyMask}
        );
    },

    /**
     * Method: destroy
     */
    destroy: function() {
        OpenLayers.Handler.prototype.destroy.apply(this, arguments);
        if (this.dragHandler) {
            this.dragHandler.destroy();
            this.dragHandler = null;
        }            
    },

    /**
     * Method: setMap
     */
    setMap: function (map) {
        OpenLayers.Handler.prototype.setMap.apply(this, arguments);
        if (this.dragHandler) {
            this.dragHandler.setMap(map);
        }
    },

    /**
    * Method: startBox
    *
    * Parameters:
    * xy - {<OpenLayers.Pixel>}
    */
    startBox: function (xy) {
        this.callback("start", []);
        this.zoomBox = OpenLayers.Util.createDiv('zoomBox', {
            x: -9999, y: -9999
        });
        this.zoomBox.className = this.boxDivClassName;                                         
        this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
        
        this.map.viewPortDiv.appendChild(this.zoomBox);
        
        OpenLayers.Element.addClass(
            this.map.viewPortDiv, "olDrawBox"
        );
    },

    /**
    * Method: moveBox
    */
    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);

        var offset = this.getBoxOffsets();
        this.zoomBox.style.width = (deltaX + offset.width + 1) + "px";
        this.zoomBox.style.height = (deltaY + offset.height + 1) + "px";
        this.zoomBox.style.left = (xy.x < startX ?
            startX - deltaX - offset.left : startX - offset.left) + "px";
        this.zoomBox.style.top = (xy.y < startY ?
            startY - deltaY - offset.top : startY - offset.top) + "px";
    },

    /**
    * Method: endBox
    */
    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(); // i.e. OL.Pixel
        } 
        this.removeBox();

        this.callback("done", [result]);
    },

    /**
     * Method: removeBox
     * Remove the zoombox from the screen and nullify our reference to it.
     */
    removeBox: function() {
        this.map.viewPortDiv.removeChild(this.zoomBox);
        this.zoomBox = null;
        this.boxOffsets = null;
        OpenLayers.Element.removeClass(
            this.map.viewPortDiv, "olDrawBox"
        );

    },

    /**
     * Method: activate
     */
    activate: function () {
        if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
            this.dragHandler.activate();
            return true;
        } else {
            return false;
        }
    },

    /**
     * Method: deactivate
     */
    deactivate: function () {
        if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
            if (this.dragHandler.deactivate()) {
                if (this.zoomBox) {
                    this.removeBox();
                }
            }
            return true;
        } else {
            return false;
        }
    },
    
    /**
     * Method: getBoxOffsets
     * Determines border offsets for a box, according to the box model.
     * 
     * Returns:
     * {Object} an object with the following offsets:
     *     - left
     *     - right
     *     - top
     *     - bottom
     *     - width
     *     - height
     */
    getBoxOffsets: function() {
        if (!this.boxOffsets) {
            // Determine the box model. If the testDiv's clientWidth is 3, then
            // the borders are outside and we are dealing with the w3c box
            // model. Otherwise, the browser uses the traditional box model and
            // the borders are inside the box bounds, leaving us with a
            // clientWidth of 1.
            var testDiv = document.createElement("div");
            //testDiv.style.visibility = "hidden";
            testDiv.style.position = "absolute";
            testDiv.style.border = "1px solid black";
            testDiv.style.width = "3px";
            document.body.appendChild(testDiv);
            var w3cBoxModel = testDiv.clientWidth == 3;
            document.body.removeChild(testDiv);
            
            var left = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
                "border-left-width"));
            var right = parseInt(OpenLayers.Element.getStyle(
                this.zoomBox, "border-right-width"));
            var top = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
                "border-top-width"));
            var bottom = parseInt(OpenLayers.Element.getStyle(
                this.zoomBox, "border-bottom-width"));
            this.boxOffsets = {
                left: left,
                right: right,
                top: top,
                bottom: bottom,
                width: w3cBoxModel === false ? left + right : 0,
                height: w3cBoxModel === false ? top + bottom : 0
            };
        }
        return this.boxOffsets;
    },
  
    CLASS_NAME: "OpenLayers.Handler.Box"
});
/* ======================================================================
    OpenLayers/Control/ZoomBox.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Handler/Box.js
 */

/**
 * Class: OpenLayers.Control.ZoomBox
 * The ZoomBox control enables zooming directly to a given extent, by drawing 
 * a box on the map. The box is drawn by holding down shift, whilst dragging 
 * the mouse.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
    /**
     * Property: type
     * {OpenLayers.Control.TYPE}
     */
    type: OpenLayers.Control.TYPE_TOOL,

    /**
     * Property: out
     * {Boolean} Should the control be used for zooming out?
     */
    out: false,

    /**
     * APIProperty: keyMask
     * {Integer} Zoom only occurs if the keyMask matches the combination of 
     *     keys down. Use bitwise operators and one or more of the
     *     <OpenLayers.Handler> constants to construct a keyMask. Leave null if 
     *     not used mask. Default is null.
     */
    keyMask: null,

    /**
     * APIProperty: alwaysZoom
     * {Boolean} Always zoom in/out when box drawn, even if the zoom level does
     * not change.
     */
    alwaysZoom: false,
    
    /**
     * APIProperty: zoomOnClick
     * {Boolean} Should we zoom when no box was dragged, i.e. the user only
     * clicked? Default is true.
     */
    zoomOnClick: true,

    /**
     * Method: draw
     */    
    draw: function() {
        this.handler = new OpenLayers.Handler.Box( this,
                            {done: this.zoomBox}, {keyMask: this.keyMask} );
    },

    /**
     * Method: zoomBox
     *
     * Parameters:
     * position - {<OpenLayers.Bounds>} or {<OpenLayers.Pixel>}
     */
    zoomBox: function (position) {
        if (position instanceof OpenLayers.Bounds) {
            var bounds,
                targetCenterPx = position.getCenterPixel();
            if (!this.out) {
                var minXY = this.map.getLonLatFromPixel({
                    x: position.left,
                    y: position.bottom
                });
                var maxXY = this.map.getLonLatFromPixel({
                    x: position.right,
                    y: position.top
                });
                bounds = new OpenLayers.Bounds(minXY.lon, minXY.lat,
                                               maxXY.lon, maxXY.lat);
            } else {
                var pixWidth = position.right - position.left;
                var pixHeight = position.bottom - position.top;
                var zoomFactor = Math.min((this.map.size.h / pixHeight),
                    (this.map.size.w / pixWidth));
                var extent = this.map.getExtent();
                var center = this.map.getLonLatFromPixel(targetCenterPx);
                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;
                bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax);
            }
            // always zoom in/out 
            var lastZoom = this.map.getZoom(),
                size = this.map.getSize(),
                centerPx = {x: size.w / 2, y: size.h / 2},
                zoom = this.map.getZoomForExtent(bounds),
                oldRes = this.map.getResolution(),
                newRes = this.map.getResolutionForZoom(zoom);
            if (oldRes == newRes) {
                this.map.setCenter(this.map.getLonLatFromPixel(targetCenterPx));
            } else {
              var zoomOriginPx = {
                    x: (oldRes * targetCenterPx.x - newRes * centerPx.x) /
                        (oldRes - newRes),
                    y: (oldRes * targetCenterPx.y - newRes * centerPx.y) /
                        (oldRes - newRes)
                };
                this.map.zoomTo(zoom, zoomOriginPx);
            }
            if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){ 
                this.map.zoomTo(lastZoom + (this.out ? -1 : 1)); 
            }
        } else if (this.zoomOnClick) { // it's a pixel
            if (!this.out) {
                this.map.zoomTo(this.map.getZoom() + 1, position);
            } else {
                this.map.zoomTo(this.map.getZoom() - 1, position);
            }
        }
    },

    CLASS_NAME: "OpenLayers.Control.ZoomBox"
});
/* ======================================================================
    OpenLayers/Control/DragPan.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Handler/Drag.js
 */

/**
 * Class: OpenLayers.Control.DragPan
 * The DragPan control pans the map with a drag of the mouse.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {

    /** 
     * Property: type
     * {OpenLayers.Control.TYPES}
     */
    type: OpenLayers.Control.TYPE_TOOL,
    
    /**
     * Property: panned
     * {Boolean} The map moved.
     */
    panned: false,
    
    /**
     * Property: interval
     * {Integer} The number of milliseconds that should ellapse before
     *     panning the map again. Defaults to 0 milliseconds, which means that
     *     no separate cycle is used for panning. In most cases you won't want
     *     to change this value. For slow machines/devices larger values can be
     *     tried out.
     */
    interval: 0,
    
    /**
     * APIProperty: documentDrag
     * {Boolean} If set to true, mouse dragging will continue even if the
     *     mouse cursor leaves the map viewport. Default is false.
     */
    documentDrag: false,

    /**
     * Property: kinetic
     * {<OpenLayers.Kinetic>} The OpenLayers.Kinetic object.
     */
    kinetic: null,

    /**
     * APIProperty: enableKinetic
     * {Boolean} Set this option to enable "kinetic dragging". Can be
     *     set to true or to an object. If set to an object this
     *     object will be passed to the {<OpenLayers.Kinetic>}
     *     constructor. Defaults to true.
     *     To get kinetic dragging, ensure that OpenLayers/Kinetic.js is
     *     included in your build config.
     */
    enableKinetic: true,

    /**
     * APIProperty: kineticInterval
     * {Integer} Interval in milliseconds between 2 steps in the "kinetic
     *     scrolling". Applies only if enableKinetic is set. Defaults
     *     to 10 milliseconds.
     */
    kineticInterval: 10,


    /**
     * Method: draw
     * Creates a Drag handler, using <panMap> and
     * <panMapDone> as callbacks.
     */    
    draw: function() {
        if (this.enableKinetic && OpenLayers.Kinetic) {
            var config = {interval: this.kineticInterval};
            if(typeof this.enableKinetic === "object") {
                config = OpenLayers.Util.extend(config, this.enableKinetic);
            }
            this.kinetic = new OpenLayers.Kinetic(config);
        }
        this.handler = new OpenLayers.Handler.Drag(this, {
                "move": this.panMap,
                "done": this.panMapDone,
                "down": this.panMapStart
            }, {
                interval: this.interval,
                documentDrag: this.documentDrag
            }
        );
    },

    /**
     * Method: panMapStart
     */
    panMapStart: function() {
        if(this.kinetic) {
            this.kinetic.begin();
        }
    },

    /**
    * Method: panMap
    *
    * Parameters:
    * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
    */
    panMap: function(xy) {
        if(this.kinetic) {
            this.kinetic.update(xy);
        }
        this.panned = true;
        this.map.pan(
            this.handler.last.x - xy.x,
            this.handler.last.y - xy.y,
            {dragging: true, animate: false}
        );
    },
    
    /**
     * Method: panMapDone
     * Finish the panning operation.  Only call setCenter (through <panMap>)
     *     if the map has actually been moved.
     *
     * Parameters:
     * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
     */
    panMapDone: function(xy) {
        if(this.panned) {
            var res = null;
            if (this.kinetic) {
                res = this.kinetic.end(xy);
            }
            this.map.pan(
                this.handler.last.x - xy.x,
                this.handler.last.y - xy.y,
                {dragging: !!res, animate: false}
            );
            if (res) {
                var self = this;
                this.kinetic.move(res, function(x, y, end) {
                    self.map.pan(x, y, {dragging: !end, animate: false});
                });
            }
            this.panned = false;
        }
    },

    CLASS_NAME: "OpenLayers.Control.DragPan"
});
/* ======================================================================
    OpenLayers/Handler/Click.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler.js
 */

/**
 * Class: OpenLayers.Handler.Click
 * A handler for mouse clicks.  The intention of this handler is to give
 *     controls more flexibility with handling clicks.  Browsers trigger
 *     click events twice for a double-click.  In addition, the mousedown,
 *     mousemove, mouseup sequence fires a click event.  With this handler,
 *     controls can decide whether to ignore clicks associated with a double
 *     click.  By setting a <pixelTolerance>, controls can also ignore clicks
 *     that include a drag.  Create a new instance with the
 *     <OpenLayers.Handler.Click> constructor.
 * 
 * Inherits from:
 *  - <OpenLayers.Handler> 
 */
OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
    /**
     * APIProperty: delay
     * {Number} Number of milliseconds between clicks before the event is
     *     considered a double-click.
     */
    delay: 300,
    
    /**
     * APIProperty: single
     * {Boolean} Handle single clicks.  Default is true.  If false, clicks
     * will not be reported.  If true, single-clicks will be reported.
     */
    single: true,
    
    /**
     * APIProperty: double
     * {Boolean} Handle double-clicks.  Default is false.
     */
    'double': false,
    
    /**
     * APIProperty: pixelTolerance
     * {Number} Maximum number of pixels between mouseup and mousedown for an
     *     event to be considered a click.  Default is 0.  If set to an
     *     integer value, clicks with a drag greater than the value will be
     *     ignored.  This property can only be set when the handler is
     *     constructed.
     */
    pixelTolerance: 0,
        
    /**
     * APIProperty: dblclickTolerance
     * {Number} Maximum distance in pixels between clicks for a sequence of 
     *     events to be considered a double click.  Default is 13.  If the
     *     distance between two clicks is greater than this value, a double-
     *     click will not be fired.
     */
    dblclickTolerance: 13,
        
    /**
     * APIProperty: stopSingle
     * {Boolean} Stop other listeners from being notified of clicks.  Default
     *     is false.  If true, any listeners registered before this one for 
     *     click or rightclick events will not be notified.
     */
    stopSingle: false,
    
    /**
     * APIProperty: stopDouble
     * {Boolean} Stop other listeners from being notified of double-clicks.
     *     Default is false.  If true, any click listeners registered before
     *     this one will not be notified of *any* double-click events.
     * 
     * The one caveat with stopDouble is that given a map with two click
     *     handlers, one with stopDouble true and the other with stopSingle
     *     true, the stopSingle handler should be activated last to get
     *     uniform cross-browser performance.  Since IE triggers one click
     *     with a dblclick and FF triggers two, if a stopSingle handler is
     *     activated first, all it gets in IE is a single click when the
     *     second handler stops propagation on the dblclick.
     */
    stopDouble: false,

    /**
     * Property: timerId
     * {Number} The id of the timeout waiting to clear the <delayedCall>.
     */
    timerId: null,
    
    /**
     * Property: down
     * {Object} Object that store relevant information about the last
     *     mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
     *     the average location of the mouse/touch event. Its 'touches'
     *     property records clientX/clientY of each touches.
     */
    down: null,

    /**
     * Property: last
     * {Object} Object that store relevant information about the last
     *     mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
     *     the average location of the mouse/touch event. Its 'touches'
     *     property records clientX/clientY of each touches.
     */
    last: null,

    /** 
     * Property: first
     * {Object} When waiting for double clicks, this object will store 
     *     information about the first click in a two click sequence.
     */
    first: 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.
     * 
     * Parameters:
     * control - {<OpenLayers.Control>} The control that is making use of
     *     this handler.  If a handler is being used without a control, the
     *     handler's setMap method must be overridden to deal properly with
     *     the map.
     * callbacks - {Object} An object with keys corresponding to callbacks
     *     that will be called by the handler. The callbacks should
     *     expect to recieve a single argument, the click event.
     *     Callbacks for 'click' and 'dblclick' are supported.
     * options - {Object} Optional object whose properties will be set on the
     *     handler.
     */
    
    /**
     * Method: touchstart
     * Handle touchstart.
     *
     * Returns:
     * {Boolean} Continue propagating this event.
     */
    touchstart: function(evt) {
        this.startTouch();
        this.down = this.getEventInfo(evt);
        this.last = this.getEventInfo(evt);
        return true;
    },
    
    /**
     * Method: touchmove
     *    Store position of last move, because touchend event can have
     *    an empty "touches" property.
     *
     * Returns:
     * {Boolean} Continue propagating this event.
     */
    touchmove: function(evt) {
        this.last = this.getEventInfo(evt);
        return true;
    },

    /**
     * Method: touchend
     *   Correctly set event xy property, and add lastTouches to have
     *   touches property from last touchstart or touchmove
     *
     * Returns:
     * {Boolean} Continue propagating this event.
     */
    touchend: function(evt) {
        // touchstart may not have been allowed to propagate
        if (this.down) {
            evt.xy = this.last.xy;
            evt.lastTouches = this.last.touches;
            this.handleSingle(evt);
            this.down = null;
        }
        return true;
    },

    /**
     * Method: mousedown
     * Handle mousedown.
     *
     * Returns:
     * {Boolean} Continue propagating this event.
     */
    mousedown: function(evt) {
        this.down = this.getEventInfo(evt);
        this.last = this.getEventInfo(evt);
        return true;
    },

    /**
     * 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)) {
            propagate = 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]);
        }
    },
    
    /**
     * Method: click
     * Handle click events from the browser.  This is registered as a listener
     *     for click events and should not be called from other events in this
     *     handler.
     *
     * Returns:
     * {Boolean} Continue propagating this event.
     */
    click: function(evt) {
        if (!this.last) {
            this.last = this.getEventInfo(evt);
        }
        this.handleSingle(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
     *     dblclick to properly handle single clicks.  This method is registered
     *     as a listener for the dblclick browser event.  It should *not* be
     *     called by other methods in this handler.
     *     
     * Returns:
     * {Boolean} Continue propagating this event.
     */
    dblclick: function(evt) {
        this.handleDouble(evt);
        return !this.stopDouble;
    },
    
    /** 
     * Method: handleDouble
     * Handle double-click sequence.
     */
    handleDouble: function(evt) {
        if (this.passesDblclickTolerance(evt)) {
            if (this["double"]) {
                this.callback("dblclick", [evt]);
            }
            // to prevent a dblclick from firing the click callback in IE
            this.clearTimer();
        }
    },
    
    /** 
     * Method: handleSingle
     * Handle single click sequence.
     */
    handleSingle: function(evt) {
        if (this.passesTolerance(evt)) {
            if (this.timerId != null) {
                // already received a click
                if (this.last.touches && this.last.touches.length === 1) {
                    // touch device, no dblclick event - this may be a double
                    if (this["double"]) {
                        // on Android don't let the browser zoom on the page
                        OpenLayers.Event.preventDefault(evt);
                    }
                    this.handleDouble(evt);
                }
                // if we're not in a touch environment we clear the click timer
                // if we've got a second touch, we'll get two touchend events
                if (!this.last.touches || this.last.touches.length !== 2) {
                    this.clearTimer();
                }
            } else {
                // remember the first click info so we can compare to the second
                this.first = this.getEventInfo(evt);
                // set the timer, send evt only if single is true
                //use a clone of the event object because it will no longer 
                //be a valid event object in IE in the timer callback
                var clickEvent = this.single ?
                    OpenLayers.Util.extend({}, evt) : null;
                this.queuePotentialClick(clickEvent);
            }
        }
    },
    
    /** 
     * Method: queuePotentialClick
     * This method is separated out largely to make testing easier (so we
     *     don't have to override window.setTimeout)
     */
    queuePotentialClick: function(evt) {
        this.timerId = window.setTimeout(
            OpenLayers.Function.bind(this.delayedCall, this, evt),
            this.delay
        );
    },

    /**
     * Method: passesTolerance
     * Determine whether the event is within the optional pixel tolerance.  Note
     *     that the pixel tolerance check only works if mousedown events get to
     *     the listeners registered here.  If they are stopped by other elements,
     *     the <pixelTolerance> will have no effect here (this method will always
     *     return true).
     *
     * Returns:
     * {Boolean} The click is within the pixel tolerance (if specified).
     */
    passesTolerance: function(evt) {
        var passes = true;
        if (this.pixelTolerance != null && this.down && this.down.xy) {
            passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
            // for touch environments, we also enforce that all touches
            // start and end within the given tolerance to be considered a click
            if (passes && this.touch && 
                this.down.touches.length === this.last.touches.length) {
                // the touchend event doesn't come with touches, so we check
                // down and last
                for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
                    if (this.getTouchDistance(
                            this.down.touches[i], 
                            this.last.touches[i]
                        ) > this.pixelTolerance) {
                        passes = false;
                        break;
                    }
                }
            }
        }
        return passes;
    },
    
    /** 
     * Method: getTouchDistance
     *
     * Returns:
     * {Boolean} The pixel displacement between two touches.
     */
    getTouchDistance: function(from, to) {
        return Math.sqrt(
            Math.pow(from.clientX - to.clientX, 2) +
            Math.pow(from.clientY - to.clientY, 2)
        );
    },
    
    /**
     * Method: passesDblclickTolerance
     * Determine whether the event is within the optional double-cick pixel 
     *     tolerance.
     *
     * Returns:
     * {Boolean} The click is within the double-click pixel tolerance.
     */
    passesDblclickTolerance: function(evt) {
        var passes = true;
        if (this.down && this.first) {
            passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
        }
        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;
        }
        if (this.rightclickTimerId != null) {
            window.clearTimeout(this.rightclickTimerId);
            this.rightclickTimerId = null;
        }
    },
    
    /**
     * Method: delayedCall
     * Sets <timerId> to null.  And optionally triggers the click callback if
     *     evt is set.
     */
    delayedCall: function(evt) {
        this.timerId = null;
        if (evt) {
            this.callback("click", [evt]);
        }
    },

    /**
     * Method: getEventInfo
     * This method allows us to store event information without storing the
     *     actual event.  In touch devices (at least), the same event is 
     *     modified between touchstart, touchmove, and touchend.
     *
     * Returns:
     * {Object} An object with event related info.
     */
    getEventInfo: function(evt) {
        var touches;
        if (evt.touches) {
            var len = evt.touches.length;
            touches = new Array(len);
            var touch;
            for (var i=0; i<len; i++) {
                touch = evt.touches[i];
                touches[i] = {
                    clientX: touch.olClientX,
                    clientY: touch.olClientY
                };
            }
        }
        return {
            xy: evt.xy,
            touches: touches
        };
    },

    /**
     * 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();
            this.down = null;
            this.first = null;
            this.last = null;
            deactivated = true;
        }
        return deactivated;
    },

    CLASS_NAME: "OpenLayers.Handler.Click"
});
/* ======================================================================
    OpenLayers/Control/Navigation.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control/ZoomBox.js
 * @requires OpenLayers/Control/DragPan.js
 * @requires OpenLayers/Handler/MouseWheel.js
 * @requires OpenLayers/Handler/Click.js
 */

/**
 * Class: OpenLayers.Control.Navigation
 * The navigation control handles map browsing with mouse events (dragging,
 *     double-clicking, and scrolling the wheel).  Create a new navigation 
 *     control with the <OpenLayers.Control.Navigation> control.  
 * 
 *     Note that this control is added to the map by default (if no controls 
 *     array is sent in the options object to the <OpenLayers.Map> 
 *     constructor).
 * 
 * Inherits:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {

    /** 
     * Property: dragPan
     * {<OpenLayers.Control.DragPan>} 
     */
    dragPan: null,

    /**
     * APIProperty: dragPanOptions
     * {Object} Options passed to the DragPan control.
     */
    dragPanOptions: null,

    /**
     * Property: pinchZoom
     * {<OpenLayers.Control.PinchZoom>}
     */
    pinchZoom: null,

    /**
     * APIProperty: pinchZoomOptions
     * {Object} Options passed to the PinchZoom control.
     */
    pinchZoomOptions: null,

    /**
     * APIProperty: documentDrag
     * {Boolean} Allow panning of the map by dragging outside map viewport.
     *     Default is false.
     */
    documentDrag: false,

    /** 
     * Property: zoomBox
     * {<OpenLayers.Control.ZoomBox>}
     */
    zoomBox: null,

    /**
     * APIProperty: zoomBoxEnabled
     * {Boolean} Whether the user can draw a box to zoom
     */
    zoomBoxEnabled: true, 

    /**
     * APIProperty: zoomWheelEnabled
     * {Boolean} Whether the mousewheel should zoom the map
     */
    zoomWheelEnabled: true,
    
    /**
     * Property: mouseWheelOptions
     * {Object} Options passed to the MouseWheel control (only useful if
     *     <zoomWheelEnabled> is set to true). Default is no options for maps
     *     with fractionalZoom set to true, otherwise
     *     {cumulative: false, interval: 50, maxDelta: 6} 
     */
    mouseWheelOptions: null,

    /**
     * APIProperty: handleRightClicks
     * {Boolean} Whether or not to handle right clicks. Default is false.
     */
    handleRightClicks: false,

    /**
     * APIProperty: zoomBoxKeyMask
     * {Integer} <OpenLayers.Handler> key code of the key, which has to be
     *    pressed, while drawing the zoom box with the mouse on the screen. 
     *    You should probably set handleRightClicks to true if you use this
     *    with MOD_CTRL, to disable the context menu for machines which use
     *    CTRL-Click as a right click.
     * Default: <OpenLayers.Handler.MOD_SHIFT>
     */
    zoomBoxKeyMask: OpenLayers.Handler.MOD_SHIFT,
    
    /**
     * APIProperty: autoActivate
     * {Boolean} Activate the control when it is added to a map.  Default is
     *     true.
     */
    autoActivate: true,

    /**
     * Constructor: OpenLayers.Control.Navigation
     * Create a new navigation control
     * 
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *                    the control
     */
    initialize: function(options) {
        this.handlers = {};
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
    },

    /**
     * Method: destroy
     * The destroy method is used to perform any clean up before the control
     * is dereferenced.  Typically this is where event listeners are removed
     * to prevent memory leaks.
     */
    destroy: function() {
        this.deactivate();

        if (this.dragPan) {
            this.dragPan.destroy();
        }
        this.dragPan = null;

        if (this.zoomBox) {
            this.zoomBox.destroy();
        }
        this.zoomBox = null;

        if (this.pinchZoom) {
            this.pinchZoom.destroy();
        }
        this.pinchZoom = null;

        OpenLayers.Control.prototype.destroy.apply(this,arguments);
    },
    
    /**
     * Method: activate
     */
    activate: function() {
        this.dragPan.activate();
        if (this.zoomWheelEnabled) {
            this.handlers.wheel.activate();
        }    
        this.handlers.click.activate();
        if (this.zoomBoxEnabled) {
            this.zoomBox.activate();
        }
        if (this.pinchZoom) {
            this.pinchZoom.activate();
        }
        return OpenLayers.Control.prototype.activate.apply(this,arguments);
    },

    /**
     * Method: deactivate
     */
    deactivate: function() {
        if (this.pinchZoom) {
            this.pinchZoom.deactivate();
        }
        this.zoomBox.deactivate();
        this.dragPan.deactivate();
        this.handlers.click.deactivate();
        this.handlers.wheel.deactivate();
        return OpenLayers.Control.prototype.deactivate.apply(this,arguments);
    },
    
    /**
     * Method: draw
     */
    draw: function() {
        // disable right mouse context menu for support of right click events
        if (this.handleRightClicks) {
            this.map.viewPortDiv.oncontextmenu = OpenLayers.Function.False;
        }

        var clickCallbacks = { 
            'click': this.defaultClick,
            '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,
                documentDrag: this.documentDrag
            }, this.dragPanOptions)
        );
        this.zoomBox = new OpenLayers.Control.ZoomBox(
                    {map: this.map, keyMask: this.zoomBoxKeyMask});
        this.dragPan.draw();
        this.zoomBox.draw();
        var wheelOptions = this.map.fractionalZoom ? {} : {
            cumulative: false,
            interval: 50,
            maxDelta: 6
        };
        this.handlers.wheel = new OpenLayers.Handler.MouseWheel(
            this, {up : this.wheelUp, down: this.wheelDown},
            OpenLayers.Util.extend(wheelOptions, this.mouseWheelOptions)
        );
        if (OpenLayers.Control.PinchZoom) {
            this.pinchZoom = new OpenLayers.Control.PinchZoom(
                OpenLayers.Util.extend(
                    {map: this.map}, this.pinchZoomOptions));
        }
    },

    /**
     * Method: defaultClick
     *
     * Parameters:
     * evt - {Event}
     */
    defaultClick: function (evt) {
        if (evt.lastTouches && evt.lastTouches.length == 2) {
            this.map.zoomOut();
        }
    },

    /**
     * Method: defaultDblClick 
     * 
     * Parameters:
     * evt - {Event} 
     */
    defaultDblClick: function (evt) {
        this.map.zoomTo(this.map.zoom + 1, evt.xy);
    },

    /**
     * Method: defaultDblRightClick 
     * 
     * Parameters:
     * evt - {Event} 
     */
    defaultDblRightClick: function (evt) {
        this.map.zoomTo(this.map.zoom - 1, evt.xy);
    },
    
    /**
     * Method: wheelChange  
     *
     * Parameters:
     * evt - {Event}
     * deltaZ - {Integer}
     */
    wheelChange: function(evt, deltaZ) {
        if (!this.map.fractionalZoom) {
            deltaZ =  Math.round(deltaZ);
        }
        var currentZoom = this.map.getZoom(),
            newZoom = currentZoom + deltaZ;
        newZoom = Math.max(newZoom, 0);
        newZoom = Math.min(newZoom, this.map.getNumZoomLevels());
        if (newZoom === currentZoom) {
            return;
        }
        this.map.zoomTo(newZoom, evt.xy);
    },

    /** 
     * Method: wheelUp
     * User spun scroll wheel up
     * 
     * Parameters:
     * evt - {Event}
     * delta - {Integer}
     */
    wheelUp: function(evt, delta) {
        this.wheelChange(evt, delta || 1);
    },

    /** 
     * Method: wheelDown
     * User spun scroll wheel down
     * 
     * Parameters:
     * evt - {Event}
     * delta - {Integer}
     */
    wheelDown: function(evt, delta) {
        this.wheelChange(evt, delta || -1);
    },
    
    /**
     * Method: disableZoomBox
     */
    disableZoomBox : function() {
        this.zoomBoxEnabled = false;
        this.zoomBox.deactivate();       
    },
    
    /**
     * Method: enableZoomBox
     */
    enableZoomBox : function() {
        this.zoomBoxEnabled = true;
        if (this.active) {
            this.zoomBox.activate();
        }    
    },
    
    /**
     * Method: disableZoomWheel
     */
    
    disableZoomWheel : function() {
        this.zoomWheelEnabled = false;
        this.handlers.wheel.deactivate();       
    },
    
    /**
     * Method: enableZoomWheel
     */
    
    enableZoomWheel : function() {
        this.zoomWheelEnabled = true;
        if (this.active) {
            this.handlers.wheel.activate();
        }    
    },

    CLASS_NAME: "OpenLayers.Control.Navigation"
});
/* ======================================================================
    OpenLayers/Layer/WMS.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Layer/Grid.js
 */

/**
 * Class: OpenLayers.Layer.WMS
 * Instances of OpenLayers.Layer.WMS are used to display data from OGC Web
 *     Mapping Services. Create a new WMS layer with the <OpenLayers.Layer.WMS>
 *     constructor.
 * 
 * Inherits from:
 *  - <OpenLayers.Layer.Grid>
 */
OpenLayers.Layer.WMS = OpenLayers.Class(OpenLayers.Layer.Grid, {

    /**
     * Constant: DEFAULT_PARAMS
     * {Object} Hashtable of default parameter key/value pairs 
     */
    DEFAULT_PARAMS: { service: "WMS",
                      version: "1.1.1",
                      request: "GetMap",
                      styles: "",
                      format: "image/jpeg"
                     },
    
    /**
     * APIProperty: isBaseLayer
     * {Boolean} Default is true for WMS layer
     */
    isBaseLayer: 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: noMagic 
     * {Boolean} If true, the image format will not be automagicaly switched 
     *     from image/jpeg to image/png or image/gif when using 
     *     TRANSPARENT=TRUE. Also isBaseLayer will not changed by the  
     *     constructor. Default false. 
     */ 
    noMagic: false,
    
    /**
     * Property: yx
     * {Object} Keys in this object are EPSG codes for which the axis order
     *     is to be reversed (yx instead of xy, LatLon instead of LonLat), with
     *     true as value. This is only relevant for WMS versions >= 1.3.0, and
     *     only if yx is not set in <OpenLayers.Projection.defaults> for the
     *     used projection.
     */
    yx: {},
    
    /**
     * Constructor: OpenLayers.Layer.WMS
     * Create a new WMS layer object
     *
     * Examples:
     *
     * The code below creates a simple WMS layer using the image/jpeg format.
     * (code)
     * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
     *                                    "http://wms.jpl.nasa.gov/wms.cgi", 
     *                                    {layers: "modis,global_mosaic"});
     * (end)
     * Note the 3rd argument (params). Properties added to this object will be
     * added to the WMS GetMap requests used for this layer's tiles. The only
     * mandatory parameter is "layers". Other common WMS params include
     * "transparent", "styles" and "format". Note that the "srs" param will
     * always be ignored. Instead, it will be derived from the baseLayer's or
     * map's projection.
     *
     * The code below creates a transparent WMS layer with additional options.
     * (code)
     * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
     *                                    "http://wms.jpl.nasa.gov/wms.cgi", 
     *                                    {
     *                                        layers: "modis,global_mosaic",
     *                                        transparent: true
     *                                    }, {
     *                                        opacity: 0.5,
     *                                        singleTile: true
     *                                    });
     * (end)
     * Note that by default, a WMS layer is configured as baseLayer. Setting
     * the "transparent" param to true will apply some magic (see <noMagic>).
     * The default image format changes from image/jpeg to image/png, and the
     * layer is not configured as baseLayer.
     *
     * Parameters:
     * name - {String} A name for the layer
     * url - {String} Base url for the WMS
     *                (e.g. http://wms.jpl.nasa.gov/wms.cgi)
     * params - {Object} An object with key/value pairs representing the
     *                   GetMap query string parameters and parameter values.
     * options - {Object} Hashtable of extra options to tag onto the layer.
     *     These options include all properties listed above, plus the ones
     *     inherited from superclasses.
     */
    initialize: function(name, url, params, options) {
        var newArguments = [];
        //uppercase params
        params = OpenLayers.Util.upperCaseObject(params);
        if (parseFloat(params.VERSION) >= 1.3 && !params.EXCEPTIONS) {
            params.EXCEPTIONS = "INIMAGE";
        } 
        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)
                       );


        //layer is transparent        
        if (!this.noMagic && this.params.TRANSPARENT && 
            this.params.TRANSPARENT.toString().toLowerCase() == "true") {
            
            // unless explicitly set in options, make layer an overlay
            if ( (options == null) || (!options.isBaseLayer) ) {
                this.isBaseLayer = false;
            } 
            
            // jpegs can never be transparent, so intelligently switch the 
            //  format, depending on the browser's capabilities
            if (this.params.FORMAT == "image/jpeg") {
                this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif"
                                                                 : "image/png";
            }
        }

    },    

    /**
     * Method: clone
     * Create a clone of this layer
     *
     * Returns:
     * {<OpenLayers.Layer.WMS>} An exact clone of this layer
     */
    clone: function (obj) {
        
        if (obj == null) {
            obj = new OpenLayers.Layer.WMS(this.name,
                                           this.url,
                                           this.params,
                                           this.getOptions());
        }

        //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;
    },    
    
    /**
     * APIMethod: reverseAxisOrder
     * Returns true if the axis order is reversed for the WMS version and
     * projection of the layer.
     * 
     * Returns:
     * {Boolean} true if the axis order is reversed, false otherwise.
     */
    reverseAxisOrder: function() {
        var projCode = this.projection.getCode();
        return parseFloat(this.params.VERSION) >= 1.3 && 
            !!(this.yx[projCode] || (OpenLayers.Projection.defaults[projCode] && 
            OpenLayers.Projection.defaults[projCode].yx));
    },
    
    /**
     * Method: getURL
     * Return a GetMap query string for this layer
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
     *                                request.
     *
     * 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 imageSize = this.getImageSize();
        var newParams = {};
        // WMS 1.3 introduced axis order
        var reverseAxisOrder = this.reverseAxisOrder();
        newParams.BBOX = this.encodeBBOX ?
            bounds.toBBOX(null, reverseAxisOrder) :
            bounds.toArray(reverseAxisOrder);
        newParams.WIDTH = imageSize.w;
        newParams.HEIGHT = imageSize.h;
        var requestString = this.getFullRequestString(newParams);
        return requestString;
    },

    /**
     * APIMethod: mergeNewParams
     * Catch changeParams and uppercase the new params to be merged in
     *     before calling changeParams on the super class.
     * 
     *     Once params have been changed, the tiles will be reloaded with
     *     the new parameters.
     * 
     * Parameters:
     * newParams - {Object} Hashtable of new params to use
     */
    mergeNewParams:function(newParams) {
        var upperParams = OpenLayers.Util.upperCaseObject(newParams);
        var newArguments = [upperParams];
        return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this, 
                                                             newArguments);
    },

    /** 
     * 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
     * 
     * Returns:
     * {String} 
     */
    getFullRequestString:function(newParams, altUrl) {
        var mapProjection = this.map.getProjectionObject();
        var projectionCode = this.projection && this.projection.equals(mapProjection) ?
            this.projection.getCode() :
            mapProjection.getCode();
        var value = (projectionCode == "none") ? null : projectionCode;
        if (parseFloat(this.params.VERSION) >= 1.3) {
            this.params.CRS = value;
        } else {
            this.params.SRS = value;
        }
        
        if (typeof this.params.TRANSPARENT == "boolean") {
            newParams.TRANSPARENT = this.params.TRANSPARENT ? "TRUE" : "FALSE";
        }

        return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
                                                    this, arguments);
    },

    CLASS_NAME: "OpenLayers.Layer.WMS"
});
/* ======================================================================
    OpenLayers/StyleMap.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Style.js
 * @requires OpenLayers/Feature/Vector.js
 */
 
/**
 * Class: OpenLayers.StyleMap
 */
OpenLayers.StyleMap = OpenLayers.Class({
    
    /**
     * Property: styles
     * {Object} Hash of {<OpenLayers.Style>}, keyed by names of well known
     * rendering intents (e.g. "default", "temporary", "select", "delete").
     */
    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"]),
            "delete": new OpenLayers.Style(
                OpenLayers.Feature.Vector.style["delete"])
        };
        
        // 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;
            this.styles["delete"] = 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);
                    this.styles["delete"] = 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/Layer/Vector.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Layer.js
 * @requires OpenLayers/Renderer.js
 * @requires OpenLayers/StyleMap.js
 * @requires OpenLayers/Feature/Vector.js
 * @requires OpenLayers/Console.js
 * @requires OpenLayers/Lang.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, {

    /**
     * APIProperty: events
     * {<OpenLayers.Events>}
     *
     * 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.events>):
     * beforefeatureadded - Triggered before a feature is added.  Listeners
     *      will receive an object with a *feature* property referencing the
     *      feature to be added.  To stop the feature from being added, a
     *      listener should return false.
     * beforefeaturesadded - Triggered before an array of features is added.
     *      Listeners will receive an object with a *features* property
     *      referencing the feature to be added. To stop the features from
     *      being added, a listener should return false.
     * 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.
     * beforefeaturesremoved - Triggered before multiple features are removed. 
     *      Listeners will receive an object with a *features* property
     *      referencing the features 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.
     * beforefeatureselected - Triggered before a feature is selected.  Listeners
     *      will receive an object with a *feature* property referencing the
     *      feature to be selected. To stop the feature from being selectd, a
     *      listener should return false.
     * 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.
     * vertexmodified - Triggered when a vertex within any feature geometry
     *      has been modified.  Listeners will receive an object with a
     *      *feature* property referencing the modified feature, a *vertex*
     *      property referencing the vertex modified (always a point geometry),
     *      and a *pixel* property referencing the pixel location of the
     *      modification.
     * vertexremoved - Triggered when a vertex within any feature geometry
     *      has been deleted.  Listeners will receive an object with a
     *      *feature* property referencing the modified feature, a *vertex*
     *      property referencing the vertex modified (always a point geometry),
     *      and a *pixel* property referencing the pixel location of the
     *      removal.
     * sketchstarted - Triggered when a feature sketch bound for this layer
     *      is started.  Listeners will receive an object with a *feature*
     *      property referencing the new sketch feature and a *vertex* property
     *      referencing the creation point.
     * sketchmodified - Triggered when a feature sketch bound for this layer
     *      is modified.  Listeners will receive an object with a *vertex*
     *      property referencing the modified vertex and a *feature* property
     *      referencing the sketch feature.
     * sketchcomplete - Triggered when a feature sketch bound for this layer
     *      is complete.  Listeners will receive an object with a *feature*
     *      property referencing the sketch feature.  By returning false, a
     *      listener can stop the sketch feature from being added to the layer.
     * refresh - Triggered when something wants a strategy to ask the protocol
     *      for a new set of features.
     */

    /**
     * APIProperty: isBaseLayer
     * {Boolean} The layer is a base layer.  Default is false.  Set this property
     * in the layer options.
     */
    isBaseLayer: false,

    /** 
     * APIProperty: isFixed
     * {Boolean} Whether the layer remains in one place while dragging the
     * map. Note that setting this to true will move the layer to the bottom
     * of the layer stack.
     */
    isFixed: false,

    /** 
     * APIProperty: features
     * {Array(<OpenLayers.Feature.Vector>)} 
     */
    features: null,
    
    /** 
     * Property: filter
     * {<OpenLayers.Filter>} The filter set in this layer,
     *     a strategy launching read requests can combined
     *     this filter with its own filter.
     */
    filter: 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,
    
    /** 
     * APIProperty: ratio
     * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map.
     */   
    ratio: 1,

    /**
     * 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) {
        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: clone
     * Create a clone of this layer.
     * 
     * Note: Features of the layer are also cloned.
     *
     * Returns:
     * {<OpenLayers.Layer.Vector>} An exact clone of this layer
     */
    clone: function (obj) {
        
        if (obj == null) {
            obj = new OpenLayers.Layer.Vector(this.name, this.getOptions());
        }

        //get all additions from superclasses
        obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);

        // copy/set any non-init, non-simple values here
        var features = this.features;
        var len = features.length;
        var clonedFeatures = new Array(len);
        for(var i=0; i<len; ++i) {
            clonedFeatures[i] = features[i].clone();
        }
        obj.features = clonedFeatures;

        return obj;
    },    
    
    /**
     * Method: refresh
     * Ask the layer to request features again and redraw them.  Triggers
     *     the refresh event if the layer is in range and visible.
     *
     * Parameters:
     * obj - {Object} Optional object with properties for any listener of
     *     the refresh event.
     */
    refresh: function(obj) {
        if(this.calculateInRange() && this.visibility) {
            this.events.triggerEvent("refresh", obj);
        }
    },

    /** 
     * 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<len; i++) {
            var rendererClass = this.renderers[i];
            var renderer = (typeof rendererClass == "function") ?
                rendererClass :
                OpenLayers.Renderer[rendererClass];
            if (renderer && renderer.prototype.supported()) {
                this.renderer = new renderer(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;

            var newSize = this.map.getSize();
            newSize.w = newSize.w * this.ratio;
            newSize.h = newSize.h * this.ratio;
            this.renderer.setSize(newSize);
        }
    },

    /**
     * Method: afterAdd
     * Called at the end of the map.addLayer sequence.  At this point, the map
     *     will have a base layer.  Any autoActivate strategies will be
     *     activated here.
     */
    afterAdd: function() {
        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) {
        this.drawn = false;
        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);
        
        var newSize = this.map.getSize();
        newSize.w = newSize.w * this.ratio;
        newSize.h = newSize.h * this.ratio;
        this.renderer.setSize(newSize);
    },

    /**
     * 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';

            var viewSize = this.map.getSize(),
                viewWidth = viewSize.w,
                viewHeight = viewSize.h,
                offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2,
                offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2;
            offsetLeft += this.map.layerContainerOriginPx.x;
            offsetLeft = -Math.round(offsetLeft);
            offsetTop += this.map.layerContainerOriginPx.y;
            offsetTop = -Math.round(offsetTop);

            this.div.style.left = offsetLeft + 'px';
            this.div.style.top = offsetTop + 'px';

            var extent = this.map.getExtent().scale(this.ratio);
            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 (OpenLayers.IS_GECKO === true) {
                this.div.scrollLeft = this.div.scrollLeft;
            }
            
            if (!zoomChanged && coordSysUnchanged) {
                for (var i in this.unrenderedFeatures) {
                    var feature = this.unrenderedFeatures[i];
                    this.drawFeature(feature);
                }
            }
        }
        if (!this.drawn || zoomChanged || !coordSysUnchanged) {
            this.drawn = true;
            var feature;
            for(var i=0, len=this.features.length; i<len; i++) {
                this.renderer.locked = (i !== (len - 1));
                feature = this.features[i];
                this.drawFeature(feature);
            }
        }    
    },
    
    /** 
     * APIMethod: display
     * Hide or show the Layer
     * 
     * Parameters:
     * display - {Boolean}
     */
    display: function(display) {
        OpenLayers.Layer.prototype.display.apply(this, arguments);
        // we need to set the display style of the root in case it is attached
        // to a foreign layer
        var currentDisplay = this.div.style.display;
        if(currentDisplay != this.renderer.root.style.display) {
            this.renderer.root.style.display = currentDisplay;
        }
    },

    /**
     * APIMethod: addFeatures
     * Add Features to the layer.
     *
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>)} 
     * options - {Object}
     */
    addFeatures: function(features, options) {
        if (!(OpenLayers.Util.isArray(features))) {
            features = [features];
        }
        
        var notify = !options || !options.silent;
        if(notify) {
            var event = {features: features};
            var ret = this.events.triggerEvent("beforefeaturesadded", event);
            if(ret === false) {
                return;
            }
            features = event.features;
        }
        
        // Track successfully added features for featuresadded event, since
        // beforefeatureadded can veto single features.
        var featuresAdded = [];
        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)) {
                throw new TypeError('addFeatures: component should be an ' +
                                    this.geometryType.prototype.CLASS_NAME);
              }

            //give feature reference to its layer
            feature.layer = this;

            if (!feature.style && this.style) {
                feature.style = OpenLayers.Util.extend({}, this.style);
            }

            if (notify) {
                if(this.events.triggerEvent("beforefeatureadded",
                                            {feature: feature}) === false) {
                    continue;
                }
                this.preFeatureInsert(feature);
            }

            featuresAdded.push(feature);
            this.features.push(feature);
            this.drawFeature(feature);
            
            if (notify) {
                this.events.triggerEvent("featureadded", {
                    feature: feature
                });
                this.onFeatureInsert(feature);
            }
        }
        
        if(notify) {
            this.events.triggerEvent("featuresadded", {features: featuresAdded});
        }
    },


    /**
     * APIMethod: removeFeatures
     * Remove features from the layer.  This erases any drawn features and
     *     removes them from the layer's control.  The beforefeatureremoved
     *     and featureremoved events will be triggered for each feature.  The
     *     featuresremoved event will be triggered after all features have
     *     been removed.  To supress event triggering, use the silent option.
     * 
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be
     *     removed.
     * options - {Object} Optional properties for changing behavior of the
     *     removal.
     *
     * Valid options:
     * silent - {Boolean} Supress event triggering.  Default is false.
     */
    removeFeatures: function(features, options) {
        if(!features || features.length === 0) {
            return;
        }
        if (features === this.features) {
            return this.removeAllFeatures(options);
        }
        if (!(OpenLayers.Util.isArray(features))) {
            features = [features];
        }
        if (features === this.selectedFeatures) {
            features = features.slice();
        }

        var notify = !options || !options.silent;
        
        if (notify) {
            this.events.triggerEvent(
                "beforefeaturesremoved", {features: features}
            );
        }

        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.eraseFeatures(feature);
            }
                    
            //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: removeAllFeatures
     * Remove all features from the layer.
     *
     * Parameters:
     * options - {Object} Optional properties for changing behavior of the
     *     removal.
     *
     * Valid options:
     * silent - {Boolean} Supress event triggering.  Default is false.
     */
    removeAllFeatures: function(options) {
        var notify = !options || !options.silent;
        var features = this.features;
        if (notify) {
            this.events.triggerEvent(
                "beforefeaturesremoved", {features: features}
            );
        }
        var feature;
        for (var i = features.length-1; i >= 0; i--) {
            feature = features[i];
            if (notify) {
                this.events.triggerEvent("beforefeatureremoved", {
                    feature: feature
                });
            }
            feature.layer = null;
            if (notify) {
                this.events.triggerEvent("featureremoved", {
                    feature: feature
                });
            }
        }
        this.renderer.clear();
        this.features = [];
        this.unrenderedFeatures = {};
        this.selectedFeatures = [];
        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;
        }
        if(features) {
            this.removeFeatures(features, options);
            for(var i=features.length-1; i>=0; 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.
     * 
     * This function is not designed to be used when adding features to 
     * the layer (use addFeatures instead). It is meant to be used when
     * the style of a feature has changed, or in some other way needs to 
     * visually updated *after* it has already been added to a layer. You
     * must add the feature to the layer for most layer-related events to 
     * happen.
     *
     * Parameters: 
     * feature - {<OpenLayers.Feature.Vector>} 
     * style - {String | Object} Named render intent or full symbolizer object.
     */
    drawFeature: function(feature, style) {
        // don't try to draw the feature with the renderer if the layer is not 
        // drawn itself
        if (!this.drawn) {
            return;
        }
        if (typeof style != "object") {
            if(!style && feature.state === OpenLayers.State.DELETE) {
                style = "delete";
            }
            var renderIntent = style || feature.renderIntent;
            style = feature.style || this.style;
            if (!style) {
                style = this.styleMap.createSymbolizer(feature, renderIntent);
            }
        }
        
        var drawn = this.renderer.drawFeature(feature, style);
        //TODO remove the check for null when we get rid of Renderer.SVG
        if (drawn === false || drawn === null) {
            this.unrenderedFeatures[feature.id] = feature;
        } else {
            delete this.unrenderedFeatures[feature.id];
        }
    },
    
    /**
     * 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) {
            throw new Error('getFeatureFromEvent called on layer with no ' +
                            'renderer. This usually means you destroyed a ' +
                            'layer, but not some handler which is associated ' +
                            'with it.');
        }
        var feature = null;
        var featureId = this.renderer.getFeatureIdFromEvent(evt);
        if (featureId) {
            if (typeof featureId === "string") {
                feature = this.getFeatureById(featureId);
            } else {
                feature = featureId;
            }
        }
        return feature;
    },

    /**
     * APIMethod: getFeatureBy
     * Given a property value, return the feature if it exists in the features array
     *
     * Parameters:
     * property - {String}
     * value - {String}
     *
     * Returns:
     * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
     * property value or null if there is no such feature.
     */
    getFeatureBy: function(property, value) {
        //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][property] == value) {
                feature = this.features[i];
                break;
            }
        }
        return feature;
    },

    /**
     * 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 or null if there is no such feature.
     */
    getFeatureById: function(featureId) {
        return this.getFeatureBy('id', featureId);
    },

    /**
     * APIMethod: getFeatureByFid
     * Given a feature fid, return the feature if it exists in the features array
     *
     * Parameters:
     * featureFid - {String}
     *
     * Returns:
     * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
     * featureFid or null if there is no such feature.
     */
    getFeatureByFid: function(featureFid) {
        return this.getFeatureBy('fid', featureFid);
    },
    
    /**
     * APIMethod: getFeaturesByAttribute
     * Returns an array of features that have the given attribute key set to the
     * given value. Comparison of attribute values takes care of datatypes, e.g.
     * the string '1234' is not equal to the number 1234.
     *
     * Parameters:
     * attrName - {String}
     * attrValue - {Mixed}
     *
     * Returns:
     * Array({<OpenLayers.Feature.Vector>}) An array of features that have the 
     * passed named attribute set to the given value.
     */
    getFeaturesByAttribute: function(attrName, attrValue) {
        var i,
            feature,    
            len = this.features.length,
            foundFeatures = [];
        for(i = 0; i < len; i++) {            
            feature = this.features[i];
            if(feature && feature.attributes) {
                if (feature.attributes[attrName] === attrValue) {
                    foundFeatures.push(feature);
                }
            }
        }
        return foundFeatures;
    },

    /**
     * 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.
     *
     * Parameters: 
     * 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>} or null if the layer has no features with
     * geometries.
     */
    getDataExtent: function () {
        var maxExtent = null;
        var features = this.features;
        if(features && (features.length > 0)) {
            var geometry = null;
            for(var i=0, len=features.length; i<len; i++) {
                geometry = features[i].geometry;
                if (geometry) {
                    if (maxExtent === null) {
                        maxExtent = new OpenLayers.Bounds();
                    }
                    maxExtent.extend(geometry.getBounds());
                }
            }
        }
        return maxExtent;
    },

    CLASS_NAME: "OpenLayers.Layer.Vector"
});
/* ======================================================================
    OpenLayers/Renderer/SVG.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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: symbolMetrics
     * {Object} Cache for symbol metrics according to their svg coordinate
     *     space. This is an object keyed by the symbol's id, and values are
     *     an array of [width, centerX, centerY].
     */
    symbolMetrics: null,
    
    /**
     * 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};
        
        this.symbolMetrics = {};
    },

    /**
     * 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) {
        var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
        
        var resolution = this.getResolution(),
            left = -extent.left / resolution,
            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(this.xOffset, 0);
            return true;
        } else {
            var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
            if (!inRange) {
                // recenter the coordinate system
                this.setExtent(extent, true);
            }
            return coordSysUnchanged && inRange;
        }
    },
    
    /**
     * Method: translate
     * Transforms the SVG coordinate system
     * 
     * Parameters:
     * x - {Float}
     * y - {Float}
     * 
     * Returns:
     * {Boolean} true if the translation parameters are 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 = "svg";
                } 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":
                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 title = style.title || style.graphicTitle;
        if (title) {
            node.setAttributeNS(null, "title", title);
            //Standards-conformant SVG
            // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92 
            var titleNode = node.getElementsByTagName("title");
            if (titleNode.length > 0) {
                titleNode[0].firstChild.textContent = title;
            } else {
                var label = this.nodeFactory(null, "title");
                label.textContent = title;
                node.appendChild(label);
            }
        }

        var r = parseFloat(node.getAttributeNS(null, "r"));
        var widthFactor = 1;
        var pos;
        if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
            node.style.visibility = "";
            if (style.graphic === false) {
                node.style.visibility = "hidden";
            } else 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, "xlink:href", style.externalGraphic);
                node.setAttributeNS(null, "style", "opacity: "+opacity);
                node.onclick = OpenLayers.Event.preventDefault;
            } 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 src = this.importSymbol(style.graphicName);
                pos = this.getPosition(node);
                widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
                
                // remove the node from the dom before we modify it. This
                // prevents various rendering issues in Safari and FF
                var parent = node.parentNode;
                var nextSibling = node.nextSibling;
                if(parent) {
                    parent.removeChild(node);
                }
                
                // The more appropriate way to implement this would be use/defs,
                // but due to various issues in several browsers, it is safer to
                // copy the symbols instead of referencing them. 
                // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985 
                // and this email thread
                // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
                node.firstChild && node.removeChild(node.firstChild);
                node.appendChild(src.firstChild.cloneNode(true));
                node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
                
                node.setAttributeNS(null, "width", size);
                node.setAttributeNS(null, "height", size);
                node.setAttributeNS(null, "x", pos.x - offset);
                node.setAttributeNS(null, "y", pos.y - offset);
                
                // now that the node has all its new properties, insert it
                // back into the dom where it was
                if(nextSibling) {
                    parent.insertBefore(node, nextSibling);
                } else if(parent) {
                    parent.appendChild(node);
                }
            } else {
                node.setAttributeNS(null, "r", style.pointRadius);
            }

            var rotation = style.rotation;
            
            if ((rotation !== undefined || node._rotation !== undefined) && pos) {
                node._rotation = rotation;
                rotation |= 0;
                if (node.nodeName !== "svg") { 
                    node.setAttributeNS(null, "transform", 
                        "rotate(" + rotation + " " + pos.x + " " + 
                        pos.y + ")"); 
                } else {
                    var metrics = this.symbolMetrics[src.id];
                    node.firstChild.setAttributeNS(null, "transform", "rotate(" 
                        + rotation + " " 
                        + metrics[1] + " "
                        + metrics[2] + ")");
                }
            }
        }
        
        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 || "round");
            // 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");
            style.strokeDashstyle && 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;
        var str = style.strokeDashstyle;
        switch (str) {
            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 OpenLayers.String.trim(str).replace(/\s+/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() {
        var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
        svg.style.display = "block";
        return svg;
    },

    /**
     * Method: createRoot
     * 
     * Parameters:
     * suffix - {String} suffix to append to the id
     * 
     * Returns:
     * {DOMElement}
     */
    createRoot: function(suffix) {
        return this.nodeFactory(this.container.id + suffix, "g");
    },

    /**
     * Method: createDefs
     *
     * Returns:
     * {DOMElement} The element to which we'll add the symbol definitions
     */
    createDefs: function() {
        var defs = this.nodeFactory(this.container.id + "_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 - this.featureDx) / 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 - this.featureDx) / 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: drawText
     * This method is only called by the renderer itself.
     *
     * Parameters:
     * featureId - {String}
     * style -
     * location - {<OpenLayers.Geometry.Point>}
     */
    drawText: function(featureId, style, location) {
        var drawOutline = (!!style.labelOutlineWidth);
        // First draw text in halo color and size and overlay the
        // normal text afterwards
        if (drawOutline) {
            var outlineStyle = OpenLayers.Util.extend({}, style);
            outlineStyle.fontColor = outlineStyle.labelOutlineColor;
            outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
            outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
            if (style.labelOutlineOpacity) {
                outlineStyle.fontOpacity = style.labelOutlineOpacity;
            }
            delete outlineStyle.labelOutlineWidth;
            this.drawText(featureId, outlineStyle, location);
        }

        var resolution = this.getResolution();

        var x = ((location.x - this.featureDx) / resolution + this.left);
        var y = (location.y / resolution - this.top);

        var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
        var label = this.nodeFactory(featureId + suffix, "text");

        label.setAttributeNS(null, "x", x);
        label.setAttributeNS(null, "y", -y);

        if (style.fontColor) {
            label.setAttributeNS(null, "fill", style.fontColor);
        }
        if (style.fontStrokeColor) {
            label.setAttributeNS(null, "stroke", style.fontStrokeColor);
        }
        if (style.fontStrokeWidth) {
            label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
        }
        if (style.fontOpacity) {
            label.setAttributeNS(null, "opacity", style.fontOpacity);
        }
        if (style.fontFamily) {
            label.setAttributeNS(null, "font-family", style.fontFamily);
        }
        if (style.fontSize) {
            label.setAttributeNS(null, "font-size", style.fontSize);
        }
        if (style.fontWeight) {
            label.setAttributeNS(null, "font-weight", style.fontWeight);
        }
        if (style.fontStyle) {
            label.setAttributeNS(null, "font-style", style.fontStyle);
        }
        if (style.labelSelect === true) {
            label.setAttributeNS(null, "pointer-events", "visible");
            label._featureId = featureId;
        } else {
            label.setAttributeNS(null, "pointer-events", "none");
        }
        var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
        label.setAttributeNS(null, "text-anchor",
            OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");

        if (OpenLayers.IS_GECKO === true) {
            label.setAttributeNS(null, "dominant-baseline",
                OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
        }

        var labelRows = style.label.split('\n');
        var numRows = labelRows.length;
        while (label.childNodes.length > numRows) {
            label.removeChild(label.lastChild);
        }
        for (var i = 0; i < numRows; i++) {
            var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
            if (style.labelSelect === true) {
                tspan._featureId = featureId;
                tspan._geometry = location;
                tspan._geometryClass = location.CLASS_NAME;
            }
            if (OpenLayers.IS_GECKO === false) {
                tspan.setAttributeNS(null, "baseline-shift",
                    OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
            }
            tspan.setAttribute("x", x);
            if (i == 0) {
                var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
                if (vfactor == null) {
                     vfactor = -.5;
                }
                tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
            } else {
                tspan.setAttribute("dy", "1em");
            }
            tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
            if (!tspan.parentNode) {
                label.appendChild(tspan);
            }
        }

        if (!label.parentNode) {
            this.textRoot.appendChild(label);
        }
    },
    
    /** 
     * 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;
        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 - this.featureDx) / resolution + this.left;
        var y1 = this.top - goodComponent.y / resolution;
        var x2 = (badComponent.x - this.featureDx) / 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 - this.featureDx) / 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:
     * {DOMElement} - 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
        var existing = document.getElementById(id);
        if (existing != null) {
            return existing;
        }
        
        var symbol = OpenLayers.Renderer.symbol[graphicName];
        if (!symbol) {
            throw new Error(graphicName + ' is not a valid symbol name');
        }

        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.push(x, ",", y);
        }
        
        node.setAttributeNS(null, "points", points.join(" "));
        
        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.symbolMetrics[id] = [
            Math.max(width, height),
            symbolExtent.getCenterLonLat().lon,
            symbolExtent.getCenterLonLat().lat
        ];
        
        this.defs.appendChild(symbolNode);
        return symbolNode;
    },
    
    /**
     * Method: getFeatureIdFromEvent
     * 
     * Parameters:
     * evt - {Object} An <OpenLayers.Event> object
     *
     * Returns:
     * {String} A feature id or undefined.
     */
    getFeatureIdFromEvent: function(evt) {
        var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
        if(!featureId) {
            var target = evt.target;
            featureId = target.parentNode && target != this.rendererRoot ?
                target.parentNode._featureId : undefined;
        }
        return featureId;
    },

    CLASS_NAME: "OpenLayers.Renderer.SVG"
});

/**
 * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
 * {Object}
 */
OpenLayers.Renderer.SVG.LABEL_ALIGN = {
    "l": "start",
    "r": "end",
    "b": "bottom",
    "t": "hanging"
};

/**
 * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
 * {Object}
 */
OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
    // according to
    // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
    // a baseline-shift of -70% shifts the text exactly from the
    // bottom to the top of the baseline, so -35% moves the text to
    // the center of the baseline.
    "t": "-70%",
    "b": "0"    
};

/**
 * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
 * {Object}
 */
OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
    "t": 0,
    "b": -1
};

/**
 * Function: OpenLayers.Renderer.SVG.preventDefault
 * *Deprecated*.  Use <OpenLayers.Event.preventDefault> method instead.
 * Used to prevent default events (especially opening images in a new tab on
 * ctrl-click) from being executed for externalGraphic symbols
 */
OpenLayers.Renderer.SVG.preventDefault = function(e) {
    OpenLayers.Event.preventDefault(e);
};
/* ======================================================================
    OpenLayers/Control/ScaleLine.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control.js
 */

/**
 * Class: OpenLayers.Control.ScaleLine
 * The ScaleLine displays a small line indicator representing the current 
 * map scale on the map. By default it is drawn in the lower left corner of
 * the map.
 * 
 * Inherits from:
 *  - <OpenLayers.Control>
 *  
 * Is a very close copy of:
 *  - <OpenLayers.Control.Scale>
 */
OpenLayers.Control.ScaleLine = OpenLayers.Class(OpenLayers.Control, {

    /**
     * Property: maxWidth
     * {Integer} Maximum width of the scale line in pixels.  Default is 100.
     */
    maxWidth: 100,

    /**
     * Property: topOutUnits
     * {String} Units for zoomed out on top bar.  Default is km.
     */
    topOutUnits: "km",
    
    /**
     * Property: topInUnits
     * {String} Units for zoomed in on top bar.  Default is m.
     */
    topInUnits: "m",

    /**
     * Property: bottomOutUnits
     * {String} Units for zoomed out on bottom bar.  Default is mi.
     */
    bottomOutUnits: "mi",

    /**
     * Property: bottomInUnits
     * {String} Units for zoomed in on bottom bar.  Default is ft.
     */
    bottomInUnits: "ft",
    
    /**
     * Property: eTop
     * {DOMElement}
     */
    eTop: null,

    /**
     * Property: eBottom
     * {DOMElement}
     */
    eBottom:null,
    
    /**
     * APIProperty: geodesic
     * {Boolean} Use geodesic measurement. Default is false. The recommended
     * setting for maps in EPSG:4326 is false, and true EPSG:900913. If set to
     * true, the scale will be calculated based on the horizontal size of the
     * pixel in the center of the map viewport.
     */
    geodesic: false,

    /**
     * Constructor: OpenLayers.Control.ScaleLine
     * Create a new scale line control.
     * 
     * Parameters:
     * options - {Object} An optional object whose properties will be used
     *     to extend the control.
     */

    /**
     * Method: draw
     * 
     * Returns:
     * {DOMElement}
     */
    draw: function() {
        OpenLayers.Control.prototype.draw.apply(this, arguments);
        if (!this.eTop) {
            // stick in the top bar
            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";
            }

            // and the bottom bar
            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;
    },

    /** 
     * Method: getBarLen
     * Given a number, round it down to the nearest 1,2,5 times a power of 10.
     * That seems a fairly useful set of number groups to use.
     * 
     * Parameters:
     * maxLen - {float}  the number we're rounding down from
     * 
     * Returns:
     * {Float} the rounded number (less than or equal to maxLen)
     */
    getBarLen: function(maxLen) {
        // nearest power of 10 lower than maxLen
        var digits = parseInt(Math.log(maxLen) / Math.log(10));
        var pow10 = Math.pow(10, digits);
        
        // ok, find first character
        var firstChar = parseInt(maxLen / pow10);

        // right, put it into the correct bracket
        var barLen;
        if(firstChar > 5) {
            barLen = 5;
        } else if(firstChar > 2) {
            barLen = 2;
        } else {
            barLen = 1;
        }

        // scale it up the correct power of 10
        return barLen * pow10;
    },

    /**
     * Method: update
     * Update the size of the bars, and the labels they contain.
     */
    update: function() {
        var res = this.map.getResolution();
        if (!res) {
            return;
        }

        var curMapUnits = this.map.getUnits();
        var inches = OpenLayers.INCHES_PER_UNIT;

        // convert maxWidth to map units
        var maxSizeData = this.maxWidth * res * inches[curMapUnits];
        var geodesicRatio = 1;
        if(this.geodesic === true) {
            var maxSizeGeodesic = (this.map.getGeodesicPixelSize().w ||
                0.000001) * this.maxWidth;
            var maxSizeKilometers = maxSizeData / inches["km"];
            geodesicRatio = maxSizeGeodesic / maxSizeKilometers;
            maxSizeData *= geodesicRatio;
        }

        // decide whether to use large or small scale units     
        var topUnits;
        var bottomUnits;
        if(maxSizeData > 100000) {
            topUnits = this.topOutUnits;
            bottomUnits = this.bottomOutUnits;
        } else {
            topUnits = this.topInUnits;
            bottomUnits = this.bottomInUnits;
        }

        // and to map units units
        var topMax = maxSizeData / inches[topUnits];
        var bottomMax = maxSizeData / inches[bottomUnits];

        // now trim this down to useful block length
        var topRounded = this.getBarLen(topMax);
        var bottomRounded = this.getBarLen(bottomMax);

        // and back to display units
        topMax = topRounded / inches[curMapUnits] * inches[topUnits];
        bottomMax = bottomRounded / inches[curMapUnits] * inches[bottomUnits];

        // and to pixel units
        var topPx = topMax / res / geodesicRatio;
        var bottomPx = bottomMax / res / geodesicRatio;
        
        // now set the pixel widths
        // and the values inside them
        
        if (this.eBottom.style.visibility == "visible"){
            this.eBottom.style.width = Math.round(bottomPx) + "px"; 
            this.eBottom.innerHTML = bottomRounded + " " + bottomUnits ;
        }
            
        if (this.eTop.style.visibility == "visible"){
            this.eTop.style.width = Math.round(topPx) + "px";
            this.eTop.innerHTML = topRounded + " " + topUnits;
        }
        
    }, 

    CLASS_NAME: "OpenLayers.Control.ScaleLine"
});

/* ======================================================================
    OpenLayers/Control/PanZoom.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Events/buttonclick.js
 */

/**
 * Class: OpenLayers.Control.PanZoom
 * The PanZoom is a visible control, composed of a
 * <OpenLayers.Control.PanPanel> and a <OpenLayers.Control.ZoomPanel>. By
 * default it is drawn in the upper left corner of the map.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.PanZoom = OpenLayers.Class(OpenLayers.Control, {

    /** 
     * APIProperty: slideFactor
     * {Integer} Number of pixels by which we'll pan the map in any direction 
     *     on clicking the arrow buttons.  If you want to pan by some ratio
     *     of the map dimensions, use <slideRatio> instead.
     */
    slideFactor: 50,

    /** 
     * APIProperty: slideRatio
     * {Number} The fraction of map width/height by which we'll pan the map            
     *     on clicking the arrow buttons.  Default is null.  If set, will
     *     override <slideFactor>. E.g. if slideRatio is .5, then the Pan Up
     *     button will pan up half the map height. 
     */
    slideRatio: null,

    /** 
     * Property: buttons
     * {Array(DOMElement)} Array of Button Divs 
     */
    buttons: null,

    /** 
     * Property: position
     * {<OpenLayers.Pixel>} 
     */
    position: null,

    /**
     * Constructor: OpenLayers.Control.PanZoom
     * 
     * Parameters:
     * options - {Object}
     */
    initialize: function(options) {
        this.position = new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,
                                             OpenLayers.Control.PanZoom.Y);
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
    },

    /**
     * APIMethod: destroy
     */
    destroy: function() {
        if (this.map) {
            this.map.events.unregister("buttonclick", this, this.onButtonClick);
        }
        this.removeButtons();
        this.buttons = null;
        this.position = null;
        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.register("buttonclick", this, this.onButtonClick);
    },

    /**
     * Method: draw
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>} 
     * 
     * Returns:
     * {DOMElement} A reference to the container div for the PanZoom control.
     */
    draw: function(px) {
        // initialize our internal div
        OpenLayers.Control.prototype.draw.apply(this, arguments);
        px = this.position;

        // place the controls
        this.buttons = [];

        var sz = {w: 18, h: 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;
    },
    
    /**
     * Method: _addButton
     * 
     * Parameters:
     * id - {String} 
     * img - {String} 
     * xy - {<OpenLayers.Pixel>} 
     * sz - {<OpenLayers.Size>} 
     * 
     * Returns:
     * {DOMElement} A Div (an alphaImageDiv, to be precise) that contains the
     *     image of the button, and has all the proper event handlers set.
     */
    _addButton:function(id, img, xy, sz) {
        var imgLocation = OpenLayers.Util.getImageLocation(img);
        var btn = OpenLayers.Util.createAlphaImageDiv(
                                    this.id + "_" + id, 
                                    xy, sz, imgLocation, "absolute");
        btn.style.cursor = "pointer";
        //we want to add the outer div
        this.div.appendChild(btn);
        btn.action = id;
        btn.className = "olButton";
    
        //we want to remember/reference the outer div
        this.buttons.push(btn);
        return btn;
    },
    
    /**
     * Method: _removeButton
     * 
     * Parameters:
     * btn - {Object}
     */
    _removeButton: function(btn) {
        this.div.removeChild(btn);
        OpenLayers.Util.removeItem(this.buttons, btn);
    },
    
    /**
     * Method: removeButtons
     */
    removeButtons: function() {
        for(var i=this.buttons.length-1; i>=0; --i) {
            this._removeButton(this.buttons[i]);
        }
    },
    
    /**
     * Method: onButtonClick
     *
     * Parameters:
     * evt - {Event}
     */
    onButtonClick: function(evt) {
        var btn = evt.buttonElement;
        switch (btn.action) {
            case "panup": 
                this.map.pan(0, -this.getSlideFactor("h"));
                break;
            case "pandown": 
                this.map.pan(0, this.getSlideFactor("h"));
                break;
            case "panleft": 
                this.map.pan(-this.getSlideFactor("w"), 0);
                break;
            case "panright": 
                this.map.pan(this.getSlideFactor("w"), 0);
                break;
            case "zoomin": 
                this.map.zoomIn(); 
                break;
            case "zoomout": 
                this.map.zoomOut(); 
                break;
            case "zoomworld": 
                this.map.zoomToMaxExtent(); 
                break;
        }
    },
    
    /**
     * Method: getSlideFactor
     *
     * Parameters:
     * dim - {String} "w" or "h" (for width or height).
     *
     * Returns:
     * {Number} The slide factor for panning in the requested direction.
     */
    getSlideFactor: function(dim) {
        return this.slideRatio ?
            this.map.getSize()[dim] * this.slideRatio :
            this.slideFactor;
    },

    CLASS_NAME: "OpenLayers.Control.PanZoom"
});

/**
 * Constant: X
 * {Integer}
 */
OpenLayers.Control.PanZoom.X = 4;

/**
 * Constant: Y
 * {Integer}
 */
OpenLayers.Control.PanZoom.Y = 4;
/* ======================================================================
    OpenLayers/Icon.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.Icon
 * 
 * The icon represents a graphical icon on the screen.  Typically used in
 * conjunction with a <OpenLayers.Marker> to represent markers on a screen.
 *
 * An icon has a url, size and position.  It also contains an offset which 
 * allows the center point to be represented correctly.  This can be
 * provided either as a fixed offset or a function provided to calculate
 * the desired offset. 
 * 
 */
OpenLayers.Icon = OpenLayers.Class({
    
    /** 
     * Property: url 
     * {String}  image url
     */
    url: null,
    
    /** 
     * Property: size 
     * {<OpenLayers.Size>|Object} An OpenLayers.Size or
     * an object with a 'w' and 'h' properties.
     */
    size: null,

    /** 
     * Property: offset 
     * {<OpenLayers.Pixel>|Object} distance in pixels to offset the
     * image when being rendered. An OpenLayers.Pixel or an object
     * with a 'x' and 'y' properties.
     */
    offset: null,    
    
    /** 
     * Property: calculateOffset 
     * {Function} Function to calculate the offset (based on the size)
     */
    calculateOffset: null,    
    
    /** 
     * Property: imageDiv 
     * {DOMElement} 
     */
    imageDiv: null,

    /** 
     * Property: px 
     * {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object
     * with a 'x' and 'y' properties.
     */
    px: null,
    
    /** 
     * Constructor: OpenLayers.Icon
     * Creates an icon, which is an image tag in a div.  
     *
     * url - {String} 
     * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or an
     *                                   object with a 'w' and 'h'
     *                                   properties.
     * offset - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
     *                                      object with a 'x' and 'y'
     *                                      properties.
     * calculateOffset - {Function} 
     */
    initialize: function(url, size, offset, calculateOffset) {
        this.url = url;
        this.size = size || {w: 20, h: 20};
        this.offset = offset || {x: -(this.size.w/2), y: -(this.size.h/2)};
        this.calculateOffset = calculateOffset;

        var id = OpenLayers.Util.createUniqueID("OL_Icon_");
        this.imageDiv = OpenLayers.Util.createAlphaImageDiv(id);
    },
    
    /** 
     * Method: destroy
     * Nullify references and remove event listeners to prevent circular 
     * references and memory leaks
     */
    destroy: function() {
        // erase any drawn elements
        this.erase();

        OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild); 
        this.imageDiv.innerHTML = "";
        this.imageDiv = null;
    },

    /** 
     * Method: clone
     * 
     * Returns:
     * {<OpenLayers.Icon>} A fresh copy of the icon.
     */
    clone: function() {
        return new OpenLayers.Icon(this.url, 
                                   this.size, 
                                   this.offset, 
                                   this.calculateOffset);
    },
    
    /**
     * Method: setSize
     * 
     * Parameters:
     * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or
     * an object with a 'w' and 'h' properties.
     */
    setSize: function(size) {
        if (size != null) {
            this.size = size;
        }
        this.draw();
    },
    
    /**
     * Method: setUrl
     * 
     * Parameters:
     * url - {String} 
     */
    setUrl: function(url) {
        if (url != null) {
            this.url = url;
        }
        this.draw();
    },

    /** 
     * Method: draw
     * Move the div to the given pixel.
     * 
     * Parameters:
     * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
     *                                  object with a 'x' and 'y' properties.
     * 
     * Returns:
     * {DOMElement} A new DOM Image of this icon set at the location passed-in
     */
    draw: function(px) {
        OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, 
                                            null, 
                                            null, 
                                            this.size, 
                                            this.url, 
                                            "absolute");
        this.moveTo(px);
        return this.imageDiv;
    }, 

    /** 
     * Method: erase
     * Erase the underlying image element.
     */
    erase: function() {
        if (this.imageDiv != null && this.imageDiv.parentNode != null) {
            OpenLayers.Element.remove(this.imageDiv);
        }
    }, 
    
    /** 
     * Method: setOpacity
     * Change the icon's opacity
     *
     * Parameters:
     * opacity - {float} 
     */
    setOpacity: function(opacity) {
        OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null, 
                                            null, null, null, null, opacity);

    },
    
    /**
     * Method: moveTo
     * move icon to passed in px.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
     * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
     */
    moveTo: function (px) {
        //if no px passed in, use stored location
        if (px != null) {
            this.px = px;
        }

        if (this.imageDiv != null) {
            if (this.px == null) {
                this.display(false);
            } else {
                if (this.calculateOffset) {
                    this.offset = this.calculateOffset(this.size);  
                }
                OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, {
                    x: this.px.x + this.offset.x,
                    y: this.px.y + this.offset.y
                });
            }
        }
    },
    
    /** 
     * Method: display
     * Hide or show the icon
     *
     * Parameters:
     * display - {Boolean} 
     */
    display: function(display) {
        this.imageDiv.style.display = (display) ? "" : "none"; 
    },
    

    /**
     * APIMethod: isDrawn
     * 
     * Returns:
     * {Boolean} Whether or not the icon is drawn.
     */
    isDrawn: function() {
        // nodeType 11 for ie, whose nodes *always* have a parentNode
        // (of type document fragment)
        var isDrawn = (this.imageDiv && this.imageDiv.parentNode && 
                       (this.imageDiv.parentNode.nodeType != 11));    

        return isDrawn;   
    },

    CLASS_NAME: "OpenLayers.Icon"
});
/* ======================================================================
    OpenLayers/Marker.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Events.js
 * @requires OpenLayers/Icon.js
 */

/**
 * Class: OpenLayers.Marker
 * Instances of OpenLayers.Marker are a combination of a 
 * <OpenLayers.LonLat> and an <OpenLayers.Icon>.  
 *
 * Markers are generally added to a special layer called
 * <OpenLayers.Layer.Markers>.
 *
 * Example:
 * (code)
 * var markers = new OpenLayers.Layer.Markers( "Markers" );
 * map.addLayer(markers);
 *
 * var size = new OpenLayers.Size(21,25);
 * var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
 * var icon = new OpenLayers.Icon('http://www.openlayers.org/dev/img/marker.png', size, offset);
 * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon));
 * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon.clone()));
 *
 * (end)
 *
 * Note that if you pass an icon into the Marker constructor, it will take
 * that icon and use it. This means that you should not share icons between
 * markers -- you use them once, but you should clone() for any additional
 * markers using that same icon.
 */
OpenLayers.Marker = OpenLayers.Class({
    
    /** 
     * Property: icon 
     * {<OpenLayers.Icon>} The icon used by this marker.
     */
    icon: null,

    /** 
     * Property: lonlat 
     * {<OpenLayers.LonLat>} location of object
     */
    lonlat: null,
    
    /** 
     * Property: events 
     * {<OpenLayers.Events>} the event handler.
     */
    events: null,
    
    /** 
     * Property: map 
     * {<OpenLayers.Map>} the map this marker is attached to
     */
    map: null,
    
    /** 
     * Constructor: OpenLayers.Marker
     *
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>} the position of this marker
     * icon - {<OpenLayers.Icon>}  the icon for this marker
     */
    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);
    },
    
    /**
     * APIMethod: destroy
     * Destroy the marker. You must first remove the marker from any 
     * layer which it has been added to, or you will get buggy behavior.
     * (This can not be done within the marker since the marker does not
     * know which layer it is attached to.)
     */
    destroy: function() {
        // erase any drawn features
        this.erase();

        this.map = null;

        this.events.destroy();
        this.events = null;

        if (this.icon != null) {
            this.icon.destroy();
            this.icon = null;
        }
    },
    
    /** 
    * Method: draw
    * Calls draw on the icon, and returns that output.
    * 
    * Parameters:
    * px - {<OpenLayers.Pixel>}
    * 
    * Returns:
    * {DOMElement} A new DOM Image with this marker's icon set at the 
    * location passed-in
    */
    draw: function(px) {
        return this.icon.draw(px);
    }, 

    /** 
    * Method: erase
    * Erases any drawn elements for this marker.
    */
    erase: function() {
        if (this.icon != null) {
            this.icon.erase();
        }
    }, 

    /**
    * Method: moveTo
    * Move the marker to the new location.
    *
    * Parameters:
    * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
    * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
    */
    moveTo: function (px) {
        if ((px != null) && (this.icon != null)) {
            this.icon.moveTo(px);
        }           
        this.lonlat = this.map.getLonLatFromLayerPx(px);
    },

    /**
     * APIMethod: isDrawn
     * 
     * Returns:
     * {Boolean} Whether or not the marker is drawn.
     */
    isDrawn: function() {
        var isDrawn = (this.icon && this.icon.isDrawn());
        return isDrawn;   
    },

    /**
     * Method: onScreen
     *
     * Returns:
     * {Boolean} Whether or not the marker is currently visible on screen.
     */
    onScreen:function() {
        
        var onScreen = false;
        if (this.map) {
            var screenBounds = this.map.getExtent();
            onScreen = screenBounds.containsLonLat(this.lonlat);
        }    
        return onScreen;
    },
    
    /**
     * Method: inflate
     * Englarges the markers icon by the specified ratio.
     *
     * Parameters:
     * inflate - {float} the ratio to enlarge the marker by (passing 2
     *                   will double the size).
     */
    inflate: function(inflate) {
        if (this.icon) {
            this.icon.setSize({
                w: this.icon.size.w * inflate,
                h: this.icon.size.h * inflate
            });
        }        
    },
    
    /** 
     * Method: setOpacity
     * Change the opacity of the marker by changin the opacity of 
     *   its icon
     * 
     * Parameters:
     * opacity - {float}  Specified as fraction (0.4, etc)
     */
    setOpacity: function(opacity) {
        this.icon.setOpacity(opacity);
    },

    /**
     * Method: setUrl
     * Change URL of the Icon Image.
     * 
     * url - {String} 
     */
    setUrl: function(url) {
        this.icon.setUrl(url);
    },    

    /** 
     * Method: display
     * Hide or show the icon
     * 
     * display - {Boolean} 
     */
    display: function(display) {
        this.icon.display(display);
    },

    CLASS_NAME: "OpenLayers.Marker"
});


/**
 * Function: defaultIcon
 * Creates a default <OpenLayers.Icon>.
 * 
 * Returns:
 * {<OpenLayers.Icon>} A default OpenLayers.Icon to use for a marker
 */
OpenLayers.Marker.defaultIcon = function() {
    return new OpenLayers.Icon(OpenLayers.Util.getImageLocation("marker.png"),
                               {w: 21, h: 25}, {x: -10.5, y: -25});
};
    

/* ======================================================================
    OpenLayers/Strategy/Paging.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Strategy.js
 */

/**
 * Class: OpenLayers.Strategy.Paging
 * Strategy for vector feature paging
 *
 * Inherits from:
 *  - <OpenLayers.Strategy>
 */
OpenLayers.Strategy.Paging = OpenLayers.Class(OpenLayers.Strategy, {
    
    /**
     * Property: features
     * {Array(<OpenLayers.Feature.Vector>)} Cached features.
     */
    features: null,
    
    /**
     * Property: length
     * {Integer} Number of features per page.  Default is 10.
     */
    length: 10,
    
    /**
     * Property: num
     * {Integer} The currently displayed page number.
     */
    num: null,
    
    /**
     * Property: paging
     * {Boolean} The strategy is currently changing pages.
     */
    paging: false,

    /**
     * Constructor: OpenLayers.Strategy.Paging
     * Create a new paging strategy.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     */
    
    /**
     * APIMethod: activate
     * Activate the strategy.  Register any listeners, do appropriate setup.
     * 
     * Returns:
     * {Boolean} The strategy was successfully activated.
     */
    activate: function() {
        var activated = OpenLayers.Strategy.prototype.activate.call(this);
        if(activated) {
            this.layer.events.on({
                "beforefeaturesadded": this.cacheFeatures,
                scope: this
            });
        }
        return activated;
    },
    
    /**
     * APIMethod: deactivate
     * Deactivate the strategy.  Unregister any listeners, do appropriate
     *     tear-down.
     * 
     * Returns:
     * {Boolean} The strategy was successfully deactivated.
     */
    deactivate: function() {
        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
        if(deactivated) {
            this.clearCache();
            this.layer.events.un({
                "beforefeaturesadded": this.cacheFeatures,
                scope: this
            });
        }
        return deactivated;
    },
    
    /**
     * Method: cacheFeatures
     * Cache features before they are added to the layer.
     *
     * Parameters:
     * event - {Object} The event that this was listening for.  This will come
     *     with a batch of features to be paged.
     */
    cacheFeatures: function(event) {
        if(!this.paging) {
            this.clearCache();
            this.features = event.features;
            this.pageNext(event);
        }
    },
    
    /**
     * Method: clearCache
     * Clear out the cached features.  This destroys features, assuming
     *     nothing else has a reference.
     */
    clearCache: function() {
        if(this.features) {
            for(var i=0; i<this.features.length; ++i) {
                this.features[i].destroy();
            }
        }
        this.features = null;
        this.num = null;
    },
    
    /**
     * APIMethod: pageCount
     * Get the total count of pages given the current cache of features.
     *
     * Returns:
     * {Integer} The page count.
     */
    pageCount: function() {
        var numFeatures = this.features ? this.features.length : 0;
        return Math.ceil(numFeatures / this.length);
    },

    /**
     * APIMethod: pageNum
     * Get the zero based page number.
     *
     * Returns:
     * {Integer} The current page number being displayed.
     */
    pageNum: function() {
        return this.num;
    },

    /**
     * APIMethod: pageLength
     * Gets or sets page length.
     *
     * Parameters:
     * newLength - {Integer} Optional length to be set.
     *
     * Returns:
     * {Integer} The length of a page (number of features per page).
     */
    pageLength: function(newLength) {
        if(newLength && newLength > 0) {
            this.length = newLength;
        }
        return this.length;
    },

    /**
     * APIMethod: pageNext
     * Display the next page of features.
     *
     * Returns:
     * {Boolean} A new page was displayed.
     */
    pageNext: function(event) {
        var changed = false;
        if(this.features) {
            if(this.num === null) {
                this.num = -1;
            }
            var start = (this.num + 1) * this.length;
            changed = this.page(start, event);
        }
        return changed;
    },

    /**
     * APIMethod: pagePrevious
     * Display the previous page of features.
     *
     * Returns:
     * {Boolean} A new page was displayed.
     */
    pagePrevious: function() {
        var changed = false;
        if(this.features) {
            if(this.num === null) {
                this.num = this.pageCount();
            }
            var start = (this.num - 1) * this.length;
            changed = this.page(start);
        }
        return changed;
    },
    
    /**
     * Method: page
     * Display the page starting at the given index from the cache.
     *
     * Returns:
     * {Boolean} A new page was displayed.
     */
    page: function(start, event) {
        var changed = false;
        if(this.features) {
            if(start >= 0 && start < this.features.length) {
                var num = Math.floor(start / this.length);
                if(num != this.num) {
                    this.paging = true;
                    var features = this.features.slice(start, start + this.length);
                    this.layer.removeFeatures(this.layer.features);
                    this.num = num;
                    // modify the event if any
                    if(event && event.features) {
                        // this.was called by an event listener
                        event.features = features;
                    } else {
                        // this was called directly on the strategy
                        this.layer.addFeatures(features);
                    }
                    this.paging = false;
                    changed = true;
                }
            }
        }
        return changed;
    },
    
    CLASS_NAME: "OpenLayers.Strategy.Paging" 
});
/* ======================================================================
    OpenLayers/Format/WKT.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format.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.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]);
    },

    /**
     * APIMethod: 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;
        wkt = wkt.replace(/[\n\r]/g, " ");
        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;
    },

    /**
     * APIMethod: 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, 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;
            pieces.push(this.extractGeometry(geometry));
        }
        if (isCollection) {
            pieces.push(')');
        }
        return pieces.join('');
    },

    /**
     * Method: extractGeometry
     * Entry point to construct the WKT for a single Geometry object.
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry.Geometry>}
     *
     * Returns:
     * {String} A WKT string of representing the geometry
     */
    extractGeometry: function(geometry) {
        var 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);
        }                       
        var wktType = type == 'collection' ? 'GEOMETRYCOLLECTION' : type.toUpperCase();
        var data = wktType + '(' + this.extract[type].apply(this, [geometry]) + ')';
        return data;
    },
    
    /**
     * 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 {String} 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(',');
        },

        /**
         * Return the WKT portion between 'GEOMETRYCOLLECTION(' and ')' for an <OpenLayers.Geometry.Collection>
         * @param {OpenLayers.Geometry.Collection} collection
         * @returns {String} internal WKT representation of the collection
         */
        'collection': function(collection) {
            var array = [];
            for(var i=0, len=collection.components.length; i<len; ++i) {
                array.push(this.extractGeometry.apply(this, [collection.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} str A WKT fragment representing the multipoint
         * @returns {OpenLayers.Feature.Vector} A multipoint feature
         * @private
         */
        'multipoint': function(str) {
            var point;
            var points = OpenLayers.String.trim(str).split(',');
            var components = [];
            for(var i=0, len=points.length; i<len; ++i) {
                point = points[i].replace(this.regExes.trimParens, '$1');
                components.push(this.parse.point.apply(this, [point]).geometry);
            }
            return new OpenLayers.Feature.Vector(
                new OpenLayers.Geometry.MultiPoint(components)
            );
        },
        
        /**
         * Return a linestring feature given a linestring WKT fragment.
         * @param {String} str 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} str 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} str 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} str 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} str 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/Popup.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */


/**
 * Class: OpenLayers.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,

    /** 
     * Property: disableFirefoxOverflowHack
     * {Boolean} The hack for overflow in Firefox causes all elements 
     *     to be re-drawn, which causes Flash elements to be 
     *     re-initialized, which is troublesome.
     *     With this property the hack can be disabled.
     */
    disableFirefoxOverflowHack: false,

    /**
     * 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,
    
    /**
     * APIProperty: keepInMap 
     * {Boolean} If panMapIfOutOfView is false, and this property is true, 
     *     contrain the popup such that it always fits in the available map
     *     space. By default, this is not set on the base class. If you are
     *     creating popups that are near map edges and not allowing pannning,
     *     and especially if you have a popup which has a
     *     fixedRelativePosition, setting this to false may be a smart thing to
     *     do. Subclasses may want to override this setting.
     *   
     *     Default is false.
     */
    keepInMap: false,

    /**
     * APIProperty: closeOnMove
     * {Boolean} When map pans, close the popup.
     *     Default is false.
     */
    closeOnMove: 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;
        
        if (this.closeOnMove && this.map) {
            this.map.events.unregister("movestart", this, this.hide);
        }

        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);
            }
        }

        // this assumes that this.map already exists, which is okay because 
        // this.draw is only called once the popup has been added to the map.
        if (this.closeOnMove) {
            this.map.events.register("movestart", this, this.hide);
        }
        
        //listen to movestart, moveend to disable overflow (FF bug)
        if (!this.disableFirefoxOverflowHack && OpenLayers.BROWSER_NAME == '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() {
        this.div.style.display = '';

        if (this.panMapIfOutOfView) {
            this.panIntoView();
        }    
    },

    /**
     * Method: hide
     * Makes the popup invisible.
     */
    hide: function() {
        this.div.style.display = 'none';
    },

    /**
     * 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.BROWSER_NAME == "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 containerElement = (this.map) ? this.map.div : document.body;
        var realSize = OpenLayers.Util.getRenderedDimensions(
            preparedHTML, null, {
                displayClass: this.displayClass,
                containerElement: containerElement
            }
        );

        // 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 'size' object with the clipped dimensions 
            // set or null if not clipped.
            var fixedSize = {
                w: (safeSize.w < realSize.w) ? safeSize.w : null,
                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,
                        containerElement: containerElement
                    }
                );
                
                //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() {
            if (this.popup.id === null) { // this.popup has been destroyed!
                return;
            }
            this.popup.updateSize();
     
            if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
                this.popup.panIntoView();
            }

            OpenLayers.Event.stopObserving(
                this.img, "load", this.img._onImgLoad
            );
    
        };

        //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) {
            
            var extraX = 0, extraY = 0;
            if (this.keepInMap && !this.panMapIfOutOfView) {
                var px = this.map.getPixelFromLonLat(this.lonlat);
                switch (this.relativePosition) {
                    case "tr":
                        extraX = px.x;
                        extraY = this.map.size.h - px.y;
                        break;
                    case "tl":
                        extraX = this.map.size.w - px.x;
                        extraY = this.map.size.h - px.y;
                        break;
                    case "bl":
                        extraX = this.map.size.w - px.x;
                        extraY = px.y;
                        break;
                    case "br":
                        extraX = px.x;
                        extraY = px.y;
                        break;
                    default:    
                        extraX = px.x;
                        extraY = this.map.size.h - px.y;
                        break;
                }
            }    
          
            var maxY = this.map.size.h - 
                this.map.paddingForPopups.top - 
                this.map.paddingForPopups.bottom - 
                hPadding - extraY;
            
            var maxX = this.map.size.w - 
                this.map.paddingForPopups.left - 
                this.map.paddingForPopups.right - 
                wPadding - extraX;
            
            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) {

            if (this.div.parentNode == null) {
                //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;

            if (this.div.parentNode == document.body) {
                //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, {w: 17, h: 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, "touchend", 
                OpenLayers.Function.bindAsEventListener(closePopup, this));
        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. The same procedure is needed for
     *    touchmove and touchend events.
     * 
     *   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);

        function onTouchstart(evt) {
            OpenLayers.Event.stop(evt, true);
        }
        this.events.on({
            "mousedown": this.onmousedown,
            "mousemove": this.onmousemove,
            "mouseup": this.onmouseup,
            "click": this.onclick,
            "mouseout": this.onmouseout,
            "dblclick": this.ondblclick,
            "touchstart": onTouchstart,
            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/Strategy/BBOX.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Strategy.js
 * @requires OpenLayers/Filter/Spatial.js
 */

/**
 * Class: OpenLayers.Strategy.BBOX
 * A simple strategy that reads new features when the viewport invalidates
 *     some bounds.
 *
 * Inherits from:
 *  - <OpenLayers.Strategy>
 */
OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
    
    /**
     * Property: bounds
     * {<OpenLayers.Bounds>} The current data bounds (in the same projection
     *     as the layer - not always the same projection as the map).
     */
    bounds: null,
    
    /** 
     * Property: resolution 
     * {Float} The current data resolution. 
     */ 
    resolution: null, 
           
    /**
     * APIProperty: ratio
     * {Float} The ratio of the data bounds to the viewport bounds (in each
     *     dimension).  Default is 2.
     */
    ratio: 2,

    /** 
     * Property: resFactor 
     * {Float} Optional factor used to determine when previously requested 
     *     features are invalid.  If set, the resFactor will be compared to the
     *     resolution of the previous request to the current map resolution.
     *     If resFactor > (old / new) and 1/resFactor < (old / new).  If you
     *     set a resFactor of 1, data will be requested every time the
     *     resolution changes.  If you set a resFactor of 3, data will be
     *     requested if the old resolution is 3 times the new, or if the new is
     *     3 times the old.  If the old bounds do not contain the new bounds
     *     new data will always be requested (with or without considering
     *     resFactor). 
     */ 
    resFactor: null, 
    
    /**
     * Property: response
     * {<OpenLayers.Protocol.Response>} The protocol response object returned
     *      by the layer protocol.
     */
    response: null,

    /**
     * Constructor: OpenLayers.Strategy.BBOX
     * Create a new BBOX strategy.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     */
    
    /**
     * Method: activate
     * Set up strategy with regard to reading new batches of remote data.
     * 
     * Returns:
     * {Boolean} The strategy was successfully activated.
     */
    activate: function() {
        var activated = OpenLayers.Strategy.prototype.activate.call(this);
        if(activated) {
            this.layer.events.on({
                "moveend": this.update,
                "refresh": this.update,
                "visibilitychanged": this.update,
                scope: this
            });
            this.update();
        }
        return activated;
    },
    
    /**
     * Method: deactivate
     * Tear down strategy with regard to reading new batches of remote data.
     * 
     * Returns:
     * {Boolean} The strategy was successfully deactivated.
     */
    deactivate: function() {
        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
        if(deactivated) {
            this.layer.events.un({
                "moveend": this.update,
                "refresh": this.update,
                "visibilitychanged": this.update,
                scope: this
            });
        }
        return deactivated;
    },

    /**
     * Method: update
     * Callback function called on "moveend" or "refresh" layer events.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will determine
     *     the behaviour of this Strategy
     *
     * Valid options include:
     * force - {Boolean} if true, new data must be unconditionally read.
     * noAbort - {Boolean} if true, do not abort previous requests.
     */
    update: function(options) {
        var mapBounds = this.getMapBounds();
        if (mapBounds !== null && ((options && options.force) ||
          (this.layer.visibility && this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) {
            this.calculateBounds(mapBounds);
            this.resolution = this.layer.map.getResolution(); 
            this.triggerRead(options);
        }
    },
    
    /**
     * Method: getMapBounds
     * Get the map bounds expressed in the same projection as this layer.
     *
     * Returns:
     * {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
     */
    getMapBounds: function() {
        if (this.layer.map === null) {
            return null;
        }
        var bounds = this.layer.map.getExtent();
        if(bounds && !this.layer.projection.equals(
                this.layer.map.getProjectionObject())) {
            bounds = bounds.clone().transform(
                this.layer.map.getProjectionObject(), this.layer.projection
            );
        }
        return bounds;
    },

    /**
     * Method: invalidBounds
     * Determine whether the previously requested set of features is invalid. 
     *     This occurs when the new map bounds do not contain the previously 
     *     requested bounds.  In addition, if <resFactor> is set, it will be 
     *     considered.
     *
     * Parameters:
     * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
     *      retrieved from the map object if not provided
     *
     * Returns:
     * {Boolean} 
     */
    invalidBounds: function(mapBounds) {
        if(!mapBounds) {
            mapBounds = this.getMapBounds();
        }
        var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
        if(!invalid && this.resFactor) {
            var ratio = this.resolution / this.layer.map.getResolution();
            invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
        }
        return invalid;
    },
 
    /**
     * Method: calculateBounds
     *
     * Parameters:
     * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
     *      retrieved from the map object if not provided
     */
    calculateBounds: function(mapBounds) {
        if(!mapBounds) {
            mapBounds = this.getMapBounds();
        }
        var center = mapBounds.getCenterLonLat();
        var dataWidth = mapBounds.getWidth() * this.ratio;
        var dataHeight = mapBounds.getHeight() * this.ratio;
        this.bounds = new OpenLayers.Bounds(
            center.lon - (dataWidth / 2),
            center.lat - (dataHeight / 2),
            center.lon + (dataWidth / 2),
            center.lat + (dataHeight / 2)
        );
    },
    
    /**
     * Method: triggerRead
     *
     * Parameters:
     * options - {Object} Additional options for the protocol's read method 
     *     (optional)
     *
     * Returns:
     * {<OpenLayers.Protocol.Response>} The protocol response object
     *      returned by the layer protocol.
     */
    triggerRead: function(options) {
        if (this.response && !(options && options.noAbort === true)) {
            this.layer.protocol.abort(this.response);
            this.layer.events.triggerEvent("loadend");
        }
        var evt = {filter: this.createFilter()};
        this.layer.events.triggerEvent("loadstart", evt);
        this.response = this.layer.protocol.read(
            OpenLayers.Util.applyDefaults({
                filter: evt.filter,
                callback: this.merge,
                scope: this
        }, options));
    },
 
    /**
     * Method: createFilter
     * Creates a spatial BBOX filter. If the layer that this strategy belongs
     * to has a filter property, this filter will be combined with the BBOX 
     * filter.
     * 
     * Returns
     * {<OpenLayers.Filter>} The filter object.
     */
    createFilter: function() {
        var filter = new OpenLayers.Filter.Spatial({
            type: OpenLayers.Filter.Spatial.BBOX,
            value: this.bounds,
            projection: this.layer.projection
        });
        if (this.layer.filter) {
            filter = new OpenLayers.Filter.Logical({
                type: OpenLayers.Filter.Logical.AND,
                filters: [this.layer.filter, filter]
            });
        }
        return filter;
    },
   
    /**
     * Method: merge
     * Given a list of features, determine which ones to add to the layer.
     *     If the layer projection differs from the map projection, features
     *     will be transformed from the layer projection to the map projection.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>} The response object passed
     *      by the protocol.
     */
    merge: function(resp) {
        this.layer.destroyFeatures();
        if (resp.success()) {
            var features = resp.features;
            if(features && features.length > 0) {
                var remote = this.layer.projection;
                var local = this.layer.map.getProjectionObject();
                if(!local.equals(remote)) {
                    var geom;
                    for(var i=0, len=features.length; i<len; ++i) {
                        geom = features[i].geometry;
                        if(geom) {
                            geom.transform(remote, local);
                        }
                    }
                }
                this.layer.addFeatures(features);
            }
        } else {
            this.bounds = null;
        }
        this.response = null;
        this.layer.events.triggerEvent("loadend", {response: resp});
    },
   
    CLASS_NAME: "OpenLayers.Strategy.BBOX" 
});
/* ======================================================================
    OpenLayers/Layer/Google/v3.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Layer/Google.js
 */

/**
 * Constant: OpenLayers.Layer.Google.v3
 * 
 * Mixin providing functionality specific to the Google Maps API v3.
 * 
 * To use this layer, you must include the GMaps v3 API in your html.
 * 
 * Note that this layer configures the google.maps.map object with the
 * "disableDefaultUI" option set to true. Using UI controls that the Google
 * Maps API provides is not supported by the OpenLayers API.
 */
OpenLayers.Layer.Google.v3 = {
    
    /**
     * Constant: DEFAULTS
     * {Object} It is not recommended to change the properties set here. Note
     * that Google.v3 layers only work when sphericalMercator is set to true.
     * 
     * (code)
     * {
     *     sphericalMercator: true,
     *     projection: "EPSG:900913"
     * }
     * (end)
     */
    DEFAULTS: {
        sphericalMercator: true,
        projection: "EPSG:900913"
    },

    /**
     * APIProperty: animationEnabled
     * {Boolean} If set to true, the transition between zoom levels will be
     *     animated (if supported by the GMaps API for the device used). Set to
     *     false to match the zooming experience of other layer types. Default
     *     is true. Note that the GMaps API does not give us control over zoom
     *     animation, so if set to false, when zooming, this will make the
     *     layer temporarily invisible, wait until GMaps reports the map being
     *     idle, and make it visible again. The result will be a blank layer
     *     for a few moments while zooming.
     */
    animationEnabled: true,
    
    /**
     * APIProperty: enableTilt
     * {Boolean} If set to true, Google's 45-degree tilt view will be enabled.
     *     Default is false, which disables 45-degree tilt.
     *     Note that enabling 45-degree tilt may cause problems with overlays and markers matching up to the map.
     */
    enableTilt: false,

    /** 
     * Method: loadMapObject
     * Load the GMap and register appropriate event listeners.
     */
    loadMapObject: function() {
        if (!this.type) {
            this.type = google.maps.MapTypeId.ROADMAP;
        }
        var mapObject;
        var cache = OpenLayers.Layer.Google.cache[this.map.id];
        if (cache) {
            // there are already Google layers added to this map
            mapObject = cache.mapObject;
            // increment the layer count
            ++cache.count;
        } else {
            // this is the first Google layer for this map
            // create GMap
            var center = this.map.getCenter();
            var container = document.createElement('div');
            container.className = "olForeignContainer";
            container.style.width = '100%';
            container.style.height = '100%';
            mapObject = new google.maps.Map(container, {
                center: center ?
                    new google.maps.LatLng(center.lat, center.lon) :
                    new google.maps.LatLng(0, 0),
                zoom: this.map.getZoom() || 0,
                mapTypeId: this.type,
                disableDefaultUI: true,
                keyboardShortcuts: false,
                draggable: false,
                disableDoubleClickZoom: true,
                scrollwheel: false,
                streetViewControl: false
            });
            
            if (!this.enableTilt) mapObject.setTilt(0);
            
            var googleControl = document.createElement('div');
            googleControl.style.width = '100%';
            googleControl.style.height = '100%';
            mapObject.controls[google.maps.ControlPosition.TOP_LEFT].push(googleControl);
            
            // cache elements for use by any other google layers added to
            // this same map
            cache = {
                googleControl: googleControl,
                mapObject: mapObject,
                count: 1
            };
            OpenLayers.Layer.Google.cache[this.map.id] = cache;
        }
        this.mapObject = mapObject;
        this.setGMapVisibility(this.visibility);
    },
    
    /**
     * APIMethod: onMapResize
     */
    onMapResize: function() {
        if (this.visibility) {
            google.maps.event.trigger(this.mapObject, "resize");
        }
    },

    /**
     * Method: setGMapVisibility
     * Display the GMap container and associated elements.
     * 
     * Parameters:
     * visible - {Boolean} Display the GMap elements.
     */
    setGMapVisibility: function(visible) {
        var cache = OpenLayers.Layer.Google.cache[this.map.id];
        var map = this.map;
        if (cache) {
            var type = this.type;
            var layers = map.layers;
            var layer;
            for (var i=layers.length-1; i>=0; --i) {
                layer = layers[i];
                if (layer instanceof OpenLayers.Layer.Google &&
                            layer.visibility === true && layer.inRange === true) {
                    type = layer.type;
                    visible = true;
                    break;
                }
            }
            var container = this.mapObject.getDiv();
            if (visible === true) {
                if (container.parentNode !== map.div) {
                    if (!cache.rendered) {
                        var me = this;
                        google.maps.event.addListenerOnce(this.mapObject, 'tilesloaded', function() {
                            cache.rendered = true;
                            me.setGMapVisibility(me.getVisibility());
                            me.moveTo(me.map.getCenter());
                        });
                    } else {
                        map.div.appendChild(container);
                        cache.googleControl.appendChild(map.viewPortDiv);
                        google.maps.event.trigger(this.mapObject, 'resize');
                    }
                }
                this.mapObject.setMapTypeId(type);                
            } else if (cache.googleControl.hasChildNodes()) {
                map.div.appendChild(map.viewPortDiv);
                map.div.removeChild(container);
            }
        }
    },
    
    /**
     * Method: getMapContainer
     * 
     * Returns:
     * {DOMElement} the GMap container's div
     */
    getMapContainer: function() {
        return this.mapObject.getDiv();
    },
    
  //
  // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
  //

    /**
     * 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 google.maps.LatLngBounds(
                new google.maps.LatLng(sw.lat, sw.lon),
                new google.maps.LatLng(ne.lat, ne.lon)
            );
        }
        return moBounds;
    },


    /************************************
     *                                  *
     *   MapObject Interface Controls   *
     *                                  *
     ************************************/


  // LonLat - Pixel Translation
  
    /**
     * APIMethod: getMapObjectLonLatFromMapObjectPixel
     * 
     * Parameters:
     * moPixel - {Object} MapObject Pixel format
     * 
     * Returns:
     * {Object} MapObject LonLat translated from MapObject Pixel
     */
    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
        var size = this.map.getSize();
        var lon = this.getLongitudeFromMapObjectLonLat(this.mapObject.center);
        var lat = this.getLatitudeFromMapObjectLonLat(this.mapObject.center);
        var res = this.map.getResolution();

        var delta_x = moPixel.x - (size.w / 2);
        var delta_y = moPixel.y - (size.h / 2);
    
        var lonlat = new OpenLayers.LonLat(
            lon + delta_x * res,
            lat - delta_y * res
        ); 

        if (this.wrapDateLine) {
            lonlat = lonlat.wrapDateLine(this.maxExtent);
        }
        return this.getMapObjectLonLatFromLonLat(lonlat.lon, lonlat.lat);
    },

    /**
     * APIMethod: getMapObjectPixelFromMapObjectLonLat
     * 
     * Parameters:
     * moLonLat - {Object} MapObject LonLat format
     * 
     * Returns:
     * {Object} MapObject Pixel transtlated from MapObject LonLat
     */
    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
        var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
        var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
        var res = this.map.getResolution();
        var extent = this.map.getExtent();
        return this.getMapObjectPixelFromXY((1/res * (lon - extent.left)),
                                            (1/res * (extent.top - lat)));
    },

  
    /** 
     * 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) {
        if (this.animationEnabled === false && zoom != this.mapObject.zoom) {
            var mapContainer = this.getMapContainer();
            google.maps.event.addListenerOnce(
                this.mapObject, 
                "idle", 
                function() {
                    mapContainer.style.visibility = "";
                }
            );
            mapContainer.style.visibility = "hidden";
        }
        this.mapObject.setOptions({
            center: center,
            zoom: zoom
        });
    },
   
    
  // 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: 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 google.maps.LatLng(lonlat.lat, lonlat.lon);
        } else {
            gLatLng = new google.maps.LatLng(lat, lon);
        }
        return gLatLng;
    },
    
  // Pixel
    
    /**
     * APIMethod: getMapObjectPixelFromXY
     * 
     * Parameters:
     * x - {Integer}
     * y - {Integer}
     * 
     * Returns:
     * {Object} MapObject Pixel from x and y parameters
     */
    getMapObjectPixelFromXY: function(x, y) {
        return new google.maps.Point(x, y);
    }
    
};
/* ======================================================================
    OpenLayers/Request.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Events.js
 * @requires OpenLayers/Request/XMLHttpRequest.js
 */

/**
 * TODO: deprecate me
 * Use OpenLayers.Request.proxy instead.
 */
OpenLayers.ProxyHost = "";

/**
 * 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.
 */
if (!OpenLayers.Request) {
    /**
     * This allows for OpenLayers/Request/XMLHttpRequest.js to be included
     * before or after this script.
     */
    OpenLayers.Request = {};
}
OpenLayers.Util.extend(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
    },
    
    /**
     * Constant: URL_SPLIT_REGEX
     */
    URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,
    
    /**
     * APIProperty: events
     * {<OpenLayers.Events>} An events object that handles all 
     *     events on the {<OpenLayers.Request>} object.
     *
     * All event listeners will receive an event object with three properties:
     * request - {<OpenLayers.Request.XMLHttpRequest>} The request object.
     * config - {Object} The config object sent to the specific request method.
     * requestUrl - {String} The request url.
     * 
     * Supported event types:
     * complete - Triggered when we have a response from the request, if a
     *     listener returns false, no further response processing will take
     *     place.
     * success - Triggered when the HTTP response has a success code (200-299).
     * failure - Triggered when the HTTP response does not have a success code.
     */
    events: new OpenLayers.Events(this),
    
    /**
     * Method: makeSameOrigin
     * Using the specified proxy, returns a same origin url of the provided url.
     *
     * Parameters:
     * url - {String} An arbitrary url
     * proxy {String|Function} The proxy to use to make the provided url a
     *     same origin url.
     *
     * Returns
     * {String} the same origin url. If no proxy is provided, the returned url
     *     will be the same as the provided url.
     */
    makeSameOrigin: function(url, proxy) {
        var sameOrigin = url.indexOf("http") !== 0;
        var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
        if (urlParts) {
            var location = window.location;
            sameOrigin =
                urlParts[1] == location.protocol &&
                urlParts[3] == location.hostname;
            var uPort = urlParts[4], lPort = location.port;
            if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
                sameOrigin = sameOrigin && uPort == lPort;
            }
        }
        if (!sameOrigin) {
            if (proxy) {
                if (typeof proxy == "function") {
                    url = proxy(url);
                } else {
                    url = proxy + encodeURIComponent(url);
                }
            }
        }
        return url;
    },

    /**
     * APIMethod: issue
     * Create a new XMLHttpRequest object, open it, set any headers, bind
     *     a callback to done state, and send any data.  It is recommended that
     *     you use one <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
     *     This method is only documented to provide detail on the configuration
     *     options available to all request methods.
     *
     * 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.  Typically, this is only appropriate for <GET>
     *     requests where the query string will be appended to the url.
     *     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 - {String | Document} Optional data to send with the request.
     *     Typically, this is only used with <POST> and <PUT> requests.
     *     Make sure to provide the appropriate "Content-Type" header for your
     *     data.  For <POST> and <PUT> requests, the content type defaults to
     *     "application-xml".  If your data is a different content type, or
     *     if you are using a different HTTP method, set the "Content-Type"
     *     header to match your data type.
     * 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 = config || {};
        config.headers = config.headers || {};
        config = OpenLayers.Util.applyDefaults(config, defaultConfig);
        config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers);
        // Always set the "X-Requested-With" header to signal that this request
        // was issued through the XHR-object. Since header keys are case 
        // insensitive and we want to allow overriding of the "X-Requested-With"
        // header through the user we cannot use applyDefaults, but have to 
        // check manually whether we were called with a "X-Requested-With"
        // header.
        var customRequestedWithHeader = false,
            headerKey;
        for(headerKey in config.headers) {
            if (config.headers.hasOwnProperty( headerKey )) {
                if (headerKey.toLowerCase() === 'x-requested-with') {
                    customRequestedWithHeader = true;
                }
            }
        }
        if (customRequestedWithHeader === false) {
            // we did not have a custom "X-Requested-With" header
            config.headers['X-Requested-With'] = 'XMLHttpRequest';
        }

        // create request, open, and set headers
        var request = new OpenLayers.Request.XMLHttpRequest();
        var url = OpenLayers.Util.urlAppend(config.url, 
            OpenLayers.Util.getParameterString(config.params || {}));
        url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
        request.open(
            config.method, url, config.async, config.user, config.password
        );
        for(var header in config.headers) {
            request.setRequestHeader(header, config.headers[header]);
        }

        var events = this.events;

        // we want to execute runCallbacks with "this" as the
        // execution scope
        var self = this;
        
        request.onreadystatechange = function() {
            if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
                var proceed = events.triggerEvent(
                    "complete",
                    {request: request, config: config, requestUrl: url}
                );
                if(proceed !== false) {
                    self.runCallbacks(
                        {request: request, config: config, requestUrl: url}
                    );
                }
            }
        };
        
        // send request (optionally with data) and return
        // call in a timeout for asynchronous requests so the return is
        // available before readyState == 4 for cached docs
        if(config.async === false) {
            request.send(config.data);
        } else {
            window.setTimeout(function(){
                if (request.readyState !== 0) { // W3C: 0-UNSENT
                    request.send(config.data);
                }
            }, 0);
        }
        return request;
    },
    
    /**
     * Method: runCallbacks
     * Calls the complete, success and failure callbacks. Application
     *    can listen to the "complete" event, have the listener 
     *    display a confirm window and always return false, and
     *    execute OpenLayers.Request.runCallbacks if the user
     *    hits "yes" in the confirm window.
     *
     * Parameters:
     * options - {Object} Hash containing request, config and requestUrl keys
     */
    runCallbacks: function(options) {
        var request = options.request;
        var config = options.config;
        
        // 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;
        }

        if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" &&
                                                        request.responseText) {
            request.status = 200;
        }
        complete(request);

        if (!request.status || (request.status >= 200 && request.status < 300)) {
            this.events.triggerEvent("success", options);
            if(success) {
                success(request);
            }
        }
        if(request.status && (request.status < 200 || request.status >= 300)) {                    
            this.events.triggerEvent("failure", options);
            if(failure) {
                failure(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/Request/XMLHttpRequest.js
   ====================================================================== */

// XMLHttpRequest.js Copyright (C) 2010 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,
        bIE7    = bIE && window.navigator.userAgent.match(/MSIE 7.0/);

    // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
    function fXMLHttpRequest() {
        this._object    = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
        this._listeners    = [];
    };

    // Constructor
    function cXMLHttpRequest() {
        return new fXMLHttpRequest;
    };
    cXMLHttpRequest.prototype    = fXMLHttpRequest.prototype;

    // 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    = '';

    // Priority proposal
    cXMLHttpRequest.prototype.priority        = "NORMAL";

    // 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) {
        // Delete headers, required when object is reused
        delete this._headers;

        // When bAsync parameter value is omitted, use true as default
        if (arguments.length < 3)
            bAsync    = true;

        // 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,
            fOnUnload;

        // BUGFIX: IE - memory leak on page unload (inter-page leak)
        if (bIE && bAsync) {
            fOnUnload = function() {
                if (nState != cXMLHttpRequest.DONE) {
                    fCleanTransport(oRequest);
                    // Safe to abort here since onreadystatechange handler removed
                    oRequest.abort();
                }
            };
            window.attachEvent("onunload", fOnUnload);
        }

        // Add method sniffer
        if (cXMLHttpRequest.onopen)
            cXMLHttpRequest.onopen.apply(this, arguments);

        if (arguments.length > 4)
            this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
        else
        if (arguments.length > 3)
            this._object.open(sMethod, sUrl, bAsync, sUser);
        else
            this._object.open(sMethod, sUrl, bAsync);

        this.readyState    = cXMLHttpRequest.OPENED;
        fReadyStateChange(this);

        this._object.onreadystatechange    = function() {
            if (bGecko && !bAsync)
                return;

            // Synchronize state
            oRequest.readyState        = oRequest._object.readyState;

            //
            fSynchronizeValues(oRequest);

            // BUGFIX: Firefox fires unnecessary DONE when aborting
            if (oRequest._aborted) {
                // Reset readyState to UNSENT
                oRequest.readyState    = cXMLHttpRequest.UNSENT;

                // Return now
                return;
            }

            if (oRequest.readyState == cXMLHttpRequest.DONE) {
                // Free up queue
                delete oRequest._data;
/*                if (bAsync)
                    fQueue_remove(oRequest);*/
                //
                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
                    if (sUser) {
                         if (sPassword)
                            oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
                        else
                            oRequest._object.open(sMethod, sUrl, bAsync, sUser);
                    }
                    else
                        oRequest._object.open(sMethod, sUrl, bAsync);
                    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 until 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;
        }
    };
    function fXMLHttpRequest_send(oRequest) {
        oRequest._object.send(oRequest._data);

        // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
        if (bGecko && !oRequest._async) {
            oRequest.readyState    = cXMLHttpRequest.OPENED;

            // Synchronize state
            fSynchronizeValues(oRequest);

            // Simulate missing states
            while (oRequest.readyState < cXMLHttpRequest.DONE) {
                oRequest.readyState++;
                fReadyStateChange(oRequest);
                // Check if we are aborted
                if (oRequest._aborted)
                    return;
            }
        }
    };
    cXMLHttpRequest.prototype.send    = function(vData) {
        // Add method sniffer
        if (cXMLHttpRequest.onsend)
            cXMLHttpRequest.onsend.apply(this, arguments);

        if (!arguments.length)
            vData    = null;

        // 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._data    = vData;
/*
        // Add to queue
        if (this._async)
            fQueue_add(this);
        else*/
            fXMLHttpRequest_send(this);
    };
    cXMLHttpRequest.prototype.abort    = function() {
        // Add method sniffer
        if (cXMLHttpRequest.onabort)
            cXMLHttpRequest.onabort.apply(this, arguments);

        // BUGFIX: Gecko - unnecessary DONE when aborting
        if (this.readyState > cXMLHttpRequest.UNSENT)
            this._aborted    = true;

        this._object.abort();

        // BUGFIX: IE - memory leak
        fCleanTransport(this);

        this.readyState    = cXMLHttpRequest.UNSENT;

        delete this._data;
/*        if (this._async)
            fQueue_remove(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);
    };

    // EventTarget interface implementation
    cXMLHttpRequest.prototype.addEventListener    = function(sName, fHandler, bUseCapture) {
        for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
            if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
                return;
        // Add listener
        this._listeners.push([sName, fHandler, bUseCapture]);
    };

    cXMLHttpRequest.prototype.removeEventListener    = function(sName, fHandler, bUseCapture) {
        for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
            if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
                break;
        // Remove listener
        if (oListener)
            this._listeners.splice(nIndex, 1);
    };

    cXMLHttpRequest.prototype.dispatchEvent    = function(oEvent) {
        var oEventPseudo    = {
            'type':            oEvent.type,
            'target':        this,
            'currentTarget':this,
            'eventPhase':    2,
            'bubbles':        oEvent.bubbles,
            'cancelable':    oEvent.cancelable,
            'timeStamp':    oEvent.timeStamp,
            'stopPropagation':    function() {},    // There is no flow
            'preventDefault':    function() {},    // There is no default action
            'initEvent':        function() {}    // Original event object should be initialized
        };

        // Execute onreadystatechange
        if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
            (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);

        // Execute listeners
        for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
            if (oListener[0] == oEventPseudo.type && !oListener[2])
                (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
    };

    //
    cXMLHttpRequest.prototype.toString    = function() {
        return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
    };

    cXMLHttpRequest.toString    = function() {
        return '[' + "XMLHttpRequest" + ']';
    };

    // Helper function
    function fReadyStateChange(oRequest) {
        // Sniffing code
        if (cXMLHttpRequest.onreadystatechange)
            cXMLHttpRequest.onreadystatechange.apply(oRequest);

        // Fake event
        oRequest.dispatchEvent({
            'type':            "readystatechange",
            'bubbles':        false,
            'cancelable':    false,
            'timeStamp':    new Date + 0
        });
    };

    function fGetDocument(oRequest) {
        var oDocument    = oRequest.responseXML,
            sResponse    = oRequest.responseText;
        // Try parsing responseText
        if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
            oDocument    = new window.ActiveXObject("Microsoft.XMLDOM");
            oDocument.async                = false;
            oDocument.validateOnParse    = false;
            oDocument.loadXML(sResponse);
        }
        // Check if there is no error in document
        if (oDocument)
            if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (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;
    };
/*
    // Queue manager
    var oQueuePending    = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
        aQueueRunning    = [];
    function fQueue_add(oRequest) {
        oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
        //
        setTimeout(fQueue_process);
    };

    function fQueue_remove(oRequest) {
        for (var nIndex = 0, bFound    = false; nIndex < aQueueRunning.length; nIndex++)
            if (bFound)
                aQueueRunning[nIndex - 1]    = aQueueRunning[nIndex];
            else
            if (aQueueRunning[nIndex] == oRequest)
                bFound    = true;
        if (bFound)
            aQueueRunning.length--;
        //
        setTimeout(fQueue_process);
    };

    function fQueue_process() {
        if (aQueueRunning.length < 6) {
            for (var sPriority in oQueuePending) {
                if (oQueuePending[sPriority].length) {
                    var oRequest    = oQueuePending[sPriority][0];
                    oQueuePending[sPriority]    = oQueuePending[sPriority].slice(1);
                    //
                    aQueueRunning.push(oRequest);
                    // Send request
                    fXMLHttpRequest_send(oRequest);
                    break;
                }
            }
        }
    };
*/
    // 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/.
     */
    if (!OpenLayers.Request) {
        /**
         * This allows for OpenLayers/Request.js to be included
         * before or after this script.
         */
        OpenLayers.Request = {};
    }
    OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
})();
/* ======================================================================
    Rico/license.js
   ====================================================================== */

/**
 * @license Apache 2 
 *  
 * Contains portions of Rico <http://openrico.org/>
 * 
 * Copyright 2005 Sabre Airline Solutions  
 *
 * 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. 
 */
/* ======================================================================
    OpenLayers/Marker/Box.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Marker.js
 */

/**
 * Class: OpenLayers.Marker.Box
 *
 * Inherits from:
 *  - <OpenLayers.Marker> 
 */
OpenLayers.Marker.Box = OpenLayers.Class(OpenLayers.Marker, {

    /** 
     * Property: bounds 
     * {<OpenLayers.Bounds>} 
     */
    bounds: null,

    /** 
     * Property: div 
     * {DOMElement} 
     */
    div: null,
    
    /** 
     * Constructor: OpenLayers.Marker.Box
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>} 
     * borderColor - {String} 
     * borderWidth - {int} 
     */
    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);
        this.setBorder(borderColor, borderWidth);
    },

    /**
     * Method: destroy 
     */    
    destroy: function() {

        this.bounds = null;
        this.div = null;

        OpenLayers.Marker.prototype.destroy.apply(this, arguments);
    },

    /** 
     * Method: setBorder
     * Allow the user to change the box's color and border width
     * 
     * Parameters:
     * color - {String} Default is "red"
     * width - {int} Default is 2
     */
    setBorder: function (color, width) {
        if (!color) {
            color = "red";
        }
        if (!width) {
            width = 2;
        }
        this.div.style.border = width + "px solid " + color;
    },
    
    /** 
    * Method: draw
    * 
    * Parameters:
    * px - {<OpenLayers.Pixel>} 
    * sz - {<OpenLayers.Size>} 
    * 
    * Returns: 
    * {DOMElement} A new DOM Image with this marker's icon set at the 
    *         location passed-in
    */
    draw: function(px, sz) {
        OpenLayers.Util.modifyDOMElement(this.div, null, px, sz);
        return this.div;
    }, 

    /**
     * Method: onScreen
     * 
     * Rreturn:
     * {Boolean} Whether or not the marker is currently visible on screen.
     */
    onScreen:function() {
        var onScreen = false;
        if (this.map) {
            var screenBounds = this.map.getExtent();
            onScreen = screenBounds.containsBounds(this.bounds, true, true);
        }    
        return onScreen;
    },
    
    /**
     * Method: display
     * Hide or show the icon
     * 
     * Parameters:
     * display - {Boolean} 
     */
    display: function(display) {
        this.div.style.display = (display) ? "" : "none";
    },

    CLASS_NAME: "OpenLayers.Marker.Box"
});

/* ======================================================================
    OpenLayers/Layer/MapGuide.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Request/XMLHttpRequest.js
 * @requires OpenLayers/Layer/Grid.js
 */

/**
 * Class: OpenLayers.Layer.MapGuide
 * Instances of OpenLayers.Layer.MapGuide are used to display
 * data from a MapGuide OS instance.
 *
 * Inherits from:
 *  - <OpenLayers.Layer.Grid>
 */
OpenLayers.Layer.MapGuide = OpenLayers.Class(OpenLayers.Layer.Grid, {

    /** 
     * APIProperty: isBaseLayer
     * {Boolean} Treat this layer as a base layer.  Default is true.
     **/
    isBaseLayer: true,
    
    /**
     * APIProperty: useHttpTile
     * {Boolean} use a tile cache exposed directly via a webserver rather than the 
     *    via mapguide server. This does require extra configuration on the Mapguide Server,
     *    and will only work when singleTile is false. The url for the layer must be set to the
     *    webserver path rather than the Mapguide mapagent.
     *    See http://trac.osgeo.org/mapguide/wiki/CodeSamples/Tiles/ServingTilesViaHttp
     **/
    useHttpTile: false,
    
    /** 
     * APIProperty: singleTile
     * {Boolean} use tile server or request single tile image. 
     **/
    singleTile: false,
    
    /** 
     * APIProperty: useOverlay
     * {Boolean} flag to indicate if the layer should be retrieved using
     * GETMAPIMAGE (default) or using GETDYNAMICOVERLAY requests.
     **/
    useOverlay: false,
    
    /** 
     * APIProperty: useAsyncOverlay
     * {Boolean} indicates if the MapGuide site supports the asynchronous 
     * GETDYNAMICOVERLAY requests which is available in MapGuide Enterprise 2010
     * and MapGuide Open Source v2.0.3 or higher. The newer versions of MG 
     * is called asynchronously, allows selections to be drawn separately from 
     * the map and offers styling options.
     * 
     * With older versions of MapGuide, set useAsyncOverlay=false.  Note that in
     * this case a synchronous AJAX call is issued and the mapname and session
     * parameters must be used to initialize the layer, not the mapdefinition
     * parameter. Also note that this will issue 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.
     **/
    useAsyncOverlay: true,
    
    /**
     * Constant: TILE_PARAMS
     * {Object} Hashtable of default parameter key/value pairs for tiled layer
     */
    TILE_PARAMS: {
         operation: 'GETTILEIMAGE',
         version: '1.2.0'
    },

    /**
     * Constant: SINGLE_TILE_PARAMS
     * {Object} Hashtable of default parameter key/value pairs for untiled layer
     */
    SINGLE_TILE_PARAMS: {
        operation: 'GETMAPIMAGE',
        format: 'PNG',
        locale: 'en',
        clip: '1',
        version: '1.0.0'
    },
    
    /**
     * Constant: OVERLAY_PARAMS
     * {Object} Hashtable of default parameter key/value pairs for untiled layer
     */
    OVERLAY_PARAMS: {
        operation: 'GETDYNAMICMAPOVERLAYIMAGE',
        format: 'PNG',
        locale: 'en',
        clip: '1',
        version: '2.0.0'
    },
    
    /** 
     * Constant: FOLDER_PARAMS
     * {Object} Hashtable of parameter key/value pairs which describe 
     * the folder structure for tiles as configured in the mapguide 
     * serverconfig.ini section [TileServiceProperties]
     */
    FOLDER_PARAMS: {
        tileColumnsPerFolder: 30,
        tileRowsPerFolder: 30,
        format: 'png',
        querystring: null
    },

    /** 
     * Property: defaultSize
     * {<OpenLayers.Size>} Tile size as produced by MapGuide server
     **/
    defaultSize: new OpenLayers.Size(300,300),

    /** 
     * Property: tileOriginCorner
     * {String} MapGuide tile server uses top-left as tile origin
     **/
    tileOriginCorner: "tl",

    /**
     * Constructor: OpenLayers.Layer.MapGuide
     * Create a new Mapguide layer, either tiled or untiled.  
     *
     * For tiled layers, the 'groupName' and 'mapDefinition' values 
     * must be specified as parameters in the constructor.
     *
     * For untiled base layers, specify either combination of 'mapName' and
     * 'session', or 'mapDefinition' and 'locale'.  
     *
     * For older versions of MapGuide and overlay layers, set useAsyncOverlay 
     * to false and in this case mapName and session are required parameters 
     * for the constructor.
     *
     * 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 server. The ones that
     *     you may want to use are: 
     *   - mapDefinition - {String} The MapGuide resource definition
     *            (e.g. Library://Samples/Gmap/Maps/gmapTiled.MapDefinition)
     *   - locale - Locale setting 
     *            (for untiled overlays layers only)
     *   - mapName - {String} Name of the map as stored in the MapGuide session.
     *          (for untiled layers with a session parameter only)
     *   - session - { String} MapGuide session ID 
     *            (for untiled overlays layers only)
     *   - basemaplayergroupname - {String} GroupName for tiled MapGuide layers only
     *   - format - Image format to be returned (for untiled overlay layers only)
     *   - showLayers - {String} A comma separated list of GUID's for the
     *       layers to display eg: 'cvc-xcv34,453-345-345sdf'.
     *   - hideLayers - {String} A comma separated list of GUID's for the
     *       layers to hide eg: 'cvc-xcv34,453-345-345sdf'.
     *   - showGroups - {String} A comma separated list of GUID's for the
     *       groups to display eg: 'cvc-xcv34,453-345-345sdf'.
     *   - hideGroups - {String} A comma separated list of GUID's for the
     *       groups to hide eg: 'cvc-xcv34,453-345-345sdf'
     *   - selectionXml - {String} A selection xml string Some server plumbing
     *       is required to read such a value.
     * options - {Object} Hashtable of extra options to tag onto the layer; 
     *          will vary depending if tiled or untiled maps are being requested
     */
    initialize: function(name, url, params, options) {
        
        OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
        
        // unless explicitly set in options, if the layer is transparent, 
        // it will be an overlay
        if (options == null || options.isBaseLayer == null) {
            this.isBaseLayer = ((this.transparent != "true") && 
                                (this.transparent != true));
        }

        if (options && options.useOverlay!=null) {
          this.useOverlay = options.useOverlay;
        }
        
        //initialize for untiled layers
        if (this.singleTile) {
          if (this.useOverlay) {
            OpenLayers.Util.applyDefaults(
                           this.params,
                           this.OVERLAY_PARAMS
                           );
            if (!this.useAsyncOverlay) {
              this.params.version = "1.0.0";
            }
          } else {
            OpenLayers.Util.applyDefaults(
                           this.params,
                           this.SINGLE_TILE_PARAMS
                           );
          }         
        } else {
            //initialize for tiled layers
            if (this.useHttpTile) {
                OpenLayers.Util.applyDefaults(
                               this.params,
                               this.FOLDER_PARAMS
                               );
            } else {
                OpenLayers.Util.applyDefaults(
                               this.params,
                               this.TILE_PARAMS
                               );
            }
            this.setTileSize(this.defaultSize); 
        }
    },

    /**
     * Method: clone
     * Create a clone of this layer
     *
     * Returns:
     * {<OpenLayers.Layer.MapGuide>} An exact clone of this layer
     */
    clone: function (obj) {
      if (obj == null) {
            obj = new OpenLayers.Layer.MapGuide(this.name,
                                           this.url,
                                           this.params,
                                           this.getOptions());
      }
      //get all additions from superclasses
      obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);

      return obj;
    },

    /**
     * Method: getURL
     * Return a query string for this layer
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox 
     *                                for the request
     *
     * 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 url;
        var center = bounds.getCenterLonLat();
        var mapSize = this.map.getSize();

        if (this.singleTile) {
          //set up the call for GETMAPIMAGE or GETDYNAMICMAPOVERLAY with
          //dynamic map parameters
          var params = {
            setdisplaydpi: OpenLayers.DOTS_PER_INCH,
            setdisplayheight: mapSize.h*this.ratio,
            setdisplaywidth: mapSize.w*this.ratio,
            setviewcenterx: center.lon,
            setviewcentery: center.lat,
            setviewscale: this.map.getScale()
          };
          
          if (this.useOverlay && !this.useAsyncOverlay) {
            //first we 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';
            url = this.getFullRequestString( getVisParams );
            
            OpenLayers.Request.GET({url: url, async: false});
          }
          //construct the full URL
          url = this.getFullRequestString( params );
        } else {

          //tiled version
          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);

          if (this.useHttpTile){
              url = this.getImageFilePath(
                   {
                       tilecol: colidx,
                       tilerow: rowidx,
                       scaleindex: this.resolutions.length - this.map.zoom - 1
                    });

          } else {
            url = this.getFullRequestString(
                   {
                       tilecol: colidx,
                       tilerow: rowidx,
                       scaleindex: this.resolutions.length - this.map.zoom - 1
                    });
          }
       }
       return url;
    },

    /**
     * Method: getFullRequestString
     * getFullRequestString on MapGuide layers is special, because we 
     * do a regular expression replace on ',' in parameters to '+'.
     * This is why it is subclassed here.
     *
     * Parameters:
     * altUrl - {String} Alternative base URL to use.
     *
     * Returns:
     * {String} A string with the layer's url appropriately encoded for MapGuide
     */
    getFullRequestString:function(newParams, altUrl) {
        // use layer's url unless altUrl passed in
        var url = (altUrl == null) ? this.url : altUrl;
        
        // if url is not a string, it should be an array of strings, 
        //  in which case we will randomly select one of them in order
        //  to evenly distribute requests to different urls.
        if (typeof url == "object") {
            url = url[Math.floor(Math.random()*url.length)];
        }   
        // requestString always starts with url
        var requestString = url;        

        // create a new params hashtable with all the layer params and the 
        // new params together. then convert to string
        var allParams = OpenLayers.Util.extend({}, this.params);
        allParams = OpenLayers.Util.extend(allParams, newParams);
        // ignore parameters that are already in the url search string
        var urlParams = OpenLayers.Util.upperCaseObject(
                            OpenLayers.Util.getParameters(url));
        for(var key in allParams) {
            if(key.toUpperCase() in urlParams) {
                delete allParams[key];
            }
        }
        var paramsString = OpenLayers.Util.getParameterString(allParams);
        
        /* MapGuide needs '+' seperating things like bounds/height/width.
           Since typically this is URL encoded, we use a slight hack: we
           depend on the list-like functionality of getParameterString to
           leave ',' only in the case of list items (since otherwise it is
           encoded) then do a regular expression replace on the , characters
           to '+' */
        paramsString = paramsString.replace(/,/g, "+");
        
        if (paramsString != "") {
            var lastServerChar = url.charAt(url.length - 1);
            if ((lastServerChar == "&") || (lastServerChar == "?")) {
                requestString += paramsString;
            } else {
                if (url.indexOf('?') == -1) {
                    //serverPath has no ? -- add one
                    requestString += '?' + paramsString;
                } else {
                    //serverPath contains ?, so must already have paramsString at the end
                    requestString += '&' + paramsString;
                }
            }
        }
        return requestString;
    },

     /** 
     * Method: getImageFilePath
     * special handler to request mapguide tiles from an http exposed tilecache 
     *
     * Parameters:
     * altUrl - {String} Alternative base URL to use.
     *
     * Returns:
     * {String} A string with the url for the tile image
     */
    getImageFilePath:function(newParams, altUrl) {
        // use layer's url unless altUrl passed in
        var url = (altUrl == null) ? this.url : altUrl;
        
        // if url is not a string, it should be an array of strings, 
        //  in which case we will randomly select one of them in order
        //  to evenly distribute requests to different urls.
        if (typeof url == "object") {
            url = url[Math.floor(Math.random()*url.length)];
        }   
        // requestString always starts with url
        var requestString = url;        

        var tileRowGroup = "";
        var tileColGroup = "";
        
        if (newParams.tilerow < 0) {
          tileRowGroup =  '-';
        }
          
        if (newParams.tilerow == 0 ) {
          tileRowGroup += '0';
        } else {
          tileRowGroup += Math.floor(Math.abs(newParams.tilerow/this.params.tileRowsPerFolder)) * this.params.tileRowsPerFolder;
        }
          
        if (newParams.tilecol < 0) {
          tileColGroup =  '-';
        }
        
        if (newParams.tilecol == 0) {
          tileColGroup += '0';
        } else {
          tileColGroup += Math.floor(Math.abs(newParams.tilecol/this.params.tileColumnsPerFolder)) * this.params.tileColumnsPerFolder;
        }
        
        var tilePath = '/S' + Math.floor(newParams.scaleindex)
                + '/' + this.params.basemaplayergroupname
                + '/R' + tileRowGroup
                + '/C' + tileColGroup
                + '/' + (newParams.tilerow % this.params.tileRowsPerFolder) 
                + '_' + (newParams.tilecol % this.params.tileColumnsPerFolder) 
                + '.' + this.params.format;
    
        if (this.params.querystring) {
               tilePath += "?" + this.params.querystring;
        }
        
        requestString += tilePath;
        return requestString;
    },
    
    CLASS_NAME: "OpenLayers.Layer.MapGuide"
});
/* ======================================================================
    OpenLayers/Control/Measure.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Feature/Vector.js
 */

/**
 * Class: OpenLayers.Control.Measure
 * Allows for drawing of features for measurements.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.Measure = OpenLayers.Class(OpenLayers.Control, {

    /**
     * APIProperty: events
     * {<OpenLayers.Events>} Events instance for listeners and triggering
     *     control specific events.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * control.events.register(type, obj, listener);
     * (end)
     *
     * Supported event types (in addition to those from <OpenLayers.Control.events>):
     * measure - Triggered when a measurement sketch is complete.  Listeners
     *      will receive an event with measure, units, order, and geometry
     *      properties.
     * measurepartial - Triggered when a new point is added to the
     *      measurement sketch or if the <immediate> property is true and the
     *      measurement sketch is modified.  Listeners receive an event with measure,
     *      units, order, and geometry.
     */

    /**
     * APIProperty: handlerOptions
     * {Object} Used to set non-default properties on the control's handler
     */

    /**
     * Property: callbacks
     * {Object} The functions that are sent to the handler for callback
     */
    callbacks: null,

    /**
     * APIProperty: displaySystem
     * {String} Display system for output measurements.  Supported values
     *     are 'english', 'metric', and 'geographic'.  Default is 'metric'.
     */
    displaySystem: 'metric',

    /**
     * APIProperty: geodesic
     * {Boolean} Calculate geodesic metrics instead of planar metrics.  This
     *     requires that geometries can be transformed into Geographic/WGS84
     *     (if that is not already the map projection).  Default is false.
     */
    geodesic: false,

    /**
     * Property: displaySystemUnits
     * {Object} Units for various measurement systems.  Values are arrays
     *     of unit abbreviations (from OpenLayers.INCHES_PER_UNIT) in decreasing
     *     order of length.
     */
    displaySystemUnits: {
        geographic: ['dd'],
        english: ['mi', 'ft', 'in'],
        metric: ['km', 'm']
    },

    /**
     * Property: delay
     * {Number} Number of milliseconds between clicks before the event is
     *     considered a double-click.  The "measurepartial" event will not
     *     be triggered if the sketch is completed within this time.  This
     *     is required for IE where creating a browser reflow (if a listener
     *     is modifying the DOM by displaying the measurement values) messes
     *     with the dblclick listener in the sketch handler.
     */
    partialDelay: 300,

    /**
     * Property: delayedTrigger
     * {Number} Timeout id of trigger for measurepartial.
     */
    delayedTrigger: null,

    /**
     * APIProperty: persist
     * {Boolean} Keep the temporary measurement sketch drawn after the
     *     measurement is complete.  The geometry will persist until a new
     *     measurement is started, the control is deactivated, or <cancel> is
     *     called.
     */
    persist: false,

    /**
     * APIProperty: immediate
     * {Boolean} Activates the immediate measurement so that the "measurepartial"
     *     event is also fired once the measurement sketch is modified.
     *     Default is false.
     */
    immediate : false,

    /**
     * Constructor: OpenLayers.Control.Measure
     *
     * Parameters:
     * handler - {<OpenLayers.Handler>}
     * options - {Object}
     */
    initialize: function(handler, options) {
        OpenLayers.Control.prototype.initialize.apply(this, [options]);
        var callbacks = {done: this.measureComplete,
            point: this.measurePartial};
        if (this.immediate){
            callbacks.modify = this.measureImmediate;
        }
        this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);

        // let the handler options override, so old code that passes 'persist'
        // directly to the handler does not need an update
        this.handlerOptions = OpenLayers.Util.extend(
            {persist: this.persist}, this.handlerOptions
        );
        this.handler = new handler(this, this.callbacks, this.handlerOptions);
    },

    /**
     * APIMethod: deactivate
     */
    deactivate: function() {
        this.cancelDelay();
        return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
    },

    /**
     * APIMethod: cancel
     * Stop the control from measuring.  If <persist> is true, the temporary
     *     sketch will be erased.
     */
    cancel: function() {
        this.cancelDelay();
        this.handler.cancel();
    },

    /**
     * APIMethod: setImmediate
     * Sets the <immediate> property. Changes the activity of immediate
     * measurement.
     */
    setImmediate: function(immediate) {
        this.immediate = immediate;
        if (this.immediate){
            this.callbacks.modify = this.measureImmediate;
        } else {
            delete this.callbacks.modify;
        }
    },

    /**
     * Method: updateHandler
     *
     * Parameters:
     * handler - {Function} One of the sketch handler constructors.
     * options - {Object} Options for the handler.
     */
    updateHandler: function(handler, options) {
        var active = this.active;
        if(active) {
            this.deactivate();
        }
        this.handler = new handler(this, this.callbacks, options);
        if(active) {
            this.activate();
        }
    },

    /**
     * Method: measureComplete
     * Called when the measurement sketch is done.
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     */
    measureComplete: function(geometry) {
        this.cancelDelay();
        this.measure(geometry, "measure");
    },

    /**
     * Method: measurePartial
     * Called each time a new point is added to the measurement sketch.
     *
     * Parameters:
     * point - {<OpenLayers.Geometry.Point>} The last point added.
     * geometry - {<OpenLayers.Geometry>} The sketch geometry.
     */
    measurePartial: function(point, geometry) {
        this.cancelDelay();
        geometry = geometry.clone();
        // when we're wating for a dblclick, we have to trigger measurepartial
        // after some delay to deal with reflow issues in IE
        if (this.handler.freehandMode(this.handler.evt)) {
            // no dblclick in freehand mode
            this.measure(geometry, "measurepartial");
        } else {
            this.delayedTrigger = window.setTimeout(
                OpenLayers.Function.bind(function() {
                    this.delayedTrigger = null;
                    this.measure(geometry, "measurepartial");
                }, this),
                this.partialDelay
            );
        }
    },

    /**
     * Method: measureImmediate
     * Called each time the measurement sketch is modified.
     *
     * Parameters:
     * point - {<OpenLayers.Geometry.Point>} The point at the mouse position.
     * feature - {<OpenLayers.Feature.Vector>} The sketch feature.
     * drawing - {Boolean} Indicates whether we're currently drawing.
     */
    measureImmediate : function(point, feature, drawing) {
        if (drawing && !this.handler.freehandMode(this.handler.evt)) {
            this.cancelDelay();
            this.measure(feature.geometry, "measurepartial");
        }
    },

    /**
     * Method: cancelDelay
     * Cancels the delay measurement that measurePartial began.
     */
    cancelDelay: function() {
        if (this.delayedTrigger !== null) {
            window.clearTimeout(this.delayedTrigger);
            this.delayedTrigger = null;
        }
    },

    /**
     * Method: measure
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     * eventType - {String}
     */
    measure: function(geometry, eventType) {
        var stat, order;
        if(geometry.CLASS_NAME.indexOf('LineString') > -1) {
            stat = this.getBestLength(geometry);
            order = 1;
        } else {
            stat = this.getBestArea(geometry);
            order = 2;
        }
        this.events.triggerEvent(eventType, {
            measure: stat[0],
            units: stat[1],
            order: order,
            geometry: geometry
        });
    },

    /**
     * Method: getBestArea
     * Based on the <displaySystem> returns the area of a geometry.
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     *
     * Returns:
     * {Array([Float, String])}  Returns a two item array containing the
     *     area and the units abbreviation.
     */
    getBestArea: function(geometry) {
        var units = this.displaySystemUnits[this.displaySystem];
        var unit, area;
        for(var i=0, len=units.length; i<len; ++i) {
            unit = units[i];
            area = this.getArea(geometry, unit);
            if(area > 1) {
                break;
            }
        }
        return [area, unit];
    },

    /**
     * Method: getArea
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     * units - {String} Unit abbreviation
     *
     * Returns:
     * {Float} The geometry area in the given units.
     */
    getArea: function(geometry, units) {
        var area, geomUnits;
        if(this.geodesic) {
            area = geometry.getGeodesicArea(this.map.getProjectionObject());
            geomUnits = "m";
        } else {
            area = geometry.getArea();
            geomUnits = this.map.getUnits();
        }
        var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
        if(inPerDisplayUnit) {
            var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
            area *= Math.pow((inPerMapUnit / inPerDisplayUnit), 2);
        }
        return area;
    },

    /**
     * Method: getBestLength
     * Based on the <displaySystem> returns the length of a geometry.
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     *
     * Returns:
     * {Array([Float, String])}  Returns a two item array containing the
     *     length and the units abbreviation.
     */
    getBestLength: function(geometry) {
        var units = this.displaySystemUnits[this.displaySystem];
        var unit, length;
        for(var i=0, len=units.length; i<len; ++i) {
            unit = units[i];
            length = this.getLength(geometry, unit);
            if(length > 1) {
                break;
            }
        }
        return [length, unit];
    },

    /**
     * Method: getLength
     *
     * Parameters:
     * geometry - {<OpenLayers.Geometry>}
     * units - {String} Unit abbreviation
     *
     * Returns:
     * {Float} The geometry length in the given units.
     */
    getLength: function(geometry, units) {
        var length, geomUnits;
        if(this.geodesic) {
            length = geometry.getGeodesicLength(this.map.getProjectionObject());
            geomUnits = "m";
        } else {
            length = geometry.getLength();
            geomUnits = this.map.getUnits();
        }
        var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
        if(inPerDisplayUnit) {
            var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
            length *= (inPerMapUnit / inPerDisplayUnit);
        }
        return length;
    },

    CLASS_NAME: "OpenLayers.Control.Measure"
});
/* ======================================================================
    OpenLayers/Control/DrawFeature.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Feature/Vector.js
 */

/**
 * Class: OpenLayers.Control.DrawFeature
 * The DrawFeature control draws point, line or polygon 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,
    
    /** 
     * APIProperty: events
     * {<OpenLayers.Events>} Events instance for listeners and triggering
     *     control specific events.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * control.events.register(type, obj, listener);
     * (end)
     *
     * Supported event types (in addition to those from <OpenLayers.Control.events>):
     * featureadded - Triggered when a feature is added
     */
    
    /**
     * APIProperty: multi
     * {Boolean} Cast features to multi-part geometries before passing to the
     *     layer.  Default is false.
     */
    multi: false,

    /**
     * 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
     */
    
    /**
     * Constructor: OpenLayers.Control.DrawFeature
     * 
     * Parameters:
     * layer - {<OpenLayers.Layer.Vector>} 
     * handler - {<OpenLayers.Handler>} 
     * options - {Object} 
     */
    initialize: function(layer, handler, options) {
        OpenLayers.Control.prototype.initialize.apply(this, [options]);
        this.callbacks = OpenLayers.Util.extend(
            {
                done: this.drawFeature,
                modify: function(vertex, feature) {
                    this.layer.events.triggerEvent(
                        "sketchmodified", {vertex: vertex, feature: feature}
                    );
                },
                create: function(vertex, feature) {
                    this.layer.events.triggerEvent(
                        "sketchstarted", {vertex: vertex, feature: feature}
                    );
                }
            },
            this.callbacks
        );
        this.layer = layer;
        this.handlerOptions = this.handlerOptions || {};
        this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
            this.handlerOptions.layerOptions, {
                renderers: layer.renderers, rendererOptions: layer.rendererOptions
            }
        );
        if (!("multi" in this.handlerOptions)) {
            this.handlerOptions.multi = this.multi;
        }
        var sketchStyle = this.layer.styleMap && this.layer.styleMap.styles.temporary;
        if(sketchStyle) {
            this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
                this.handlerOptions.layerOptions,
                {styleMap: new OpenLayers.StyleMap({"default": sketchStyle})}
            );
        }
        this.handler = new handler(this, this.callbacks, this.handlerOptions);
    },

    /**
     * Method: drawFeature
     */
    drawFeature: function(geometry) {
        var feature = new OpenLayers.Feature.Vector(geometry);
        var proceed = this.layer.events.triggerEvent(
            "sketchcomplete", {feature: feature}
        );
        if(proceed !== false) {
            feature.state = OpenLayers.State.INSERT;
            this.layer.addFeatures([feature]);
            this.featureAdded(feature);
            this.events.triggerEvent("featureadded",{feature : feature});
        }
    },
    
    /**
     * APIMethod: insertXY
     * Insert a point in the current sketch given x & y coordinates.
     *
     * Parameters:
     * x - {Number} The x-coordinate of the point.
     * y - {Number} The y-coordinate of the point.
     */
    insertXY: function(x, y) {
        if (this.handler && this.handler.line) {
            this.handler.insertXY(x, y);
        }
    },

    /**
     * APIMethod: insertDeltaXY
     * Insert a point given offsets from the previously inserted point.
     *
     * Parameters:
     * dx - {Number} The x-coordinate offset of the point.
     * dy - {Number} The y-coordinate offset of the point.
     */
    insertDeltaXY: function(dx, dy) {
        if (this.handler && this.handler.line) {
            this.handler.insertDeltaXY(dx, dy);
        }
    },

    /**
     * APIMethod: insertDirectionLength
     * Insert a point in the current sketch given a direction and a length.
     *
     * Parameters:
     * direction - {Number} Degrees clockwise from the positive x-axis.
     * length - {Number} Distance from the previously drawn point.
     */
    insertDirectionLength: function(direction, length) {
        if (this.handler && this.handler.line) {
            this.handler.insertDirectionLength(direction, length);
        }
    },

    /**
     * APIMethod: insertDeflectionLength
     * Insert a point in the current sketch given a deflection and a length.
     *     The deflection should be degrees clockwise from the previously 
     *     digitized segment.
     *
     * Parameters:
     * deflection - {Number} Degrees clockwise from the previous segment.
     * length - {Number} Distance from the previously drawn point.
     */
    insertDeflectionLength: function(deflection, length) {
        if (this.handler && this.handler.line) {
            this.handler.insertDeflectionLength(deflection, length);
        }
    },
    
    /**
     * APIMethod: undo
     * Remove the most recently added point in the current sketch geometry.
     *
     * Returns: 
     * {Boolean} An edit was undone.
     */
    undo: function() {
        return this.handler.undo && this.handler.undo();
    },
    
    /**
     * APIMethod: redo
     * Reinsert the most recently removed point resulting from an <undo> call.
     *     The undo stack is deleted whenever a point is added by other means.
     *
     * Returns: 
     * {Boolean} An edit was redone.
     */
    redo: function() {
        return this.handler.redo && this.handler.redo();
    },
    
    /**
     * APIMethod: finishSketch
     * Finishes the sketch without including the currently drawn point.
     *     This method can be called to terminate drawing programmatically
     *     instead of waiting for the user to end the sketch.
     */
    finishSketch: function() {
        this.handler.finishGeometry();
    },

    /**
     * APIMethod: cancel
     * Cancel the current sketch.  This removes the current sketch and keeps
     *     the drawing control active.
     */
    cancel: function() {
        this.handler.cancel();
    },

    CLASS_NAME: "OpenLayers.Control.DrawFeature"
});
/* ======================================================================
    OpenLayers/Rule.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/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: null,
    
    /**
     * 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,
    
    /**
     * Property: symbolizers
     * {Array} Collection of symbolizers associated with this rule.  If 
     *     provided at construction, the symbolizers array has precedence
     *     over the deprecated symbolizer property.  Note that multiple 
     *     symbolizers are not currently supported by the vector renderers.
     *     Rules with multiple symbolizers are currently only useful for
     *     maintaining elements in an SLD document.
     */
    symbolizers: 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.symbolizer = {};
        OpenLayers.Util.extend(this, options);
        if (this.symbolizers) {
            delete this.symbolizer;
        }
        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
    },

    /** 
     * 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;
        delete this.symbolizers;
    },
    
    /**
     * 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;
    },
    
    /**
     * APIMethod: clone
     * Clones this rule.
     * 
     * Returns:
     * {<OpenLayers.Rule>} Clone of this rule.
     */
    clone: function() {
        var options = OpenLayers.Util.extend({}, this);
        if (this.symbolizers) {
            // clone symbolizers
            var len = this.symbolizers.length;
            options.symbolizers = new Array(len);
            for (var i=0; i<len; ++i) {
                options.symbolizers[i] = this.symbolizers[i].clone();
            }
        } else {
            // clone symbolizer
            options.symbolizer = {};
            var value, type;
            for(var key in this.symbolizer) {
                value = this.symbolizer[key];
                type = typeof value;
                if(type === "object") {
                    options.symbolizer[key] = OpenLayers.Util.extend({}, value);
                } else if(type === "string") {
                    options.symbolizer[key] = value;
                }
            }
        }
        // clone filter
        options.filter = this.filter && this.filter.clone();
        // clone context
        options.context = this.context && OpenLayers.Util.extend({}, this.context);
        return new OpenLayers.Rule(options);
    },
        
    CLASS_NAME: "OpenLayers.Rule"
});
/* ======================================================================
    OpenLayers/Handler/Pinch.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler.js
 */

/**
 * Class: OpenLayers.Handler.Pinch
 * The pinch handler is used to deal with sequences of browser events related
 *     to pinch gestures. The handler is used by controls that want to know
 *     when a pinch sequence begins, when a pinch is happening, and when it has
 *     finished.
 *
 * Controls that use the pinch handler typically construct it with callbacks
 *     for 'start', 'move', and 'done'.  Callbacks for these keys are
 *     called when the pinch begins, with each change, and when the pinch is
 *     done.
 *
 * Create a new pinch handler with the <OpenLayers.Handler.Pinch> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Handler>
 */
OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {

    /**
     * Property: started
     * {Boolean} When a touchstart event is received, we want to record it,
     *     but not set 'pinching' until the touchmove get started after
     *     starting.
     */
    started: false,

    /**
     * Property: stopDown
     * {Boolean} Stop propagation of touchstart events from getting to
     *     listeners on the same element. Default is false.
     */
    stopDown: false,

    /**
     * Property: pinching
     * {Boolean}
     */
    pinching: false,

    /**
     * Property: last
     * {Object} Object that store informations related to pinch last touch.
     */
    last: null,

    /**
     * Property: start
     * {Object} Object that store informations related to pinch touchstart.
     */
    start: null,

    /**
     * Constructor: OpenLayers.Handler.Pinch
     * Returns OpenLayers.Handler.Pinch
     *
     * 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 functions to be called when
     *     the pinch operation start, change, or is finished. The callbacks
     *     should expect to receive an object argument, which contains
     *     information about scale, distance, and position of touch points.
     * options - {Object}
     */

    /**
     * Method: touchstart
     * Handle touchstart events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    touchstart: function(evt) {
        var propagate = true;
        this.pinching = false;
        if (OpenLayers.Event.isMultiTouch(evt)) {
            this.started = true;
            this.last = this.start = {
                distance: this.getDistance(evt.touches),
                delta: 0,
                scale: 1
            };
            this.callback("start", [evt, this.start]);
            propagate = !this.stopDown;
        } else if (this.started) {
            // Some webkit versions send fake single-touch events during
            // multitouch, which cause the drag handler to trigger
            return false;
        } else {
            this.started = false;
            this.start = null;
            this.last = null;
        }
        // prevent document dragging
        OpenLayers.Event.preventDefault(evt);
        return propagate;
    },

    /**
     * Method: touchmove
     * Handle touchmove events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    touchmove: function(evt) {
        if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
            this.pinching = true;
            var current = this.getPinchData(evt);
            this.callback("move", [evt, current]);
            this.last = current;
            // prevent document dragging
            OpenLayers.Event.stop(evt);
        } else if (this.started) {
            // Some webkit versions send fake single-touch events during
            // multitouch, which cause the drag handler to trigger
            return false;
        }
        return true;
    },

    /**
     * Method: touchend
     * Handle touchend events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    touchend: function(evt) {
        if (this.started && !OpenLayers.Event.isMultiTouch(evt)) {
            this.started = false;
            this.pinching = false;
            this.callback("done", [evt, this.start, this.last]);
            this.start = null;
            this.last = null;
            return false;
        }
        return true;
    },

    /**
     * Method: activate
     * Activate the handler.
     *
     * Returns:
     * {Boolean} The handler was successfully activated.
     */
    activate: function() {
        var activated = false;
        if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
            this.pinching = false;
            activated = true;
        }
        return activated;
    },

    /**
     * Method: 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.started = false;
            this.pinching = false;
            this.start = null;
            this.last = null;
            deactivated = true;
        }
        return deactivated;
    },

    /**
     * Method: getDistance
     * Get the distance in pixels between two touches.
     *
     * Parameters:
     * touches - {Array(Object)}
     *
     * Returns:
     * {Number} The distance in pixels.
     */
    getDistance: function(touches) {
        var t0 = touches[0];
        var t1 = touches[1];
        return Math.sqrt(
            Math.pow(t0.olClientX - t1.olClientX, 2) +
            Math.pow(t0.olClientY - t1.olClientY, 2)
        );
    },


    /**
     * Method: getPinchData
     * Get informations about the pinch event.
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Object} Object that contains data about the current pinch.
     */
    getPinchData: function(evt) {
        var distance = this.getDistance(evt.touches);
        var scale = distance / this.start.distance;
        return {
            distance: distance,
            delta: this.last.distance - distance,
            scale: scale
        };
    },

    CLASS_NAME: "OpenLayers.Handler.Pinch"
});

/* ======================================================================
    OpenLayers/Handler/Hover.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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 with keys corresponding to callbacks
     *     that will be called by the handler. The callbacks should
     *     expect to receive a single argument, the event. Callbacks for
     *     'move', the mouse is moving, and 'pause', the mouse is pausing,
     *     are supported.
     * options - {Object} An optional object whose properties will be set on
     *     the handler.
     */

    /**
     * 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.viewPortDiv)) {
            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/Format/WFSDescribeFeatureType.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML.js
 * @requires OpenLayers/Format/OGCExceptionReport.js
 */
 
/**
 * Class: OpenLayers.Format.WFSDescribeFeatureType
 * Read WFS DescribeFeatureType response
 * 
 * Inherits from:
 *  - <OpenLayers.Format.XML>
 */
OpenLayers.Format.WFSDescribeFeatureType = OpenLayers.Class(
    OpenLayers.Format.XML, {

    /**
     * Property: regExes
     * Compiled regular expressions for manipulating strings.
     */
    regExes: {
        trimSpace: (/^\s*|\s*$/g)
    },
    
    /**
     * Property: namespaces
     * {Object} Mapping of namespace aliases to namespace URIs.
     */
    namespaces: {
        xsd: "http://www.w3.org/2001/XMLSchema"
    },
    
    /**
     * Constructor: OpenLayers.Format.WFSDescribeFeatureType
     * Create a new parser for WFS DescribeFeatureType responses.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */
    
    /**
     * 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: {
        "xsd": {
            "schema": function(node, obj) {
                var complexTypes = [];
                var customTypes = {};
                var schema = {
                    complexTypes: complexTypes,
                    customTypes: customTypes
                };
                var i, len;
                
                this.readChildNodes(node, schema);

                var attributes = node.attributes;
                var attr, name;
                for(i=0, len=attributes.length; i<len; ++i) {
                    attr = attributes[i];
                    name = attr.name;
                    if(name.indexOf("xmlns") === 0) {
                        this.setNamespace(name.split(":")[1] || "", attr.value);
                    } else {
                        obj[name] = attr.value;
                    }
                }
                obj.featureTypes = complexTypes;                
                obj.targetPrefix = this.namespaceAlias[obj.targetNamespace];
                
                // map complexTypes to names of customTypes
                var complexType, customType;
                for(i=0, len=complexTypes.length; i<len; ++i) {
                    complexType = complexTypes[i];
                    customType = customTypes[complexType.typeName];
                    if(customTypes[complexType.typeName]) {
                        complexType.typeName = customType.name;
                    }
                }
            },
            "complexType": function(node, obj) {
                var complexType = {
                    // this is a temporary typeName, it will be overwritten by
                    // the schema reader with the metadata found in the
                    // customTypes hash
                    "typeName": node.getAttribute("name")
                };
                this.readChildNodes(node, complexType);
                obj.complexTypes.push(complexType);
            },
            "complexContent": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "extension": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "sequence": function(node, obj) {
                var sequence = {
                    elements: []
                };
                this.readChildNodes(node, sequence);
                obj.properties = sequence.elements;
            },
            "element": function(node, obj) {
                var type;
                if(obj.elements) {
                    var element = {};
                    var attributes = node.attributes;
                    var attr;
                    for(var i=0, len=attributes.length; i<len; ++i) {
                        attr = attributes[i];
                        element[attr.name] = attr.value;
                    }
                    
                    type = element.type;
                    if(!type) {
                        type = {};
                        this.readChildNodes(node, type);
                        element.restriction = type;
                        element.type = type.base;
                    }
                    var fullType = type.base || type;
                    element.localType = fullType.split(":").pop();
                    obj.elements.push(element);
                    this.readChildNodes(node, element);
                }
                
                if(obj.complexTypes) {
                    type = node.getAttribute("type");
                    var localType = type.split(":").pop();
                    obj.customTypes[localType] = {
                        "name": node.getAttribute("name"),
                        "type": type
                    };
                }
            },
            "annotation": function(node, obj) {
                obj.annotation = {};
                this.readChildNodes(node, obj.annotation);
            },
            "appinfo": function(node, obj) {
                if (!obj.appinfo) {
                    obj.appinfo = [];
                }
                obj.appinfo.push(this.getChildValue(node));
            },
            "documentation": function(node, obj) {
                if (!obj.documentation) {
                    obj.documentation = [];
                }
                var value = this.getChildValue(node);
                obj.documentation.push({
                    lang: node.getAttribute("xml:lang"),
                    textContent: value.replace(this.regExes.trimSpace, "")
                });
            },
            "simpleType": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "restriction": function(node, obj) {
                obj.base = node.getAttribute("base");
                this.readRestriction(node, obj);
            }
        }
    },
    
    /**
     * Method: readRestriction
     * Reads restriction defined in the child nodes of a restriction element
     * 
     * Parameters:
     * node - {DOMElement} the node to parse
     * obj - {Object} the object that receives the read result
     */
    readRestriction: function(node, obj) {
        var children = node.childNodes;
        var child, nodeName, value;
        for(var i=0, len=children.length; i<len; ++i) {
            child = children[i];
            if(child.nodeType == 1) {
                nodeName = child.nodeName.split(":").pop();
                value = child.getAttribute("value");
                if(!obj[nodeName]) {
                    obj[nodeName] = value;
                } else {
                    if(typeof obj[nodeName] == "string") {
                        obj[nodeName] = [obj[nodeName]];
                    }
                    obj[nodeName].push(value);
                }
            }
        }
    },
    
    /**
     * Method: read
     *
     * Parameters:
     * data - {DOMElement|String} A WFS DescribeFeatureType document.
     *
     * Returns:
     * {Object} An object representing the WFS DescribeFeatureType response.
     */
    read: function(data) {
        if(typeof data == "string") { 
            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
        }
        if(data && data.nodeType == 9) {
            data = data.documentElement;
        }
        var schema = {};
        if (data.nodeName.split(":").pop() === 'ExceptionReport') {
            // an exception must have occurred, so parse it
            var parser = new OpenLayers.Format.OGCExceptionReport();
            schema.error = parser.read(data);
        } else {
            this.readNode(data, schema);
        }
        return schema;
    },
    
    CLASS_NAME: "OpenLayers.Format.WFSDescribeFeatureType" 

});
/* ======================================================================
    OpenLayers/Strategy/Refresh.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Strategy.js
 */

/**
 * Class: OpenLayers.Strategy.Refresh
 * A strategy that refreshes the layer. By default the strategy waits for a
 *     call to <refresh> before refreshing.  By configuring the strategy with 
 *     the <interval> option, refreshing can take place automatically.
 *
 * Inherits from:
 *  - <OpenLayers.Strategy>
 */
OpenLayers.Strategy.Refresh = OpenLayers.Class(OpenLayers.Strategy, {
    
    /**
     * Property: force
     * {Boolean} Force a refresh on the layer. Default is false.
     */
    force: false,

    /**
     * Property: interval
     * {Number} Auto-refresh. Default is 0.  If > 0, layer will be refreshed 
     *     every N milliseconds.
     */
    interval: 0,
    
    /**
     * Property: timer
     * {Number} The id of the timer.
     */
    timer: null,

    /**
     * Constructor: OpenLayers.Strategy.Refresh
     * Create a new Refresh strategy.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     */
   
    /**
     * APIMethod: activate
     * Activate the strategy. Register any listeners, do appropriate setup.
     * 
     * Returns:
     * {Boolean} True if the strategy was successfully activated.
     */
    activate: function() {
        var activated = OpenLayers.Strategy.prototype.activate.call(this);
        if(activated) {
            if(this.layer.visibility === true) {
                this.start();
            } 
            this.layer.events.on({
                "visibilitychanged": this.reset,
                scope: this
            });
        }
        return activated;
    },
    
    /**
     * APIMethod: deactivate
     * Deactivate the strategy. Unregister any listeners, do appropriate
     *     tear-down.
     * 
     * Returns:
     * {Boolean} True if the strategy was successfully deactivated.
     */
    deactivate: function() {
        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
        if(deactivated) {
            this.stop();
            this.layer.events.un({
                "visibilitychanged": this.reset,
                scope: this
            });
        }
        return deactivated;
    },
    
    /**
     * Method: reset
     * Start or cancel the refresh interval depending on the visibility of 
     *     the layer.
     */
    reset: function() {
        if(this.layer.visibility === true) {
            this.start();
        } else {
            this.stop();
        }
    },
    
    /**
     * Method: start
     * Start the refresh interval. 
     */
    start: function() {
        if(this.interval && typeof this.interval === "number" && 
            this.interval > 0) {

            this.timer = window.setInterval(
                OpenLayers.Function.bind(this.refresh, this),
                this.interval);
        }
    },
    
    /**
     * APIMethod: refresh
     * Tell the strategy to refresh which will refresh the layer.
     */
    refresh: function() {
        if (this.layer && this.layer.refresh && 
            typeof this.layer.refresh == "function") {

            this.layer.refresh({force: this.force});
        }
    },
   
    /**
     * Method: stop
     * Cancels the refresh interval. 
     */
    stop: function() {
        if(this.timer !== null) {
            window.clearInterval(this.timer);
            this.timer = null;
        }
    },
    
    CLASS_NAME: "OpenLayers.Strategy.Refresh" 
});
/* ======================================================================
    OpenLayers/Control/MousePosition.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Control.js
 */

/**
 * Class: OpenLayers.Control.MousePosition
 * The MousePosition control displays geographic coordinates of the mouse
 * pointer, as it is moved about the map.
 *
 * You can use the <prefix>- or <suffix>-properties to provide more information
 * about the displayed coordinates to the user:
 *
 * (code)
 *     var mousePositionCtrl = new OpenLayers.Control.MousePosition({
 *         prefix: '<a target="_blank" ' +
 *             'href="http://spatialreference.org/ref/epsg/4326/">' +
 *             'EPSG:4326</a> coordinates: '
 *         }
 *     );
 * (end code)
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.MousePosition = OpenLayers.Class(OpenLayers.Control, {

    /**
     * APIProperty: autoActivate
     * {Boolean} Activate the control when it is added to a map.  Default is
     *     true.
     */
    autoActivate: true,

    /**
     * Property: element
     * {DOMElement}
     */
    element: null,

    /**
     * APIProperty: prefix
     * {String} A string to be prepended to the current pointers coordinates
     *     when it is rendered.  Defaults to the empty string ''.
     */
    prefix: '',

    /**
     * APIProperty: separator
     * {String} A string to be used to seperate the two coordinates from each
     *     other.  Defaults to the string ', ', which will result in a
     *     rendered coordinate of e.g. '42.12, 21.22'.
     */
    separator: ', ',

    /**
     * APIProperty: suffix
     * {String} A string to be appended to the current pointers coordinates
     *     when it is rendered.  Defaults to the empty string ''.
     */
    suffix: '',

    /**
     * APIProperty: numDigits
     * {Integer} The number of digits each coordinate shall have when being
     *     rendered, Defaults to 5.
     */
    numDigits: 5,

    /**
     * APIProperty: granularity
     * {Integer}
     */
    granularity: 10,

    /**
     * APIProperty: emptyString
     * {String} Set this to some value to set when the mouse is outside the
     *     map.
     */
    emptyString: null,

    /**
     * Property: lastXy
     * {<OpenLayers.Pixel>}
     */
    lastXy: null,

    /**
     * APIProperty: displayProjection
     * {<OpenLayers.Projection>} The projection in which the mouse position is
     *     displayed.
     */
    displayProjection: null,

    /**
     * Constructor: OpenLayers.Control.MousePosition
     *
     * Parameters:
     * options - {Object} Options for control.
     */

    /**
     * Method: destroy
     */
     destroy: function() {
         this.deactivate();
         OpenLayers.Control.prototype.destroy.apply(this, arguments);
     },

    /**
     * APIMethod: activate
     */
    activate: function() {
        if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
            this.map.events.register('mousemove', this, this.redraw);
            this.map.events.register('mouseout', this, this.reset);
            this.redraw();
            return true;
        } else {
            return false;
        }
    },

    /**
     * APIMethod: deactivate
     */
    deactivate: function() {
        if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
            this.map.events.unregister('mousemove', this, this.redraw);
            this.map.events.unregister('mouseout', this, this.reset);
            this.element.innerHTML = "";
            return true;
        } else {
            return false;
        }
    },

    /**
     * Method: draw
     * {DOMElement}
     */
    draw: function() {
        OpenLayers.Control.prototype.draw.apply(this, arguments);

        if (!this.element) {
            this.div.left = "";
            this.div.top = "";
            this.element = this.div;
        }

        return this.div;
    },

    /**
     * Method: redraw
     */
    redraw: function(evt) {

        var lonLat;

        if (evt == null) {
            this.reset();
            return;
        } 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) {
                // map has not yet been properly initialized
                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;
        }
    },

    /**
     * Method: reset
     */
    reset: function(evt) {
        if (this.emptyString != null) {
            this.element.innerHTML = this.emptyString;
        }
    },

    /**
     * Method: formatOutput
     * Override to provide custom display output
     *
     * Parameters:
     * lonLat - {<OpenLayers.LonLat>} Location to display
     */
    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;
    },

    CLASS_NAME: "OpenLayers.Control.MousePosition"
});
/* ======================================================================
    OpenLayers/Protocol/HTTP.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Protocol.js
 * @requires OpenLayers/Request/XMLHttpRequest.js
 */

/**
 * if application uses the query string, for example, for BBOX parameters,
 * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
 */

/**
 * Class: OpenLayers.Protocol.HTTP
 * A basic HTTP protocol for vector layers.  Create a new instance with the
 *     <OpenLayers.Protocol.HTTP> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Protocol>
 */
OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {

    /**
     * Property: url
     * {String} Service URL, read-only, set through the options
     *     passed to constructor.
     */
    url: null,

    /**
     * Property: headers
     * {Object} HTTP request headers, read-only, set through the options
     *     passed to the constructor,
     *     Example: {'Content-Type': 'plain/text'}
     */
    headers: null,

    /**
     * Property: params
     * {Object} Parameters of GET requests, read-only, set through the options
     *     passed to the constructor,
     *     Example: {'bbox': '5,5,5,5'}
     */
    params: null,
    
    /**
     * Property: callback
     * {Object} Function to be called when the <read>, <create>,
     *     <update>, <delete> or <commit> operation completes, read-only,
     *     set through the options passed to the constructor.
     */
    callback: null,

    /**
     * Property: scope
     * {Object} Callback execution scope, read-only, set through the
     *     options passed to the constructor.
     */
    scope: null,

    /**
     * APIProperty: readWithPOST
     * {Boolean} true if read operations are done with POST requests
     *     instead of GET, defaults to false.
     */
    readWithPOST: false,

    /**
     * APIProperty: updateWithPOST
     * {Boolean} true if update operations are done with POST requests
     *     defaults to false.
     */
    updateWithPOST: false,
    
    /**
     * APIProperty: deleteWithPOST
     * {Boolean} true if delete operations are done with POST requests
     *     defaults to false.
     *     if true, POST data is set to output of format.write().
     */
    deleteWithPOST: false,

    /**
     * Property: wildcarded.
     * {Boolean} If true percent signs are added around values
     *     read from LIKE filters, for example if the protocol
     *     read method is passed a LIKE filter whose property
     *     is "foo" and whose value is "bar" the string
     *     "foo__ilike=%bar%" will be sent in the query string;
     *     defaults to false.
     */
    wildcarded: false,

    /**
     * APIProperty: srsInBBOX
     * {Boolean} Include the SRS identifier in BBOX query string parameter.  
     *     Default is false.  If true and the layer has a projection object set,
     *     any BBOX filter will be serialized with a fifth item identifying the
     *     projection.  E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
     */
    srsInBBOX: false,

    /**
     * Constructor: OpenLayers.Protocol.HTTP
     * A class for giving layers generic HTTP protocol.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     *
     * Valid options include:
     * url - {String}
     * headers - {Object} 
     * params - {Object} URL parameters for GET requests
     * format - {<OpenLayers.Format>}
     * callback - {Function}
     * scope - {Object}
     */
    initialize: function(options) {
        options = options || {};
        this.params = {};
        this.headers = {};
        OpenLayers.Protocol.prototype.initialize.apply(this, arguments);

        if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
            var format = new OpenLayers.Format.QueryStringFilter({
                wildcarded: this.wildcarded,
                srsInBBOX: this.srsInBBOX
            });
            this.filterToParams = function(filter, params) {
                return format.write(filter, params);
            };
        }
    },
    
    /**
     * APIMethod: destroy
     * Clean up the protocol.
     */
    destroy: function() {
        this.params = null;
        this.headers = null;
        OpenLayers.Protocol.prototype.destroy.apply(this);
    },

    /**
     * APIMethod: filterToParams
     * Optional method to translate an <OpenLayers.Filter> object into an object
     *     that can be serialized as request query string provided.  If a custom
     *     method is not provided, the filter will be serialized using the 
     *     <OpenLayers.Format.QueryStringFilter> class.
     *
     * Parameters:
     * filter - {<OpenLayers.Filter>} filter to convert.
     * params - {Object} The parameters object.
     *
     * Returns:
     * {Object} The resulting parameters object.
     */
    
    /**
     * APIMethod: read
     * Construct a request for reading new features.
     *
     * Parameters:
     * options - {Object} Optional object for configuring the request.
     *     This object is modified and should not be reused.
     *
     * Valid options:
     * url - {String} Url for the request.
     * params - {Object} Parameters to get serialized as a query string.
     * headers - {Object} Headers to be set on the request.
     * filter - {<OpenLayers.Filter>} Filter to get serialized as a
     *     query string.
     * readWithPOST - {Boolean} If the request should be done with POST.
     *
     * Returns:
     * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
     *     references the HTTP request, this object is also passed to the
     *     callback function when the request completes, its "features" property
     *     is then populated with the features received from the server.
     */
    read: function(options) {
        OpenLayers.Protocol.prototype.read.apply(this, arguments);
        options = options || {};
        options.params = OpenLayers.Util.applyDefaults(
            options.params, this.options.params);
        options = OpenLayers.Util.applyDefaults(options, this.options);
        if (options.filter && this.filterToParams) {
            options.params = this.filterToParams(
                options.filter, options.params
            );
        }
        var readWithPOST = (options.readWithPOST !== undefined) ?
                           options.readWithPOST : this.readWithPOST;
        var resp = new OpenLayers.Protocol.Response({requestType: "read"});
        if(readWithPOST) {
            var headers = options.headers || {};
            headers["Content-Type"] = "application/x-www-form-urlencoded";
            resp.priv = OpenLayers.Request.POST({
                url: options.url,
                callback: this.createCallback(this.handleRead, resp, options),
                data: OpenLayers.Util.getParameterString(options.params),
                headers: headers
            });
        } else {
            resp.priv = OpenLayers.Request.GET({
                url: options.url,
                callback: this.createCallback(this.handleRead, resp, options),
                params: options.params,
                headers: options.headers
            });
        }
        return resp;
    },

    /**
     * Method: handleRead
     * Individual callbacks are created for read, create and update, should
     *     a subclass need to override each one separately.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
     *     the user callback.
     * options - {Object} The user options passed to the read call.
     */
    handleRead: function(resp, options) {
        this.handleResponse(resp, options);
    },

    /**
     * APIMethod: 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.
     *     This object is modified and should not be reused.
     *
     * Returns:
     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *     object, whose "priv" property references the HTTP request, this 
     *     object is also passed to the callback function when the request
     *     completes, its "features" property is then populated with the
     *     the features received from the server.
     */
    create: function(features, options) {
        options = OpenLayers.Util.applyDefaults(options, this.options);

        var resp = new OpenLayers.Protocol.Response({
            reqFeatures: features,
            requestType: "create"
        });

        resp.priv = OpenLayers.Request.POST({
            url: options.url,
            callback: this.createCallback(this.handleCreate, resp, options),
            headers: options.headers,
            data: this.format.write(features)
        });

        return resp;
    },

    /**
     * Method: handleCreate
     * Called the the request issued by <create> is complete.  May be overridden
     *     by subclasses.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
     *     any user callback.
     * options - {Object} The user options passed to the create call.
     */
    handleCreate: function(resp, options) {
        this.handleResponse(resp, options);
    },

    /**
     * APIMethod: update
     * Construct a request updating modified feature.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>}
     * options - {Object} Optional object for configuring the request.
     *     This object is modified and should not be reused.
     *
     * Returns:
     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *     object, whose "priv" property references the HTTP request, this 
     *     object is also passed to the callback function when the request
     *     completes, its "features" property is then populated with the
     *     the feature received from the server.
     */
    update: function(feature, options) {
        options = options || {};
        var url = options.url ||
                  feature.url ||
                  this.options.url + "/" + feature.fid;
        options = OpenLayers.Util.applyDefaults(options, this.options);

        var resp = new OpenLayers.Protocol.Response({
            reqFeatures: feature,
            requestType: "update"
        });

        var method = this.updateWithPOST ? "POST" : "PUT";
        resp.priv = OpenLayers.Request[method]({
            url: url,
            callback: this.createCallback(this.handleUpdate, resp, options),
            headers: options.headers,
            data: this.format.write(feature)
        });

        return resp;
    },

    /**
     * Method: handleUpdate
     * Called the the request issued by <update> is complete.  May be overridden
     *     by subclasses.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
     *     any user callback.
     * options - {Object} The user options passed to the update call.
     */
    handleUpdate: function(resp, options) {
        this.handleResponse(resp, options);
    },

    /**
     * APIMethod: delete
     * Construct a request deleting a removed feature.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>}
     * options - {Object} Optional object for configuring the request.
     *     This object is modified and should not be reused.
     *
     * Returns:
     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *     object, whose "priv" property references the HTTP request, this 
     *     object is also passed to the callback function when the request
     *     completes.
     */
    "delete": function(feature, options) {
        options = options || {};
        var url = options.url ||
                  feature.url ||
                  this.options.url + "/" + feature.fid;
        options = OpenLayers.Util.applyDefaults(options, this.options);

        var resp = new OpenLayers.Protocol.Response({
            reqFeatures: feature,
            requestType: "delete"
        });

        var method = this.deleteWithPOST ? "POST" : "DELETE";
        var requestOptions = {
            url: url,
            callback: this.createCallback(this.handleDelete, resp, options),
            headers: options.headers
        };
        if (this.deleteWithPOST) {
            requestOptions.data = this.format.write(feature);
        }
        resp.priv = OpenLayers.Request[method](requestOptions);

        return resp;
    },

    /**
     * Method: handleDelete
     * Called the the request issued by <delete> is complete.  May be overridden
     *     by subclasses.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
     *     any user callback.
     * options - {Object} The user options passed to the delete call.
     */
    handleDelete: function(resp, options) {
        this.handleResponse(resp, options);
    },

    /**
     * Method: handleResponse
     * Called by CRUD specific handlers.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
     *     any user callback.
     * options - {Object} The user options passed to the create, read, update,
     *     or delete call.
     */
    handleResponse: function(resp, options) {
        var request = resp.priv;
        if(options.callback) {
            if(request.status >= 200 && request.status < 300) {
                // success
                if(resp.requestType != "delete") {
                    resp.features = this.parseFeatures(request);
                }
                resp.code = OpenLayers.Protocol.Response.SUCCESS;
            } else {
                // failure
                resp.code = OpenLayers.Protocol.Response.FAILURE;
            }
            options.callback.call(options.scope, resp);
        }
    },

    /**
     * Method: parseFeatures
     * Read HTTP response body and return features.
     *
     * Parameters:
     * request - {XMLHttpRequest} The request object
     *
     * Returns:
     * {Array({<OpenLayers.Feature.Vector>})} or
     *     {<OpenLayers.Feature.Vector>} Array of features or a single feature.
     */
    parseFeatures: function(request) {
        var doc = request.responseXML;
        if (!doc || !doc.documentElement) {
            doc = request.responseText;
        }
        if (!doc || doc.length <= 0) {
            return null;
        }
        return this.format.read(doc);
    },

    /**
     * APIMethod: commit
     * Iterate over each feature and take action based on the feature state.
     *     Possible actions are create, update and delete.
     *
     * Parameters:
     * features - {Array({<OpenLayers.Feature.Vector>})}
     * options - {Object} Optional object for setting up intermediate commit
     *     callbacks.
     *
     * Valid options:
     * create - {Object} Optional object to be passed to the <create> method.
     * update - {Object} Optional object to be passed to the <update> method.
     * delete - {Object} Optional object to be passed to the <delete> method.
     * callback - {Function} Optional function to be called when the commit
     *     is complete.
     * scope - {Object} Optional object to be set as the scope of the callback.
     *
     * Returns:
     * {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
     *     one per request made to the server, each object's "priv" property
     *     references the corresponding HTTP request.
     */
    commit: function(features, options) {
        options = OpenLayers.Util.applyDefaults(options, this.options);
        var resp = [], nResponses = 0;
        
        // Divide up features before issuing any requests.  This properly
        // counts requests in the event that any responses come in before
        // all requests have been issued.
        var types = {};
        types[OpenLayers.State.INSERT] = [];
        types[OpenLayers.State.UPDATE] = [];
        types[OpenLayers.State.DELETE] = [];
        var feature, list, requestFeatures = [];
        for(var i=0, len=features.length; i<len; ++i) {
            feature = features[i];
            list = types[feature.state];
            if(list) {
                list.push(feature);
                requestFeatures.push(feature); 
            }
        }
        // tally up number of requests
        var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
            types[OpenLayers.State.UPDATE].length +
            types[OpenLayers.State.DELETE].length;
        
        // This response will be sent to the final callback after all the others
        // have been fired.
        var success = true;
        var finalResponse = new OpenLayers.Protocol.Response({
            reqFeatures: requestFeatures        
        });
        
        function insertCallback(response) {
            var len = response.features ? response.features.length : 0;
            var fids = new Array(len);
            for(var i=0; i<len; ++i) {
                fids[i] = response.features[i].fid;
            }   
            finalResponse.insertIds = fids;
            callback.apply(this, [response]);
        }
 
        function callback(response) {
            this.callUserCallback(response, options);
            success = success && response.success();
            nResponses++;
            if (nResponses >= nRequests) {
                if (options.callback) {
                    finalResponse.code = success ? 
                        OpenLayers.Protocol.Response.SUCCESS :
                        OpenLayers.Protocol.Response.FAILURE;
                    options.callback.apply(options.scope, [finalResponse]);
                }    
            }
        }

        // start issuing requests
        var queue = types[OpenLayers.State.INSERT];
        if(queue.length > 0) {
            resp.push(this.create(
                queue, OpenLayers.Util.applyDefaults(
                    {callback: insertCallback, scope: this}, options.create
                )
            ));
        }
        queue = types[OpenLayers.State.UPDATE];
        for(var i=queue.length-1; i>=0; --i) {
            resp.push(this.update(
                queue[i], OpenLayers.Util.applyDefaults(
                    {callback: callback, scope: this}, options.update
                ))
            );
        }
        queue = types[OpenLayers.State.DELETE];
        for(var i=queue.length-1; i>=0; --i) {
            resp.push(this["delete"](
                queue[i], OpenLayers.Util.applyDefaults(
                    {callback: callback, scope: this}, options["delete"]
                ))
            );
        }
        return resp;
    },

    /**
     * APIMethod: abort
     * Abort an ongoing request, the response object passed to
     * this method must come from this HTTP protocol (as a result
     * of a create, read, update, delete or commit operation).
     *
     * Parameters:
     * response - {<OpenLayers.Protocol.Response>}
     */
    abort: function(response) {
        if (response) {
            response.priv.abort();
        }
    },

    /**
     * Method: callUserCallback
     * This method is used from within the commit method each time an
     *     an HTTP response is received from the server, it is responsible
     *     for calling the user-supplied callbacks.
     *
     * Parameters:
     * resp - {<OpenLayers.Protocol.Response>}
     * options - {Object} The map of options passed to the commit call.
     */
    callUserCallback: function(resp, options) {
        var opt = options[resp.requestType];
        if(opt && opt.callback) {
            opt.callback.call(opt.scope, resp);
        }
    },

    CLASS_NAME: "OpenLayers.Protocol.HTTP" 
});
/* ======================================================================
    OpenLayers/Strategy/Cluster.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Strategy.js
 */

/**
 * Class: OpenLayers.Strategy.Cluster
 * Strategy for vector feature clustering.
 *
 * Inherits from:
 *  - <OpenLayers.Strategy>
 */
OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, {
    
    /**
     * APIProperty: distance
     * {Integer} Pixel distance between features that should be considered a
     *     single cluster.  Default is 20 pixels.
     */
    distance: 20,
    
    /**
     * APIProperty: threshold
     * {Integer} Optional threshold below which original features will be
     *     added to the layer instead of clusters.  For example, a threshold
     *     of 3 would mean that any time there are 2 or fewer features in
     *     a cluster, those features will be added directly to the layer instead
     *     of a cluster representing those features.  Default is null (which is
     *     equivalent to 1 - meaning that clusters may contain just one feature).
     */
    threshold: null,
    
    /**
     * Property: features
     * {Array(<OpenLayers.Feature.Vector>)} Cached features.
     */
    features: null,
    
    /**
     * Property: clusters
     * {Array(<OpenLayers.Feature.Vector>)} Calculated clusters.
     */
    clusters: null,
    
    /**
     * Property: clustering
     * {Boolean} The strategy is currently clustering features.
     */
    clustering: false,
    
    /**
     * Property: resolution
     * {Float} The resolution (map units per pixel) of the current cluster set.
     */
    resolution: null,

    /**
     * Constructor: OpenLayers.Strategy.Cluster
     * Create a new clustering strategy.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     */
    
    /**
     * APIMethod: activate
     * Activate the strategy.  Register any listeners, do appropriate setup.
     * 
     * Returns:
     * {Boolean} The strategy was successfully activated.
     */
    activate: function() {
        var activated = OpenLayers.Strategy.prototype.activate.call(this);
        if(activated) {
            this.layer.events.on({
                "beforefeaturesadded": this.cacheFeatures,
                "featuresremoved": this.clearCache,
                "moveend": this.cluster,
                scope: this
            });
        }
        return activated;
    },
    
    /**
     * APIMethod: deactivate
     * Deactivate the strategy.  Unregister any listeners, do appropriate
     *     tear-down.
     * 
     * Returns:
     * {Boolean} The strategy was successfully deactivated.
     */
    deactivate: function() {
        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
        if(deactivated) {
            this.clearCache();
            this.layer.events.un({
                "beforefeaturesadded": this.cacheFeatures,
                "featuresremoved": this.clearCache,
                "moveend": this.cluster,
                scope: this
            });
        }
        return deactivated;
    },
    
    /**
     * Method: cacheFeatures
     * Cache features before they are added to the layer.
     *
     * Parameters:
     * event - {Object} The event that this was listening for.  This will come
     *     with a batch of features to be clustered.
     *     
     * Returns:
     * {Boolean} False to stop features from being added to the layer.
     */
    cacheFeatures: function(event) {
        var propagate = true;
        if(!this.clustering) {
            this.clearCache();
            this.features = event.features;
            this.cluster();
            propagate = false;
        }
        return propagate;
    },
    
    /**
     * Method: clearCache
     * Clear out the cached features.
     */
    clearCache: function() {
        if(!this.clustering) {
            this.features = null;
        }
    },
    
    /**
     * Method: cluster
     * Cluster features based on some threshold distance.
     *
     * Parameters:
     * event - {Object} The event received when cluster is called as a
     *     result of a moveend event.
     */
    cluster: function(event) {
        if((!event || event.zoomChanged) && this.features) {
            var resolution = this.layer.map.getResolution();
            if(resolution != this.resolution || !this.clustersExist()) {
                this.resolution = resolution;
                var clusters = [];
                var feature, clustered, cluster;
                for(var i=0; i<this.features.length; ++i) {
                    feature = this.features[i];
                    if(feature.geometry) {
                        clustered = false;
                        for(var j=clusters.length-1; j>=0; --j) {
                            cluster = clusters[j];
                            if(this.shouldCluster(cluster, feature)) {
                                this.addToCluster(cluster, feature);
                                clustered = true;
                                break;
                            }
                        }
                        if(!clustered) {
                            clusters.push(this.createCluster(this.features[i]));
                        }
                    }
                }
                this.clustering = true;
                this.layer.removeAllFeatures();
                this.clustering = false;
                if(clusters.length > 0) {
                    if(this.threshold > 1) {
                        var clone = clusters.slice();
                        clusters = [];
                        var candidate;
                        for(var i=0, len=clone.length; i<len; ++i) {
                            candidate = clone[i];
                            if(candidate.attributes.count < this.threshold) {
                                Array.prototype.push.apply(clusters, candidate.cluster);
                            } else {
                                clusters.push(candidate);
                            }
                        }
                    }
                    this.clustering = true;
                    // A legitimate feature addition could occur during this
                    // addFeatures call.  For clustering to behave well, features
                    // should be removed from a layer before requesting a new batch.
                    this.layer.addFeatures(clusters);
                    this.clustering = false;
                }
                this.clusters = clusters;
            }
        }
    },
    
    /**
     * Method: clustersExist
     * Determine whether calculated clusters are already on the layer.
     *
     * Returns:
     * {Boolean} The calculated clusters are already on the layer.
     */
    clustersExist: function() {
        var exist = false;
        if(this.clusters && this.clusters.length > 0 &&
           this.clusters.length == this.layer.features.length) {
            exist = true;
            for(var i=0; i<this.clusters.length; ++i) {
                if(this.clusters[i] != this.layer.features[i]) {
                    exist = false;
                    break;
                }
            }
        }
        return exist;
    },
    
    /**
     * Method: shouldCluster
     * Determine whether to include a feature in a given cluster.
     *
     * Parameters:
     * cluster - {<OpenLayers.Feature.Vector>} A cluster.
     * feature - {<OpenLayers.Feature.Vector>} A feature.
     *
     * Returns:
     * {Boolean} The feature should be included in the cluster.
     */
    shouldCluster: function(cluster, feature) {
        var cc = cluster.geometry.getBounds().getCenterLonLat();
        var fc = feature.geometry.getBounds().getCenterLonLat();
        var distance = (
            Math.sqrt(
                Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2)
            ) / this.resolution
        );
        return (distance <= this.distance);
    },
    
    /**
     * Method: addToCluster
     * Add a feature to a cluster.
     *
     * Parameters:
     * cluster - {<OpenLayers.Feature.Vector>} A cluster.
     * feature - {<OpenLayers.Feature.Vector>} A feature.
     */
    addToCluster: function(cluster, feature) {
        cluster.cluster.push(feature);
        cluster.attributes.count += 1;
    },
    
    /**
     * Method: createCluster
     * Given a feature, create a cluster.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>}
     *
     * Returns:
     * {<OpenLayers.Feature.Vector>} A cluster.
     */
    createCluster: function(feature) {
        var center = feature.geometry.getBounds().getCenterLonLat();
        var cluster = new OpenLayers.Feature.Vector(
            new OpenLayers.Geometry.Point(center.lon, center.lat),
            {count: 1}
        );
        cluster.cluster = [feature];
        return cluster;
    },

    CLASS_NAME: "OpenLayers.Strategy.Cluster" 
});
/* ======================================================================
    OpenLayers/Control/OverviewMap.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/** 
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/BaseTypes.js
 * @requires OpenLayers/Events/buttonclick.js
 * @requires OpenLayers/Map.js
 * @requires OpenLayers/Handler/Click.js
 * @requires OpenLayers/Handler/Drag.js
 */

/**
 * Class: OpenLayers.Control.OverviewMap
 * The OverMap control creates a small overview map, useful to display the 
 * extent of a zoomed map and your main map and provide additional 
 * navigation options to the User.  By default the overview map is drawn in
 * the lower right corner of the main map. Create a new overview map with the
 * <OpenLayers.Control.OverviewMap> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.OverviewMap = OpenLayers.Class(OpenLayers.Control, {

    /**
     * Property: element
     * {DOMElement} The DOM element that contains the overview map
     */
    element: null,
    
    /**
     * APIProperty: ovmap
     * {<OpenLayers.Map>} A reference to the overview map itself.
     */
    ovmap: null,

    /**
     * APIProperty: size
     * {<OpenLayers.Size>} The overvew map size in pixels.  Note that this is
     * the size of the map itself - the element that contains the map (default
     * class name olControlOverviewMapElement) may have padding or other style
     * attributes added via CSS.
     */
    size: {w: 180, h: 90},

    /**
     * APIProperty: layers
     * {Array(<OpenLayers.Layer>)} Ordered list of layers in the overview map.
     * If none are sent at construction, the base layer for the main map is used.
     */
    layers: null,
    
    /**
     * APIProperty: minRectSize
     * {Integer} The minimum width or height (in pixels) of the extent
     *     rectangle on the overview map.  When the extent rectangle reaches
     *     this size, it will be replaced depending on the value of the
     *     <minRectDisplayClass> property.  Default is 15 pixels.
     */
    minRectSize: 15,
    
    /**
     * APIProperty: minRectDisplayClass
     * {String} Replacement style class name for the extent rectangle when
     *     <minRectSize> is reached.  This string will be suffixed on to the
     *     displayClass.  Default is "RectReplacement".
     *
     * Example CSS declaration:
     * (code)
     * .olControlOverviewMapRectReplacement {
     *     overflow: hidden;
     *     cursor: move;
     *     background-image: url("img/overview_replacement.gif");
     *     background-repeat: no-repeat;
     *     background-position: center;
     * }
     * (end)
     */
    minRectDisplayClass: "RectReplacement",

    /**
     * APIProperty: minRatio
     * {Float} The ratio of the overview map resolution to the main map
     *     resolution at which to zoom farther out on the overview map.
     */
    minRatio: 8,

    /**
     * APIProperty: maxRatio
     * {Float} The ratio of the overview map resolution to the main map
     *     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
     *     the overview map's map constructor.  These should include any
     *     non-default options that the main map was constructed with.
     */
    mapOptions: null,

    /**
     * APIProperty: autoPan
     * {Boolean} Always pan the overview map, so the extent marker remains in
     *     the center.  Default is false.  If true, when you drag the extent
     *     marker, the overview map will update itself so the marker returns
     *     to the center.
     */
    autoPan: false,
    
    /**
     * Property: handlers
     * {Object}
     */
    handlers: null,

    /**
     * Property: resolutionFactor
     * {Object}
     */
    resolutionFactor: 1,

    /**
     * APIProperty: maximized
     * {Boolean} Start as maximized (visible). Defaults to false.
     */
    maximized: false,

    /**
     * APIProperty: maximizeTitle
     * {String}  This property is used for showing a tooltip over the  
     * maximize div. Defaults to "" (no title).
     */ 
    maximizeTitle: "",

    /**
     * APIProperty: minimizeTitle
     * {String}  This property is used for showing a tooltip over the  
     * minimize div. Defaults to "" (no title).
     */ 
    minimizeTitle: "",

    /**
     * Constructor: OpenLayers.Control.OverviewMap
     * Create a new overview map
     *
     * Parameters:
     * options - {Object} Properties of this object will be set on the overview
     * map object.  Note, to set options on the map object contained in this
     * control, set <mapOptions> as one of the options properties.
     */
    initialize: function(options) {
        this.layers = [];
        this.handlers = {};
        OpenLayers.Control.prototype.initialize.apply(this, [options]);
    },
    
    /**
     * APIMethod: destroy
     * Deconstruct the control
     */
    destroy: function() {
        if (!this.mapDiv) { // we've already been destroyed
            return;
        }
        if (this.handlers.click) {
            this.handlers.click.destroy();
        }
        if (this.handlers.drag) {
            this.handlers.drag.destroy();
        }

        this.ovmap && this.ovmap.viewPortDiv.removeChild(this.extentRectangle);
        this.extentRectangle = null;

        if (this.rectEvents) {
            this.rectEvents.destroy();
            this.rectEvents = null;
        }

        if (this.ovmap) {
            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) {
            this.div.removeChild(this.maximizeDiv);
            this.maximizeDiv = null;
        }
        
        if (this.minimizeDiv) {
            this.div.removeChild(this.minimizeDiv);
            this.minimizeDiv = null;
        }

        this.map.events.un({
            buttonclick: this.onButtonClick,
            moveend: this.update,
            changebaselayer: this.baseLayerDraw,
            scope: this
        });

        OpenLayers.Control.prototype.destroy.apply(this, arguments);    
    },

    /**
     * Method: draw
     * Render the control in the browser.
     */    
    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;
            }
        }

        // create overview map DOM elements
        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;  //HACK
        this.extentRectangle.className = this.displayClass+'ExtentRectangle';

        this.element.appendChild(this.mapDiv);  

        this.div.appendChild(this.element);

        // Optionally add min/max buttons if the control will go in the
        // map viewport.
        if(!this.outsideViewport) {
            this.div.className += " " + this.displayClass + 'Container';
            // maximize button div
            var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png');
            this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                        this.displayClass + 'MaximizeButton', 
                                        null, 
                                        null, 
                                        img, 
                                        'absolute');
            this.maximizeDiv.style.display = 'none';
            this.maximizeDiv.className = this.displayClass + 'MaximizeButton olButton';
            if (this.maximizeTitle) {
                this.maximizeDiv.title = this.maximizeTitle;
            }
            this.div.appendChild(this.maximizeDiv);
    
            // minimize button div
            var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png');
            this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                        'OpenLayers_Control_minimizeDiv', 
                                        null, 
                                        null, 
                                        img, 
                                        'absolute');
            this.minimizeDiv.style.display = 'none';
            this.minimizeDiv.className = this.displayClass + 'MinimizeButton olButton';
            if (this.minimizeTitle) {
                this.minimizeDiv.title = this.minimizeTitle;
            }
            this.div.appendChild(this.minimizeDiv);            
            this.minimizeControl();
        } else {
            // show the overview map
            this.element.style.display = '';
        }
        if(this.map.getExtent()) {
            this.update();
        }
        
        this.map.events.on({
            buttonclick: this.onButtonClick,
            moveend: this.update,
            scope: this
        });
        
        if (this.maximized) {
            this.maximizeControl();
        }
        return this.div;
    },
    
    /**
     * Method: baseLayerDraw
     * Draw the base layer - called if unable to complete in the initial draw
     */
    baseLayerDraw: function() {
        this.draw();
        this.map.events.unregister("changebaselayer", this, this.baseLayerDraw);
    },

    /**
     * Method: rectDrag
     * Handle extent rectangle drag
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>} The pixel location of the drag.
     */
    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();
            // don't allow dragging off of parent element
            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));
        }
    },
    
    /**
     * Method: mapDivClick
     * Handle browser events
     *
     * Parameters:
     * evt - {<OpenLayers.Event>} evt
     */
    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();
    },
    
    /**
     * Method: onButtonClick
     *
     * Parameters:
     * evt - {Event}
     */
    onButtonClick: function(evt) {
        if (evt.buttonElement === this.minimizeDiv) {
            this.minimizeControl();
        } else if (evt.buttonElement === this.maximizeDiv) {
            this.maximizeControl();
        }
    },

    /**
     * Method: maximizeControl
     * Unhide the control.  Called when the control is in the map viewport.
     *
     * Parameters:
     * e - {<OpenLayers.Event>}
     */
    maximizeControl: function(e) {
        this.element.style.display = '';
        this.showToggle(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 - {<OpenLayers.Event>}
     */
    minimizeControl: function(e) {
        this.element.style.display = 'none';
        this.showToggle(true);
        if (e != null) {
            OpenLayers.Event.stop(e);                                            
        }
    },

    /**
     * Method: showToggle
     * Hide/Show the toggle depending on whether the control is minimized
     *
     * Parameters:
     * minimize - {Boolean} 
     */
    showToggle: function(minimize) {
        if (this.maximizeDiv) {
            this.maximizeDiv.style.display = minimize ? '' : 'none';
        }
        if (this.minimizeDiv) {
            this.minimizeDiv.style.display = minimize ? 'none' : '';
        }
    },

    /**
     * Method: update
     * Update the overview map after layers move.
     */
    update: function() {
        if(this.ovmap == null) {
            this.createMap();
        }
        
        if(this.autoPan || !this.isSuitableOverview()) {
            this.updateOverview();
        }
        
        // update extent rectangle
        this.updateRectToMap();
    },
    
    /**
     * Method: isSuitableOverview
     * Determines if the overview map is suitable given the extent and
     * resolution of the main map.
     */
    isSuitableOverview: function() {
        var mapExtent = this.map.getExtent();
        var maxExtent = this.map.getMaxExtent();
        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)));
    },
    
    /**
     * Method updateOverview
     * Called by <update> if <isSuitableOverview> returns true
     */
    updateOverview: function() {
        var mapRes = this.map.getResolution();
        var targetRes = this.ovmap.getResolution();
        var resRatio = targetRes / mapRes;
        if(resRatio > this.maxRatio) {
            // zoom in overview map
            targetRes = this.minRatio * mapRes;            
        } else if(resRatio <= this.minRatio) {
            // zoom out overview map
            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();
    },
    
    /**
     * Method: createMap
     * Construct the map that this control contains
     */
    createMap: function() {
        // create the overview map
        var options = OpenLayers.Util.extend(
                        {controls: [], maxResolution: 'auto', 
                         fallThrough: false}, this.mapOptions);
        this.ovmap = new OpenLayers.Map(this.mapDiv, options);
        this.ovmap.viewPortDiv.appendChild(this.extentRectangle);
        
        // prevent ovmap from being destroyed when the page unloads, because
        // the OverviewMap control has to do this (and does it).
        OpenLayers.Event.stopObserving(window, 'unload', this.ovmap.unloadDestroy);
        
        this.ovmap.addLayers(this.layers);
        this.ovmap.zoomToMaxExtent();
        // check extent rectangle border width
        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();
            }
        });

        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;
        }
    },
        
    /**
     * Method: updateRectToMap
     * Updates the extent rectangle position and size to match the map extent
     */
    updateRectToMap: function() {
        // 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(bounds);
        if (pxBounds) {
            this.setRectPxBounds(pxBounds);
        }
    },
    
    /**
     * Method: updateMapToRect
     * Updates the map extent to match the extent rectangle position and size
     */
    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());
    },

    /**
     * Method: setRectPxBounds
     * Set extent rectangle pixel bounds.
     *
     * Parameters:
     * pxBounds - {<OpenLayers.Bounds>}
     */
    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)
        );
    },

    /**
     * Method: getRectBoundsFromMapBounds
     * Get the rect bounds from the map bounds.
     *
     * Parameters:
     * lonLatBounds - {<OpenLayers.Bounds>}
     *
     * Returns:
     * {<OpenLayers.Bounds>}A bounds which is the passed-in map lon/lat extent
     * translated into pixel bounds for the overview map
     */
    getRectBoundsFromMapBounds: function(lonLatBounds) {
        var leftBottomPx = this.getOverviewPxFromLonLat({
            lon: lonLatBounds.left,
            lat: lonLatBounds.bottom
        });
        var rightTopPx = this.getOverviewPxFromLonLat({
            lon: lonLatBounds.right,
            lat: lonLatBounds.top
        });
        var bounds = null;
        if (leftBottomPx && rightTopPx) {
            bounds = new OpenLayers.Bounds(leftBottomPx.x, leftBottomPx.y,
                                           rightTopPx.x, rightTopPx.y);
        }
        return bounds;
    },

    /**
     * Method: getMapBoundsFromRectBounds
     * Get the map bounds from the rect bounds.
     *
     * Parameters:
     * pxBounds - {<OpenLayers.Bounds>}
     *
     * Returns:
     * {<OpenLayers.Bounds>} Bounds which is the passed-in overview rect bounds
     * translated into lon/lat bounds for the overview map
     */
    getMapBoundsFromRectBounds: function(pxBounds) {
        var leftBottomLonLat = this.getLonLatFromOverviewPx({
            x: pxBounds.left,
            y: pxBounds.bottom
        });
        var rightTopLonLat = this.getLonLatFromOverviewPx({
            x: pxBounds.right,
            y: pxBounds.top
        });
        return new OpenLayers.Bounds(leftBottomLonLat.lon, leftBottomLonLat.lat,
                                     rightTopLonLat.lon, rightTopLonLat.lat);
    },

    /**
     * Method: getLonLatFromOverviewPx
     * Get a map location from a pixel location
     *
     * Parameters:
     * overviewMapPx - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or
     *                                             an object with a
     *                                             'x' and 'y' properties.
     *
     * Returns:
     * {Object} Location which is the passed-in overview map
     * OpenLayers.Pixel, translated into lon/lat by the overview
     * map. An object with a 'lon' and 'lat' properties.
     */
    getLonLatFromOverviewPx: function(overviewMapPx) {
        var size = this.ovmap.size;
        var res  = this.ovmap.getResolution();
        var center = this.ovmap.getExtent().getCenterLonLat();
    
        var deltaX = overviewMapPx.x - (size.w / 2);
        var deltaY = overviewMapPx.y - (size.h / 2);

        return {
            lon: center.lon + deltaX * res,
            lat: center.lat - deltaY * res
        };
    },

    /**
     * Method: getOverviewPxFromLonLat
     * Get a pixel location from a map location
     *
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
     *     object with a 'lon' and 'lat' properties.
     *
     * Returns:
     * {Object} Location which is the passed-in OpenLayers.LonLat, 
     * translated into overview map pixels
     */
    getOverviewPxFromLonLat: function(lonlat) {
        var res = this.ovmap.getResolution();
        var extent = this.ovmap.getExtent();
        if (extent) {
            return {
                x: Math.round(1/res * (lonlat.lon - extent.left)),
                y: Math.round(1/res * (extent.top - lonlat.lat))
            };
        } 
    },

    CLASS_NAME: 'OpenLayers.Control.OverviewMap'
});
/* ======================================================================
    OpenLayers/Control/PinchZoom.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Handler/Pinch.js
 */

/**
 * Class: OpenLayers.Control.PinchZoom
 *
 * Inherits:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {

    /** 
     * Property: type
     * {OpenLayers.Control.TYPES}
     */
    type: OpenLayers.Control.TYPE_TOOL,

    /**
     * Property: pinchOrigin
     * {Object} Cached object representing the pinch start (in pixels).
     */
    pinchOrigin: null,    
    
    /**
     * Property: currentCenter
     * {Object} Cached object representing the latest pinch center (in pixels).
     */
    currentCenter: null,    

    /**
     * APIProperty: autoActivate
     * {Boolean} Activate the control when it is added to a map.  Default is
     *     true.
     */
    autoActivate: true,

    /**
     * APIProperty: preserveCenter
     * {Boolean} Set this to true if you don't want the map center to change
     *     while pinching. For example you may want to set preserveCenter to
     *     true when the user location is being watched and you want to preserve
     *     the user location at the center of the map even if he zooms in or
     *     out using pinch. This property's value can be changed any time on an
     *     existing instance. Default is false.
     */
    preserveCenter: false,
    
    /**
     * APIProperty: handlerOptions
     * {Object} Used to set non-default properties on the pinch handler
     */

    /**
     * Constructor: OpenLayers.Control.PinchZoom
     * Create a control for zooming with pinch gestures.  This works on devices
     *     with multi-touch support.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *                    the control
     */
    initialize: function(options) {
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
        this.handler = new OpenLayers.Handler.Pinch(this, {
            start: this.pinchStart,
            move: this.pinchMove,
            done: this.pinchDone
        }, this.handlerOptions);
    },
    
    /**
     * Method: pinchStart
     *
     * Parameters:
     * evt - {Event}
     * pinchData - {Object} pinch data object related to the current touchmove
     *     of the pinch gesture. This give us the current scale of the pinch.
     */
    pinchStart: function(evt, pinchData) {
        var xy = (this.preserveCenter) ?
            this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
        this.pinchOrigin = xy;
        this.currentCenter = xy;
    },
    
    /**
     * Method: pinchMove
     *
     * Parameters:
     * evt - {Event}
     * pinchData - {Object} pinch data object related to the current touchmove
     *     of the pinch gesture. This give us the current scale of the pinch.
     */
    pinchMove: function(evt, pinchData) {
        var scale = pinchData.scale;
        var containerOrigin = this.map.layerContainerOriginPx;
        var pinchOrigin = this.pinchOrigin;
        var current = (this.preserveCenter) ?
            this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;

        var dx = Math.round((containerOrigin.x + current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
        var dy = Math.round((containerOrigin.y + current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));

        this.map.applyTransform(dx, dy, scale);
        this.currentCenter = current;
    },

    /**
     * Method: pinchDone
     *
     * Parameters:
     * evt - {Event}
     * start - {Object} pinch data object related to the touchstart event that
     *     started the pinch gesture.
     * last - {Object} pinch data object related to the last touchmove event
     *     of the pinch gesture. This give us the final scale of the pinch.
     */
    pinchDone: function(evt, start, last) {
        this.map.applyTransform();
        var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
        if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
            var resolution = this.map.getResolutionForZoom(zoom);

            var location = this.map.getLonLatFromPixel(this.pinchOrigin);
            var zoomPixel = this.currentCenter;        
            var size = this.map.getSize();

            location.lon += resolution * ((size.w / 2) - zoomPixel.x);
            location.lat -= resolution * ((size.h / 2) - zoomPixel.y);

            // Force a reflow before calling setCenter. This is to work
            // around an issue occuring in iOS.
            //
            // See https://github.com/openlayers/openlayers/pull/351.
            //
            // Without a reflow setting the layer container div's top left
            // style properties to "0px" - as done in Map.moveTo when zoom
            // is changed - won't actually correctly reposition the layer
            // container div.
            //
            // Also, we need to use a statement that the Google Closure
            // compiler won't optimize away.
            this.map.div.clientWidth = this.map.div.clientWidth;

            this.map.setCenter(location, zoom);
        }
    },

    CLASS_NAME: "OpenLayers.Control.PinchZoom"

});
/* ======================================================================
    OpenLayers/Control/TouchNavigation.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control/DragPan.js
 * @requires OpenLayers/Control/PinchZoom.js
 * @requires OpenLayers/Handler/Click.js
 */

/**
 * Class: OpenLayers.Control.TouchNavigation
 * The navigation control handles map browsing with touch events (dragging,
 *     double-tapping, tap with two fingers, and pinch zoom).  Create a new 
 *     control with the <OpenLayers.Control.TouchNavigation> constructor.
 *
 * If you’re only targeting touch enabled devices with your mapping application,
 *     you can create a map with only a TouchNavigation control. The 
 *     <OpenLayers.Control.Navigation> control is mobile ready by default, but 
 *     you can generate a smaller build of the library by only including this
 *     touch navigation control if you aren't concerned about mouse interaction.
 *
 * Inherits:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {

    /**
     * Property: dragPan
     * {<OpenLayers.Control.DragPan>}
     */
    dragPan: null,

    /**
     * APIProperty: dragPanOptions
     * {Object} Options passed to the DragPan control.
     */
    dragPanOptions: null,

    /**
     * Property: pinchZoom
     * {<OpenLayers.Control.PinchZoom>}
     */
    pinchZoom: null,

    /**
     * APIProperty: pinchZoomOptions
     * {Object} Options passed to the PinchZoom control.
     */
    pinchZoomOptions: null,

    /**
     * APIProperty: clickHandlerOptions
     * {Object} Options passed to the Click handler.
     */
    clickHandlerOptions: null,

    /**
     * APIProperty: documentDrag
     * {Boolean} Allow panning of the map by dragging outside map viewport.
     *     Default is false.
     */
    documentDrag: false,

    /**
     * APIProperty: autoActivate
     * {Boolean} Activate the control when it is added to a map.  Default is
     *     true.
     */
    autoActivate: true,

    /**
     * Constructor: OpenLayers.Control.TouchNavigation
     * Create a new navigation control
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *                    the control
     */
    initialize: function(options) {
        this.handlers = {};
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
    },

    /**
     * Method: destroy
     * The destroy method is used to perform any clean up before the control
     * is dereferenced.  Typically this is where event listeners are removed
     * to prevent memory leaks.
     */
    destroy: function() {
        this.deactivate();
        if(this.dragPan) {
            this.dragPan.destroy();
        }
        this.dragPan = null;
        if (this.pinchZoom) {
            this.pinchZoom.destroy();
            delete this.pinchZoom;
        }
        OpenLayers.Control.prototype.destroy.apply(this,arguments);
    },

    /**
     * Method: activate
     */
    activate: function() {
        if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
            this.dragPan.activate();
            this.handlers.click.activate();
            this.pinchZoom.activate();
            return true;
        }
        return false;
    },

    /**
     * Method: deactivate
     */
    deactivate: function() {
        if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
            this.dragPan.deactivate();
            this.handlers.click.deactivate();
            this.pinchZoom.deactivate();
            return true;
        }
        return false;
    },
    
    /**
     * Method: draw
     */
    draw: function() {
        var clickCallbacks = {
            click: this.defaultClick,
            dblclick: this.defaultDblClick
        };
        var clickOptions = OpenLayers.Util.extend({
            "double": true,
            stopDouble: true,
            pixelTolerance: 2
        }, this.clickHandlerOptions);
        this.handlers.click = new OpenLayers.Handler.Click(
            this, clickCallbacks, clickOptions
        );
        this.dragPan = new OpenLayers.Control.DragPan(
            OpenLayers.Util.extend({
                map: this.map,
                documentDrag: this.documentDrag
            }, this.dragPanOptions)
        );
        this.dragPan.draw();
        this.pinchZoom = new OpenLayers.Control.PinchZoom(
            OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
        );
    },

    /**
     * Method: defaultClick
     *
     * Parameters:
     * evt - {Event}
     */
    defaultClick: function (evt) {
        if(evt.lastTouches && evt.lastTouches.length == 2) {
            this.map.zoomOut();
        }
    },

    /**
     * Method: defaultDblClick
     *
     * Parameters:
     * evt - {Event}
     */
    defaultDblClick: function (evt) {
        this.map.zoomTo(this.map.zoom + 1, evt.xy);
    },

    CLASS_NAME: "OpenLayers.Control.TouchNavigation"
});
/* ======================================================================
    Rico/Color.js
   ====================================================================== */

/** 
 * @requires Rico/license.js
 * @requires OpenLayers/Console.js
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/BaseTypes/Element.js
 */


/*
 * This file has been edited substantially from the Rico-released version by
 * the OpenLayers development team.
 */

OpenLayers.Console.warn("OpenLayers.Rico is deprecated");

OpenLayers.Rico = OpenLayers.Rico || {};
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 = 
      OpenLayers.Element.getStyle(OpenLayers.Util.getElement(elem), 
                                        "backgroundColor");

   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/Format/WFSCapabilities/v1_0_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/WFSCapabilities/v1.js
 */

/**
 * Class: OpenLayers.Format.WFSCapabilities/v1_0_0
 * Read WFS Capabilities version 1.0.0.
 * 
 * Inherits from:
 *  - <OpenLayers.Format.WFSCapabilities.v1>
 */
OpenLayers.Format.WFSCapabilities.v1_0_0 = OpenLayers.Class(
    OpenLayers.Format.WFSCapabilities.v1, {
    
    /**
     * Constructor: OpenLayers.Format.WFSCapabilities.v1_0_0
     * Create a new parser for WFS capabilities version 1.0.0.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */

    /**
     * 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: {
        "wfs": OpenLayers.Util.applyDefaults({
            "Service": function(node, capabilities) {
                capabilities.service = {};
                this.readChildNodes(node, capabilities.service);
            },
            "Fees": function(node, service) {
                var fees = this.getChildValue(node);
                if (fees && fees.toLowerCase() != "none") {
                    service.fees = fees;
                }
            },
            "AccessConstraints": function(node, service) {
                var constraints = this.getChildValue(node);
                if (constraints && constraints.toLowerCase() != "none") {
                    service.accessConstraints = constraints;
                }
            },
            "OnlineResource": function(node, service) {
                var onlineResource = this.getChildValue(node);
                if (onlineResource && onlineResource.toLowerCase() != "none") {
                    service.onlineResource = onlineResource;
                }
            },
            "Keywords": function(node, service) {
                var keywords = this.getChildValue(node);
                if (keywords && keywords.toLowerCase() != "none") {
                    service.keywords = keywords.split(', ');
                }
            },
            "Capability": function(node, capabilities) {
                capabilities.capability = {};
                this.readChildNodes(node, capabilities.capability);
            },
            "Request": function(node, obj) {
                obj.request = {};
                this.readChildNodes(node, obj.request);
            },
            "GetFeature": function(node, request) {
                request.getfeature = {
                    href: {}, // DCPType
                    formats: [] // ResultFormat
                };
                this.readChildNodes(node, request.getfeature);
            },
            "ResultFormat": function(node, obj) {
                var children = node.childNodes;
                var childNode;
                for(var i=0; i<children.length; i++) {
                    childNode = children[i];
                    if(childNode.nodeType == 1) {
                        obj.formats.push(childNode.nodeName);
                    }
                }
            },
            "DCPType": function(node, obj) {
                this.readChildNodes(node, obj);
            },
            "HTTP": function(node, obj) {
                this.readChildNodes(node, obj.href);
            },
            "Get": function(node, obj) {
                obj.get = node.getAttribute("onlineResource");
            },
            "Post": function(node, obj) {
                obj.post = node.getAttribute("onlineResource");
            },
            "SRS": function(node, obj) {
                var srs = this.getChildValue(node);
                if (srs) {
                    obj.srs = srs;
                }
            }
        }, OpenLayers.Format.WFSCapabilities.v1.prototype.readers["wfs"])
    },
    
    CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1_0_0" 

});
/* ======================================================================
    OpenLayers/Layer/MapServer.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Layer/Grid.js
 */

/**
 * Class: OpenLayers.Layer.MapServer
 * Instances of OpenLayers.Layer.MapServer are used to display
 * data from a MapServer CGI instance.
 *
 * Inherits from:
 *  - <OpenLayers.Layer.Grid>
 */
OpenLayers.Layer.MapServer = OpenLayers.Class(OpenLayers.Layer.Grid, {

    /**
     * Constant: DEFAULT_PARAMS
     * {Object} Hashtable of default parameter key/value pairs 
     */
    DEFAULT_PARAMS: {
        mode: "map",
        map_imagetype: "png"
    },

    /**
     * Constructor: OpenLayers.Layer.MapServer
     * Create a new MapServer layer object
     *
     * Parameters:
     * name - {String} A name for the layer
     * url - {String} Base url for the MapServer CGI
     *       (e.g. http://www2.dmsolutions.ca/cgi-bin/mapserv)
     * params - {Object} An object with key/value pairs representing the
     *          GetMap query string parameters and parameter values.
     * options - {Object} Hashtable of extra options to tag onto the layer
     */
    initialize: function(name, url, params, options) {
        OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);

        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
        if (options == null || options.isBaseLayer == null) {
            this.isBaseLayer = ((this.params.transparent != "true") && 
                                (this.params.transparent != true));
        }
    },

    /**
     * Method: clone
     * Create a clone of this layer
     *
     * Returns:
     * {<OpenLayers.Layer.MapServer>} An exact clone of this layer
     */
    clone: function (obj) {
        if (obj == null) {
            obj = new OpenLayers.Layer.MapServer(this.name,
                                           this.url,
                                           this.params,
                                           this.getOptions());
        }
        //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
     * Return a query string for this layer
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox 
     *                                for the request
     *
     * 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);
        // Make a list, so that getFullRequestString uses literal "," 
        var extent = [bounds.left, bounds. bottom, bounds.right, bounds.top];

        var imageSize = this.getImageSize(); 
        
        // make lists, so that literal ','s are used 
        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;
    },
    
    /** 
     * Method: getFullRequestString
     * combine the layer's url with its params and these newParams. 
     *   
     * Parameters:
     * newParams - {Object} New parameters that should be added to the 
     *                      request string.
     * altUrl - {String} (optional) Replace the URL in the full request  
     *                              string with the provided URL.
     * 
     * Returns: 
     * {String} A string with the layer's url and parameters embedded in it.
     */
    getFullRequestString:function(newParams, altUrl) {
        // use layer's url unless altUrl passed in
        var url = (altUrl == null) ? this.url : altUrl;
        
        // create a new params hashtable with all the layer params and the 
        // new params together. then convert to string
        var allParams = OpenLayers.Util.extend({}, this.params);
        allParams = OpenLayers.Util.extend(allParams, newParams);
        var paramsString = OpenLayers.Util.getParameterString(allParams);
        
        // if url is not a string, it should be an array of strings, 
        // in which case we will deterministically select one of them in 
        // order to evenly distribute requests to different urls.
        if (OpenLayers.Util.isArray(url)) {
            url = this.selectUrl(paramsString, url);
        }   
        
        // ignore parameters that are already in the url search string
        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);
        
        // requestString always starts with url
        var requestString = url;        

        // MapServer needs '+' seperating things like bounds/height/width.
        //   Since typically this is URL encoded, we use a slight hack: we
        //  depend on the list-like functionality of getParameterString to
        //  leave ',' only in the case of list items (since otherwise it is
        //  encoded) then do a regular expression replace on the , characters
        //  to '+'
        //
        paramsString = paramsString.replace(/,/g, "+");
        
        if (paramsString != "") {
            var lastServerChar = url.charAt(url.length - 1);
            if ((lastServerChar == "&") || (lastServerChar == "?")) {
                requestString += paramsString;
            } else {
                if (url.indexOf('?') == -1) {
                    //serverPath has no ? -- add one
                    requestString += '?' + paramsString;
                } else {
                    //serverPath contains ?, so must already have paramsString at the end
                    requestString += '&' + paramsString;
                }
            }
        }
        return requestString;
    },

    CLASS_NAME: "OpenLayers.Layer.MapServer"
});
/* ======================================================================
    OpenLayers/Renderer/VML.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/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 (num | 0) to truncate 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: {},

    /**
     * Property: offset
     * {Object} Hash with "x" and "y" properties
     */
    offset: null,
    
    /**
     * 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();
            var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox']; 
            for (var i = 0, len = shapes.length; i < len; i++) {

                style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " +
                              "position: absolute; display: inline-block;");
            }                  
        }
        
        OpenLayers.Renderer.Elements.prototype.initialize.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) {
        var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
        var resolution = this.getResolution();
    
        var left = (extent.left/resolution) | 0;
        var top = (extent.top/resolution - this.size.h) | 0;
        if (resolutionChanged || !this.offset) {
            this.offset = {x: left, y: top};
            left = 0;
            top = 0;
        } else {
            left = left - this.offset.x;
            top = top - this.offset.y;
        }

        
        var org = (left - this.xOffset) + " " + top;
        this.root.coordorigin = org;
        var roots = [this.root, this.vectorRoot, this.textRoot];
        var root;
        for(var i=0, len=roots.length; i<len; ++i) {
            root = roots[i];

            var size = this.size.w + " " + this.size.h;
            root.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 coordSysUnchanged;
    },


    /**
     * 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);
        
        // setting width and height on all roots to avoid flicker which we
        // would get with 100% width and height on child roots
        var roots = [
            this.rendererRoot,
            this.root,
            this.vectorRoot,
            this.textRoot
        ];
        var w = this.size.w + "px";
        var h = this.size.h + "px";
        var root;
        for(var i=0, len=roots.length; i<len; ++i) {
            root = roots[i];
            root.style.width = w;
            root.style.height = h;
        }
    },

    /**
     * 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":
                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 fillColor = style.fillColor;

        var title = style.title || style.graphicTitle;
        if (title) {
            node.title = title;
        } 

        if (node._geometryClass === "OpenLayers.Geometry.Point") {
            if (style.externalGraphic) {
                options.isFilled = true;
                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 - this.featureDx)/resolution - this.offset.x)+xOffset) | 0) + "px";
                node.style.top = (((geometry.y/resolution - this.offset.y)-(yOffset+height)) | 0) + "px";
                node.style.width = width + "px";
                node.style.height = height + "px";
                node.style.flip = "y";
                
                // modify fillColor and options for stroke styling below
                fillColor = "none";
                options.isStroked = false;
            } else if (this.isComplexSymbol(style.graphicName)) {
                var cache = this.importSymbol(style.graphicName);
                node.path = cache.path;
                node.coordorigin = cache.left + "," + cache.bottom;
                var size = cache.size;
                node.coordsize = size + "," + size;        
                this.drawCircle(node, geometry, style.pointRadius);
                node.style.flip = "y";
            } else {
                this.drawCircle(node, geometry, style.pointRadius);
            }
        }

        // fill 
        if (options.isFilled) { 
            node.fillcolor = fillColor; 
        } else { 
            node.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.opacity = style.fillOpacity;

            if (node._geometryClass === "OpenLayers.Geometry.Point" &&
                    style.externalGraphic) {

                // override fillOpacity
                if (style.graphicOpacity) {
                    fill.opacity = style.graphicOpacity;
                }
                
                fill.src = style.externalGraphic;
                fill.type = "frame";
                
                if (!(style.graphicWidth && style.graphicHeight)) {
                  fill.aspect = "atmost";
                }                
            }
            if (fill.parentNode != node) {
                node.appendChild(fill);
            }
        }

        // additional rendering for rotated graphics or symbols
        var rotation = style.rotation;
        if ((rotation !== undefined || node._rotation !== undefined)) {
            node._rotation = rotation;
            if (style.externalGraphic) {
                this.graphicRotate(node, xOffset, yOffset, style);
                // 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.opacity = 0;
            } else if(node._geometryClass === "OpenLayers.Geometry.Point") {
                node.style.rotation = rotation || 0;
            }
        }

        // stroke 
        var strokes = node.getElementsByTagName("stroke");
        var stroke = (strokes.length == 0) ? null : strokes[0];
        if (!options.isStroked) {
            node.stroked = false;
            if (stroke) {
                stroke.on = false;
            }
        } else {
            if (!stroke) {
                stroke = this.createNode('olv:stroke', node.id + "_stroke");
                node.appendChild(stroke);
            }
            stroke.on = true;
            stroke.color = style.strokeColor; 
            stroke.weight = style.strokeWidth + "px"; 
            stroke.opacity = style.strokeOpacity;
            stroke.endcap = style.strokeLinecap == 'butt' ? 'flat' :
                (style.strokeLinecap || 'round');
            if (style.strokeDashstyle) {
                stroke.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
     * style   - {Object}
     */
    graphicRotate: function(node, xOffset, yOffset, style) {
        var style = style || node._style;
        var rotation = style.rotation || 0;
        
        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, style);
                }
            }, 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 rot = rotation * Math.PI / 180;
        var sintheta = Math.sin(rot);
        var costheta = Math.cos(rot);

        // 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
     * Does some node postprocessing to work around browser issues:
     * - 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.
     * - In some cases, a node won't become visible after being drawn. Setting
     *   style.visibility to "visible" works around that.
     * 
     * Parameters:
     * node - {DOMElement}
     */
    postDraw: function(node) {
        node.style.visibility = "visible";
        var fillColor = node._style.fillColor;
        var strokeColor = node._style.strokeColor;
        if (fillColor == "none" &&
                node.fillcolor != fillColor) {
            node.fillcolor = fillColor;
        }
        if (strokeColor == "none" &&
                node.strokecolor != strokeColor) {
            node.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 - this.featureDx)/resolution - this.offset.x) | 0,
                                      (bbox.bottom/resolution - this.offset.y) | 0,
                                      ((bbox.right - this.featureDx)/resolution - this.offset.x) | 0,
                                      (bbox.top/resolution - this.offset.y) | 0);
            
            // 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.id = id;
        }
        
        // IE hack to make elements unselectable, to prevent 'blue flash'
        // while dragging vectors; #1410
        node.unselectable = 'on';
        node.onselectstart = OpenLayers.Function.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
     * 
     * Parameters:
     * suffix - {String} suffix to append to the id
     *
     * Returns:
     * {DOMElement}
     */
    createRoot: function(suffix) {
        return this.nodeFactory(this.container.id + suffix, "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 - this.featureDx) /resolution - this.offset.x) | 0) - radius) + "px";
            node.style.top = (((geometry.y /resolution - this.offset.y) | 0) - 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 - this.featureDx)/resolution - this.offset.x) | 0;
            y = (comp.y/resolution - this.offset.y) | 0;
            parts[i] = " " + x + "," + y + " 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 j, jj, points, area, first, second, i, ii, comp, pathComp, x, y;
        for (j=0, jj=geometry.components.length; j<jj; j++) {
            path.push("m");
            points = geometry.components[j].components;
            // we only close paths of interior rings with area
            area = (j === 0);
            first = null;
            second = null;
            for (i=0, ii=points.length; i<ii; i++) {
                comp = points[i];
                x = ((comp.x - this.featureDx) / resolution - this.offset.x) | 0;
                y = (comp.y / resolution - this.offset.y) | 0;
                pathComp = " " + x + "," + y;
                path.push(pathComp);
                if (i==0) {
                    path.push(" l");
                }
                if (!area) {
                    // IE improperly renders sub-paths that have no area.
                    // Instead of checking the area of every ring, we confirm
                    // the ring has at least three distinct points.  This does
                    // not catch all non-zero area cases, but it greatly improves
                    // interior ring digitizing and is a minor performance hit
                    // when rendering rings with many points.
                    if (!first) {
                        first = pathComp;
                    } else if (first != pathComp) {
                        if (!second) {
                            second = pathComp;
                        } else if (second != pathComp) {
                            // stop looking
                            area = true;
                        }
                    }
                }
            }
            path.push(area ? " 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 - this.featureDx)/resolution - this.offset.x) | 0) + "px";
        node.style.top = ((geometry.y/resolution - this.offset.y) | 0) + "px";
        node.style.width = ((geometry.width/resolution) | 0) + "px";
        node.style.height = ((geometry.height/resolution) | 0) + "px";
        
        return node;
    },
    
    /**
     * Method: drawText
     * This method is only called by the renderer itself.
     * 
     * Parameters: 
     * featureId - {String}
     * style -
     * location - {<OpenLayers.Geometry.Point>}
     */
    drawText: function(featureId, style, location) {
        var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect");
        var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox");
        
        var resolution = this.getResolution();
        label.style.left = (((location.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
        label.style.top = ((location.y/resolution - this.offset.y) | 0) + "px";
        label.style.flip = "y";

        textbox.innerText = style.label;

        if (style.cursor != "inherit" && style.cursor != null) {
            textbox.style.cursor = style.cursor;
        }
        if (style.fontColor) {
            textbox.style.color = style.fontColor;
        }
        if (style.fontOpacity) {
            textbox.style.filter = 'alpha(opacity=' + (style.fontOpacity * 100) + ')';
        }
        if (style.fontFamily) {
            textbox.style.fontFamily = style.fontFamily;
        }
        if (style.fontSize) {
            textbox.style.fontSize = style.fontSize;
        }
        if (style.fontWeight) {
            textbox.style.fontWeight = style.fontWeight;
        }
        if (style.fontStyle) {
            textbox.style.fontStyle = style.fontStyle;
        }
        if(style.labelSelect === true) {
            label._featureId = featureId;
            textbox._featureId = featureId;
            textbox._geometry = location;
            textbox._geometryClass = location.CLASS_NAME;
        }
        textbox.style.whiteSpace = "nowrap";
        // fun with IE: IE7 in standards compliant mode does not display any
        // text with a left inset of 0. So we set this to 1px and subtract one
        // pixel later when we set label.style.left
        textbox.inset = "1px,0px,0px,0px";

        if(!label.parentNode) {
            label.appendChild(textbox);
            this.textRoot.appendChild(label);
        }

        var align = style.labelAlign || "cm";
        if (align.length == 1) {
            align += "m";
        }
        var xshift = textbox.clientWidth *
            (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);
        var yshift = textbox.clientHeight *
            (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);
        label.style.left = parseInt(label.style.left)-xshift-1+"px";
        label.style.top = parseInt(label.style.top)+yshift+"px";
        
    },
    
    /**
     * Method: moveRoot
     * moves this renderer's root to a different renderer.
     * 
     * Parameters:
     * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
     * root - {DOMElement} optional root node. To be used when this renderer
     *     holds roots from multiple layers to tell this method which one to
     *     detach
     * 
     * Returns:
     * {Boolean} true if successful, false otherwise
     */
    moveRoot: function(renderer) {
        var layer = this.map.getLayer(renderer.container.id);
        if(layer instanceof OpenLayers.Layer.Vector.RootContainer) {
            layer = this.map.getLayer(this.container.id);
        }
        layer && layer.renderer.clear();
        OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments);
        layer && layer.redraw();
    },
    
    /**
     * 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');
        }

        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) {
            var x = symbol[i];
            var 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(" ");

        var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
        if(diff > 0) {
            symbolExtent.bottom = symbolExtent.bottom - diff;
            symbolExtent.top = symbolExtent.top + diff;
        } else {
            symbolExtent.left = symbolExtent.left + diff;
            symbolExtent.right = symbolExtent.right - diff;
        }
        
        cache = {
            path: path,
            size: symbolExtent.getWidth(), // equals getHeight() now
            left: symbolExtent.left,
            bottom: symbolExtent.bottom
        };
        this.symbolCache[id] = cache;
        
        return cache;
    },
    
    CLASS_NAME: "OpenLayers.Renderer.VML"
});

/**
 * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT
 * {Object}
 */
OpenLayers.Renderer.VML.LABEL_SHIFT = {
    "l": 0,
    "c": .5,
    "r": 1,
    "t": 0,
    "m": .5,
    "b": 1
};
/* ======================================================================
    OpenLayers/Protocol/WFS/v1_0_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Protocol/WFS/v1.js
 * @requires OpenLayers/Format/WFST/v1_0_0.js
 */

/**
 * Class: OpenLayers.Protocol.WFS.v1_0_0
 * A WFS v1.0.0 protocol for vector layers.  Create a new instance with the
 *     <OpenLayers.Protocol.WFS.v1_0_0> constructor.
 *
 * Inherits from:
 *  - <OpenLayers.Protocol.WFS.v1>
 */
OpenLayers.Protocol.WFS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
    
    /**
     * Property: version
     * {String} WFS version number.
     */
    version: "1.0.0",
    
    /**
     * Constructor: OpenLayers.Protocol.WFS.v1_0_0
     * A class for giving layers WFS v1.0.0 protocol.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     *
     * Valid options properties:
     * featureType - {String} Local (without prefix) feature typeName (required).
     * featureNS - {String} Feature namespace (optional).
     * featurePrefix - {String} Feature namespace alias (optional - only used
     *     if featureNS is provided).  Default is 'feature'.
     * geometryName - {String} Name of geometry attribute.  Default is 'the_geom'.
     */
   
    CLASS_NAME: "OpenLayers.Protocol.WFS.v1_0_0" 
});
/* ======================================================================
    OpenLayers/Handler/RegularPolygon.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/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.Drag>
 */
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',
    
    /**
     * Property: layerOptions
     * {Object} Any optional properties to be set on the sketch layer.
     */
    layerOptions: null,

    /**
     * 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,

    /**
     * APIProperty: citeCompliant
     * {Boolean} If set to true, coordinates of features drawn in a map extent
     * crossing the date line won't exceed the world bounds. Default is false.
     */
    citeCompliant: 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 - {Object} An object with a properties whose values are
     *     functions.  Various callbacks described below.
     * 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.
     *
     * Named callbacks:
     * create - Called when a sketch is first created.  Callback called with
     *     the creation point geometry and sketch feature.
     * done - Called when the sketch drawing is finished.  The callback will
     *     recieve a single argument, the sketch geometry.
     * cancel - Called when the handler is deactivated while drawing.  The
     *     cancel callback will receive a geometry.
     */
    initialize: function(control, callbacks, options) {
        if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
            this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
        }

        OpenLayers.Handler.Drag.prototype.initialize.apply(this,
                                                [control, callbacks, options]);
        this.options = (options) ? options : {};
    },
    
    /**
     * 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.
     *
     * Returns:
     * {Boolean} The handler was successfully activated
     */
    activate: function() {
        var activated = false;
        if(OpenLayers.Handler.Drag.prototype.activate.apply(this, arguments)) {
            // create temporary vector layer for rendering geometry sketch
            var options = OpenLayers.Util.extend({
                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: OpenLayers.Function.True,
                wrapDateLine: this.citeCompliant
            }, this.layerOptions);
            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.
     *
     * Returns:
     * {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: down
     * Start drawing a new feature
     *
     * Parameters:
     * evt - {Event} The drag start event
     */
    down: function(evt) {
        this.fixedRadius = !!(this.radius);
        var maploc = this.layer.getLonLatFromViewPortPx(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.callback("create", [this.origin, this.feature]);
        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.layer.getLonLatFromViewPortPx(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();
        // the mouseup method of superclass doesn't call the
        // "done" callback if there's been no move between
        // down and up
        if (this.start == this.last) {
            this.callback("done", [evt.xy]);
        }
    },

    /**
     * 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, 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() {
        if (this.layer) {
            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"
});
/* ======================================================================
    Rico/Corner.js
   ====================================================================== */

/**
 * @requires OpenLayers/Console.js
 * @requires Rico/Color.js
 */


/*
 * This file has been edited substantially from the Rico-released
 * version by the OpenLayers development team.
 *  
 *  Copyright 2005 Sabre Airline Solutions  
 *  
 *  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.
 *
 */

OpenLayers.Console.warn("OpenLayers.Rico is deprecated");

OpenLayers.Rico = OpenLayers.Rico || {};
OpenLayers.Rico.Corner = {

    round: function(e, options) {
        e = OpenLayers.Util.getElement(e);
        this._setOptions(options);

        var color = this.options.color;
        if ( this.options.color == "fromElement" ) {
            color = this._background(e);
        }
        var bgColor = this.options.bgColor;
        if ( this.options.bgColor == "fromParent" ) {
            bgColor = this._background(e.offsetParent);
        }
        this._roundCornersImpl(e, color, bgColor);
    },

    /**   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;

        var spanElements = theDiv.parentNode.getElementsByTagName("span");
        
        for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
            spanElements[currIdx].style.backgroundColor = newColor;
        }
    }, 


    /**   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 + ')';
        
        theDiv.style.opacity = mozillaOpacity;
        theDiv.style.filter = ieOpacity;

        var spanElements = theDiv.parentNode.getElementsByTagName("span");
        
        for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
            spanElements[currIdx].style.opacity = mozillaOpacity;
            spanElements[currIdx].style.filter = ieOpacity;
        }

    },

    /** 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) {

        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); 

        this.round(theDiv.parentNode, options);
    }, 

   _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);
      }
   },

   _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>";
   },

   _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);
   },

   _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);
   },

   _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");

      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";
      }

      if ( !this.options.compact && (n == (this.options.numSlices-1)) ) {
         inStyle.height = "2px";
      }
      this._setMargin(slice, n, position);
      this._setBorder(slice, n, position);
      return slice;
   },

   _setOptions: function(options) {
      this.options = {
         corners : "all",
         color   : "fromElement",
         bgColor : "fromParent",
         blend   : true,
         border  : false,
         compact : false
      };
      OpenLayers.Util.extend(this.options, options || {});

      this.options.numSlices = this.options.compact ? 2 : 4;
      if ( this._isTransparent() ) {
         this.options.blend = false;
      }
   },

   _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 "";
   },

   _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 "";
      }

      if(this.options.corners.indexOf("bl") >=0) {
         return "left";
      } else if(this.options.corners.indexOf("br")>=0) {
         return "right";
      }
      return "";
   },

   _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 "";
      }
   },


   _setMargin: function(el, n, corners) {
      var marginSize = this._marginSize(n);
      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();

      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";
      }
   },

   _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";
      }
   },

   _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 ];

      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];
      }
   },

   _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 ];

      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;
   },

   _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; }
};
/* ======================================================================
    OpenLayers/Handler/Feature.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/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.
 *
 * Inherits from:
 *  - <OpenLayers.Handler>
 */
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},
        'touchstart': {'in': 'click', 'out': 'clickout'}
    },

    /**
     * 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: clickTolerance
     * {Number} The number of pixels the mouse can move between mousedown
     *     and mouseup for the event to still be considered a click.
     *     Dragging the map should not trigger the click and clickout callbacks
     *     unless the map is moved by less than this tolerance. Defaults to 4.
     */
    clickTolerance: 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>} 
     * layer - {<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: touchstart
     * Handle touchstart events
     *
     * Parameters:
     * evt - {Event}
     *
     * Returns:
     * {Boolean} Let the event propagate.
     */
    touchstart: function(evt) {
        this.startTouch(); 
        return OpenLayers.Event.isMultiTouch(evt) ?
                true : this.mousedown(evt);
    },

    /**
     * Method: touchmove
     * Handle touchmove events. We just prevent the browser default behavior,
     *    for Android Webkit not to select text when moving the finger after
     *    selecting a feature.
     *
     * Parameters:
     * evt - {Event}
     */
    touchmove: function(evt) {
        OpenLayers.Event.preventDefault(evt);
    },

    /**
     * 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) {
        // Feature selection is only done with a left click. Other handlers may stop the
        // propagation of left-click mousedown events but not right-click mousedown events.
        // This mismatch causes problems when comparing the location of the down and up
        // events in the click function so it is important ignore right-clicks.
        if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(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) {
        if(this.feature && !this.feature.layer) {
            // feature has been destroyed
            this.feature = null;
        }
        var type = evt.type;
        var handled = false;
        var previouslyIn = !!(this.feature); // previously in a feature
        var click = (type == "click" || type == "dblclick" || type == "touchstart");
        this.feature = this.layer.getFeatureFromEvent(evt);
        if(this.feature && !this.feature.layer) {
            // feature has been destroyed
            this.feature = null;
        }
        if(this.lastFeature && !this.lastFeature.layer) {
            // last feature has been destroyed
            this.lastFeature = null;
        }
        if(this.feature) {
            if(type === "touchstart") {
                // stop the event to prevent Android Webkit from
                // "flashing" the map div
                OpenLayers.Event.preventDefault(evt);
            }
            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
                    if(this.lastFeature) {
                        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(this.lastFeature && (previouslyIn && inNew || click)) {
                    // 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(this.lastFeature && (previouslyIn || click)) {
            this.triggerCallback(type, 'out', [this.lastFeature]);
        }
        return handled;
    },
    
    /**
     * Method: triggerCallback
     * Call the callback keyed in the event map with the supplied arguments.
     *     For click and clickout, the <clickTolerance> is checked first.
     *
     * Parameters:
     * type - {String}
     */
    triggerCallback: function(type, mode, args) {
        var key = this.EVENTMAP[type][mode];
        if(key) {
            if(type == 'click' && this.up && this.down) {
                // for click/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.clickTolerance) {
                    this.callback(key, args);
                }
                // we're done with this set of events now: clear the cached
                // positions so we can't trip over them later (this can occur
                // if one of the up/down events gets eaten before it gets to us
                // but we still get the click)
                this.up = this.down = null;
            } 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.type == "removelayer" || 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/Control/DragFeature.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Handler/Drag.js
 * @requires OpenLayers/Handler/Feature.js
 */

/**
 * Class: OpenLayers.Control.DragFeature
 * The DragFeature control moves a feature with a drag of the mouse. 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) {},

    /**
     * APIProperty: onEnter
     * {Function} Define this function if you want to know when the mouse
     *     goes over a feature and thereby makes this feature a candidate
     *     for dragging.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The feature that is ready
     *     to be dragged.
     */
    onEnter: function(feature) {},

    /**
     * APIProperty: onLeave
     * {Function} Define this function if you want to know when the mouse
     *     goes out of the feature that was dragged.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
     */
    onLeave: function(feature) {},

    /**
     * APIProperty: documentDrag
     * {Boolean} If set to true, mouse dragging will continue even if the
     *     mouse cursor leaves the map viewport. Default is false.
     */
    documentDrag: false,
    
    /**
     * 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), {
                    documentDrag: this.documentDrag
                }
            ),
            feature: new OpenLayers.Handler.Feature(
                this, this.layer, OpenLayers.Util.extend({
                    // 'click' and 'clickout' callback are for the mobile
                    // support: no 'over' or 'out' in touch based browsers.
                    click: this.clickFeature,
                    clickout: this.clickoutFeature,
                    over: this.overFeature,
                    out: this.outFeature
                }, this.featureCallbacks),
                {geometryTypes: this.geometryTypes}
            )
        };
    },

    /**
     * Method: clickFeature
     * Called when the feature handler detects a click-in on a feature.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>}
     */
    clickFeature: function(feature) {
        if (this.handlers.feature.touch && !this.over && this.overFeature(feature)) {
            this.handlers.drag.dragstart(this.handlers.feature.evt);
            // to let the events propagate to the feature handler (click callback)
            this.handlers.drag.stopDown = false;
        }
    },

    /**
     * Method: clickoutFeature
     * Called when the feature handler detects a click-out on a feature.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>}
     */
    clickoutFeature: function(feature) {
        if (this.handlers.feature.touch && this.over) {
            this.outFeature(feature);
            this.handlers.drag.stopDown = true;
        }
    },

    /**
     * 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;
        OpenLayers.Element.removeClass(
            this.map.viewPortDiv, this.displayClass + "Over"
        );
        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.
     *
     * Returns:
     * {Boolean} Successfully activated the drag handler.
     */
    overFeature: function(feature) {
        var activated = false;
        if(!this.handlers.drag.dragging) {
            this.feature = feature;
            this.handlers.drag.activate();
            activated = true;
            this.over = true;
            OpenLayers.Element.addClass(this.map.viewPortDiv, this.displayClass + "Over");
            this.onEnter(feature);
        } else {
            if(this.feature.id == feature.id) {
                this.over = true;
            } else {
                this.over = false;
            }
        }
        return activated;
    },

    /**
     * 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.
     * 
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
     */
    upFeature: function(pixel) {
        if(!this.over) {
            this.handlers.drag.deactivate();
        }
    },

    /**
     * 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();
            OpenLayers.Element.removeClass(
                this.map.viewPortDiv, this.displayClass + "Over"
            );
            this.onLeave(feature);
            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/Handler/Polygon.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/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, {
    
    /** 
     * APIProperty: holeModifier
     * {String} Key modifier to trigger hole digitizing.  Acceptable values are
     *     "altKey", "shiftKey", or "ctrlKey".  If not set, no hole digitizing
     *     will take place.  Default is null.
     */
    holeModifier: null,
    
    /**
     * Property: drawingHole
     * {Boolean} Currently drawing an interior ring.
     */
    drawingHole: false,
    
    /**
     * Property: polygon
     * {<OpenLayers.Feature.Vector>}
     */
    polygon: null,

    /**
     * Constructor: OpenLayers.Handler.Polygon
     * Create a Polygon Handler.
     *
     * Parameters:
     * control - {<OpenLayers.Control>} The control that owns this handler
     * callbacks - {Object} An object with a properties whose values are
     *     functions.  Various callbacks described below.
     * options - {Object} An optional object with properties to be set on the
     *           handler
     *
     * Named callbacks:
     * create - Called when a sketch is first created.  Callback called with
     *     the creation point geometry and sketch feature.
     * modify - Called with each move of a vertex with the vertex (point)
     *     geometry and the sketch feature.
     * point - Called as each point is added.  Receives the new point geometry.
     * done - Called when the point drawing is finished.  The callback will
     *     recieve a single argument, the polygon geometry.
     * cancel - Called when the handler is deactivated while drawing.  The
     *     cancel callback will receive a geometry.
     */
    
    /**
     * Method: createFeature
     * Add temporary geometries
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
     *     feature.
     */
    createFeature: function(pixel) {
        var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
        var geometry = new OpenLayers.Geometry.Point(
            lonlat.lon, lonlat.lat
        );
        this.point = new OpenLayers.Feature.Vector(geometry);
        this.line = new OpenLayers.Feature.Vector(
            new OpenLayers.Geometry.LinearRing([this.point.geometry])
        );
        this.polygon = new OpenLayers.Feature.Vector(
            new OpenLayers.Geometry.Polygon([this.line.geometry])
        );
        this.callback("create", [this.point.geometry, this.getSketch()]);
        this.point.geometry.clearBounds();
        this.layer.addFeatures([this.polygon, this.point], {silent: true});
    },

    /**
     * Method: addPoint
     * Add point to geometry.
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
     */
    addPoint: function(pixel) {
        if(!this.drawingHole && this.holeModifier &&
           this.evt && this.evt[this.holeModifier]) {
            var geometry = this.point.geometry;
            var features = this.control.layer.features;
            var candidate, polygon;
            // look for intersections, last drawn gets priority
            for (var i=features.length-1; i>=0; --i) {
                candidate = features[i].geometry;
                if ((candidate instanceof OpenLayers.Geometry.Polygon || 
                    candidate instanceof OpenLayers.Geometry.MultiPolygon) && 
                    candidate.intersects(geometry)) {
                    polygon = features[i];
                    this.control.layer.removeFeatures([polygon], {silent: true});
                    this.control.layer.events.registerPriority(
                        "sketchcomplete", this, this.finalizeInteriorRing
                    );
                    this.control.layer.events.registerPriority(
                        "sketchmodified", this, this.enforceTopology
                    );
                    polygon.geometry.addComponent(this.line.geometry);
                    this.polygon = polygon;
                    this.drawingHole = true;
                    break;
                }
            }
        }
        OpenLayers.Handler.Path.prototype.addPoint.apply(this, arguments);
    },

    /**
     * Method: getCurrentPointIndex
     * 
     * Returns:
     * {Number} The index of the most recently drawn point.
     */
    getCurrentPointIndex: function() {
        return this.line.geometry.components.length - 2;
    },

    /**
     * Method: enforceTopology
     * Simple topology enforcement for drawing interior rings.  Ensures vertices
     *     of interior rings are contained by exterior ring.  Other topology 
     *     rules are enforced in <finalizeInteriorRing> to allow drawing of 
     *     rings that intersect only during the sketch (e.g. a "C" shaped ring
     *     that nearly encloses another ring).
     */
    enforceTopology: function(event) {
        var point = event.vertex;
        var components = this.line.geometry.components;
        // ensure that vertices of interior ring are contained by exterior ring
        if (!this.polygon.geometry.intersects(point)) {
            var last = components[components.length-3];
            point.x = last.x;
            point.y = last.y;
        }
    },

    /**
     * Method: finishGeometry
     * Finish the geometry and send it back to the control.
     */
    finishGeometry: function() {
        var index = this.line.geometry.components.length - 2;
        this.line.geometry.removeComponent(this.line.geometry.components[index]);
        this.removePoint();
        this.finalize();
    },

    /**
     * Method: finalizeInteriorRing
     * Enforces that new ring has some area and doesn't contain vertices of any
     *     other rings.
     */
    finalizeInteriorRing: function() {
        var ring = this.line.geometry;
        // ensure that ring has some area
        var modified = (ring.getArea() !== 0);
        if (modified) {
            // ensure that new ring doesn't intersect any other rings
            var rings = this.polygon.geometry.components;
            for (var i=rings.length-2; i>=0; --i) {
                if (ring.intersects(rings[i])) {
                    modified = false;
                    break;
                }
            }
            if (modified) {
                // ensure that new ring doesn't contain any other rings
                var target;
                outer: for (var i=rings.length-2; i>0; --i) {
                    var points = rings[i].components;
                    for (var j=0, jj=points.length; j<jj; ++j) {
                        if (ring.containsPoint(points[j])) {
                            modified = false;
                            break outer;
                        }
                    }
                }
            }
        }
        if (modified) {
            if (this.polygon.state !== OpenLayers.State.INSERT) {
                this.polygon.state = OpenLayers.State.UPDATE;
            }
        } else {
            this.polygon.geometry.removeComponent(ring);
        }
        this.restoreFeature();
        return false;
    },

    /**
     * APIMethod: cancel
     * Finish the geometry and call the "cancel" callback.
     */
    cancel: function() {
        if (this.drawingHole) {
            this.polygon.geometry.removeComponent(this.line.geometry);
            this.restoreFeature(true);
        }
        return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments);
    },
    
    /**
     * Method: restoreFeature
     * Move the feature from the sketch layer to the target layer.
     *
     * Properties: 
     * cancel - {Boolean} Cancel drawing.  If falsey, the "sketchcomplete" event
     *     will be fired.
     */
    restoreFeature: function(cancel) {
        this.control.layer.events.unregister(
            "sketchcomplete", this, this.finalizeInteriorRing
        );
        this.control.layer.events.unregister(
            "sketchmodified", this, this.enforceTopology
        );
        this.layer.removeFeatures([this.polygon], {silent: true});
        this.control.layer.addFeatures([this.polygon], {silent: true});
        this.drawingHole = false;
        if (!cancel) {
            // Re-trigger "sketchcomplete" so other listeners can do their
            // business.  While this is somewhat sloppy (if a listener is 
            // registered with registerPriority - not common - between the start
            // and end of a single ring drawing - very uncommon - it will be 
            // called twice).
            // TODO: In 3.0, collapse sketch handlers into geometry specific
            // drawing controls.
            this.control.layer.events.triggerEvent(
                "sketchcomplete", {feature : this.polygon}
            );
        }
    },

    /**
     * Method: destroyFeature
     * Destroy temporary geometries
     *
     * Parameters:
     * force - {Boolean} Destroy even if persist is true.
     */
    destroyFeature: function(force) {
        OpenLayers.Handler.Path.prototype.destroyFeature.call(
            this, force);
        this.polygon = null;
    },

    /**
     * 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: getSketch
     * Return the sketch feature.
     *
     * Returns:
     * {<OpenLayers.Feature.Vector>}
     */
    getSketch: function() {
        return this.polygon;
    },

    /**
     * Method: getGeometry
     * Return the sketch geometry.  If <multi> is true, this will return
     *     a multi-part geometry.
     *
     * Returns:
     * {<OpenLayers.Geometry.Polygon>}
     */
    getGeometry: function() {
        var geometry = this.polygon && this.polygon.geometry;
        if(geometry && this.multi) {
            geometry = new OpenLayers.Geometry.MultiPolygon([geometry]);
        }
        return geometry;
    },

    CLASS_NAME: "OpenLayers.Handler.Polygon"
});
/* ======================================================================
    OpenLayers/Popup/Anchored.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Popup.js
 */

/**
 * Class: OpenLayers.Popup.Anchored
 * 
 * Inherits from:
 *  - <OpenLayers.Popup>
 */
OpenLayers.Popup.Anchored = 
  OpenLayers.Class(OpenLayers.Popup, {

    /** 
     * Property: relativePosition
     * {String} Relative position of the popup ("br", "tr", "tl" or "bl").
     */
    relativePosition: null,
    
    /**
     * APIProperty: keepInMap 
     * {Boolean} If panMapIfOutOfView is false, and this property is true, 
     *     contrain the popup such that it always fits in the available map
     *     space. By default, this is set. If you are creating popups that are
     *     near map edges and not allowing pannning, and especially if you have
     *     a popup which has a fixedRelativePosition, setting this to false may
     *     be a smart thing to do.
     *   
     *     For anchored popups, default is true, since subclasses will
     *     usually want this functionality.
     */
    keepInMap: true,

    /**
     * Property: 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);

        OpenLayers.Popup.prototype.moveTo.call(this, this.calculateNewPx(px));
        
        //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 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"
});
/* ======================================================================
    deprecated.js
   ====================================================================== */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Format.js
 * @requires OpenLayers/Request.js
 * @requires OpenLayers/Layer/WMS.js
 * @requires OpenLayers/Layer/MapServer.js
 * @requires OpenLayers/Tile.js
 * @requires OpenLayers/Request/XMLHttpRequest.js
 * @requires OpenLayers/Layer/Vector.js
 * @requires OpenLayers/Layer/Markers.js
 * @requires OpenLayers/Console.js
 * @requires OpenLayers/Lang.js
 * @requires OpenLayers/Feature.js
 * @requires OpenLayers/Layer/EventPane.js
 * @requires OpenLayers/Layer/FixedZoomLevels.js
 * @requires OpenLayers/Layer/SphericalMercator.js
 * @requires OpenLayers/Protocol.js
 * @requires OpenLayers/Format/JSON.js
 * @requires OpenLayers/Format/WKT.js
 * @requires OpenLayers/Format/XML.js
 * @requires OpenLayers/Geometry.js
 * @requires OpenLayers/Renderer/Elements.js
 * @requires OpenLayers/Popup/Anchored.js
 * @requires Rico/Corner.js
 */

/**
 * About: Deprecated
 * The deprecated.js script includes all methods, properties, and constructors
 * that are not supported as part of the long-term API.  If you use any of
 * these, you have to explicitly include this script in your application.
 *
 * For example:
 * (code)
 *     <script src="deprecated.js" type="text/javascript"></script>
 * (end)
 *
 * You are strongly encouraged to avoid using deprecated functionality.  The
 * documentation here should point you to the supported alternatives.
 */

/**
 * Namespace: OpenLayers.Class
 */

/**
 * Property: isPrototype
 * *Deprecated*.  This is no longer needed and will be removed at 3.0.
 */
OpenLayers.Class.isPrototype = function () {};

/**
 * APIFunction: OpenLayers.create
 * *Deprecated*.  Old method to create an OpenLayers style class.  Use the
 *     <OpenLayers.Class> constructor instead.
 *
 * Returns:
 * An OpenLayers class
 */
OpenLayers.Class.create = function() {
    return function() {
        if (arguments && arguments[0] != OpenLayers.Class.isPrototype) {
            this.initialize.apply(this, arguments);
        }
    };
};

/**
 * APIFunction: inherit
 * *Deprecated*.  Old method to inherit from one or more OpenLayers style
 *     classes.  Use the <OpenLayers.Class> constructor instead.
 *
 * Parameters:
 * class - One or more classes can be provided as arguments
 *
 * Returns:
 * An object prototype
 */
OpenLayers.Class.inherit = function (P) {
    var C = function() {
       P.call(this);
    };
    var newArgs = [C].concat(Array.prototype.slice.call(arguments));
    OpenLayers.inherit.apply(null, newArgs);
    return C.prototype;
};

/**
 * Namespace: OpenLayers.Util
 */

/**
 * Function: clearArray
 * *Deprecated*. This function will disappear in 3.0.
 * Please use "array.length = 0" instead.
 * 
 * Parameters:
 * array - {Array}
 */
OpenLayers.Util.clearArray = function(array) {
    OpenLayers.Console.warn(
        OpenLayers.i18n(
            "methodDeprecated", {'newMethod': 'array = []'}
        )
    );
    array.length = 0;
};

/**
 * Function: setOpacity
 * *Deprecated*.  This function has been deprecated. Instead, please use 
 *     <OpenLayers.Util.modifyDOMElement> 
 *     or 
 *     <OpenLayers.Util.modifyAlphaImageDiv>
 * 
 * Set the opacity of a DOM Element
 *     Note that for this function to work in IE, elements must "have layout"
 *     according to:
 *     http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/haslayout.asp
 *
 * Parameters:
 * element - {DOMElement} Set the opacity on this DOM element
 * opacity - {Float} Opacity value (0.0 - 1.0)
 */
OpenLayers.Util.setOpacity = function(element, opacity) {
    OpenLayers.Util.modifyDOMElement(element, null, null, null,
                                     null, null, null, opacity);
};

/**
 * Function: safeStopPropagation
 * *Deprecated*. This function has been deprecated. Please use directly 
 *     <OpenLayers.Event.stop> passing 'true' as the 2nd 
 *     argument (preventDefault)
 * 
 * Safely stop the propagation of an event *without* preventing
 *   the default browser action from occurring.
 * 
 * Parameters:
 * evt - {Event}
 */
OpenLayers.Util.safeStopPropagation = function(evt) {
    OpenLayers.Event.stop(evt, true);
};

/**
 * Function: getArgs
 * *Deprecated*.  Will be removed in 3.0.  Please use instead
 *     <OpenLayers.Util.getParameters>
 * 
 * Parameters:
 * url - {String} Optional url used to extract the query string.
 *                If null, query string is taken from page location.
 * 
 * Returns:
 * {Object} An object of key/value pairs from the query string.
 */
OpenLayers.Util.getArgs = function(url) {
    OpenLayers.Console.warn(
        OpenLayers.i18n(
            "methodDeprecated", {'newMethod': 'OpenLayers.Util.getParameters'}
        )
    );
    return OpenLayers.Util.getParameters(url);
};

/** 
 * Maintain existing definition of $.
 * 
 * The use of our $-method is deprecated and the mapping of 
 * OpenLayers.Util.getElement will eventually be removed. Do not depend on 
 * window.$ being defined by OpenLayers.
 */
if(typeof window.$  === "undefined") {
    window.$ = OpenLayers.Util.getElement;
}

/**
 * Namespace: OpenLayers.Ajax
 */

/**
 * Function: OpenLayers.nullHandler
 * @param {} request
 */
OpenLayers.nullHandler = function(request) {
    OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest", {'statusText':request.statusText}));
};

/** 
 * APIFunction: OpenLayers.loadURL
 * Background load a document.
 * *Deprecated*.  Use <OpenLayers.Request.GET> method instead.
 *
 * 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: OpenLayers.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;
};

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;
};

/**
 * Namespace: OpenLayers.Element
 */
OpenLayers.Util.extend(OpenLayers.Element, {

    /**
     * APIFunction: hide
     * *Deprecated*. Hide element(s) passed in
     * 
     * Parameters:
     * element - {DOMElement} Actually user can pass any number of elements
     */
    hide: function() {
        OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", {
            newMethod: "element.style.display = 'none';"
        }));

        for (var i=0, len=arguments.length; i<len; i++) {
            var element = OpenLayers.Util.getElement(arguments[i]);
            if (element) {
                element.style.display = 'none';
            }
        }
    },

    /**
     * APIFunction: show
     * *Deprecated*. Show element(s) passed in
     * 
     * Parameters:
     * element - {DOMElement} Actually user can pass any number of elements
     */
    show: function() {
        OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", {
            newMethod: "element.style.display = '';"
        }));

        for (var i=0, len=arguments.length; i<len; i++) {
            var element = OpenLayers.Util.getElement(arguments[i]);
            if (element) {
                element.style.display = '';
            }
        }
    },

    /**
     * APIFunction: getDimensions
     * *Deprecated*. Returns dimensions of the element passed in.
     *  
     * Parameters:
     * element - {DOMElement}
     * 
     * Returns:
     * {Object} Object with 'width' and 'height' properties which are the 
     *          dimensions of the element passed in.
     */
    getDimensions: function(element) {
        element = OpenLayers.Util.getElement(element);
        if (OpenLayers.Element.getStyle(element, 'display') != 'none') {
            return {width: element.offsetWidth, height: element.offsetHeight};
        }
    
        // All *Width and *Height properties give 0 on elements with display none,
        // so enable the element temporarily
        var els = element.style;
        var originalVisibility = els.visibility;
        var originalPosition = els.position;
        var originalDisplay = els.display;
        els.visibility = 'hidden';
        els.position = 'absolute';
        els.display = '';
        var originalWidth = element.clientWidth;
        var originalHeight = element.clientHeight;
        els.display = originalDisplay;
        els.position = originalPosition;
        els.visibility = originalVisibility;
        return {width: originalWidth, height: originalHeight};
    }
    
});

if (!String.prototype.startsWith) {
    /**
     * APIMethod: String.startsWith
     * *Deprecated*. Whether or not a string starts with another string. 
     * 
     * Parameters:
     * sStart - {String} The string we're testing for.
     *  
     * Returns:
     * {Boolean} Whether or not this string starts with the string passed in.
     */
    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) {
    /**
     * APIMethod: String.contains
     * *Deprecated*. Whether or not a string contains another string.
     * 
     * Parameters:
     * str - {String} The string that we're testing for.
     * 
     * Returns:
     * {Boolean} Whether or not this string contains with the string passed in.
     */
    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) {
    /**
     * APIMethod: String.trim
     * *Deprecated*. Removes leading and trailing whitespace characters from a string.
     * 
     * Returns:
     * {String} A trimmed version of the string - all leading and 
     *          trailing spaces removed
     */
    String.prototype.trim = function() {
        OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
                                      {'newMethod':'OpenLayers.String.trim'}));
        return OpenLayers.String.trim(this);
    };
}

if (!String.prototype.camelize) {
    /**
     * APIMethod: String.camelize
     * *Deprecated*. Camel-case a hyphenated string. 
     *     Ex. "chicken-head" becomes "chickenHead", and
     *     "-chicken-head" becomes "ChickenHead".
     * 
     * Returns:
     * {String} The string, camelized
     */
    String.prototype.camelize = function() {
        OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
                                  {'newMethod':'OpenLayers.String.camelize'}));
        return OpenLayers.String.camelize(this);
    };
}

if (!Function.prototype.bind) {
    /**
     * APIMethod: Function.bind
     * *Deprecated*. Bind a function to an object. 
     * Method to easily create closures with 'this' altered.
     * 
     * Parameters:
     * object - {Object} the this parameter
     * 
     * Returns:
     * {Function} A closure with 'this' altered to the first
     *            argument.
     */
    Function.prototype.bind = function() {
        OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
                                {'newMethod':'OpenLayers.Function.bind'}));
        // new function takes the same arguments with this function up front
        Array.prototype.unshift.apply(arguments, [this]);
        return OpenLayers.Function.bind.apply(null, arguments);
    };
}

if (!Function.prototype.bindAsEventListener) {
    /**
     * APIMethod: Function.bindAsEventListener
     * *Deprecated*. Bind a function to an object, and configure it to receive the
     *     event object as first parameter when called. 
     * 
     * Parameters:
     * object - {Object} A reference to this.
     * 
     * Returns:
     * {Function}
     */
    Function.prototype.bindAsEventListener = function(object) {
        OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
                        {'newMethod':'OpenLayers.Function.bindAsEventListener'}));
        return OpenLayers.Function.bindAsEventListener(this, object);
    };
}

// FIXME: Remove this in 3.0. In 3.0, Event.stop will no longer be provided
// by OpenLayers.
if (window.Event) {
    OpenLayers.Util.applyDefaults(window.Event, OpenLayers.Event);
} else {
    var Event = OpenLayers.Event;
}

/**
 * Namespace: OpenLayers.Tile
 */
OpenLayers.Util.extend(OpenLayers.Tile.prototype, {
    /**   
     * Method: getBoundsFromBaseLayer
     * Take the pixel locations of the corner of the tile, and pass them to 
     *     the base layer and ask for the location of those pixels, so that 
     *     displaying tiles over Google works fine.
     *
     * Parameters:
     * position - {<OpenLayers.Pixel>}
     *
     * Returns:
     * bounds - {<OpenLayers.Bounds>} 
     */
    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); 
        // Handle the case where the base layer wraps around the date line.
        // Google does this, and it breaks WMS servers to request bounds in 
        // that fashion.  
        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;
    }    
});

/**
 * Class: OpenLayers.Control.MouseDefaults
 * This class is DEPRECATED in 2.4 and will be removed by 3.0.
 * If you need this functionality, use <OpenLayers.Control.Navigation> 
 * instead!!!
 *
 * 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 = OpenLayers.Function.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.viewPortDiv)) {
            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"
});

/**
 * Class: OpenLayers.Control.MouseToolbar
 * This class is DEPRECATED in 2.4 and will be removed by 3.0.
 * If you need this functionality, use <OpenLayers.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.getImageLocation(img);
        var activeImgLocation = OpenLayers.Util.getImageLocation(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 = OpenLayers.Function.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.viewPortDiv)) {
            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;

/**
 * Class: OpenLayers.Layer.Grid
 */

OpenLayers.Util.extend(OpenLayers.Layer.Grid.prototype, {

    /**
     * Method: getGridBounds
     * Deprecated. This function will be removed in 3.0. Please use 
     *     getTilesBounds() instead.
     * 
     * Returns:
     * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
     * currently loaded tiles (including those partially or not at all seen 
     * onscreen)
     */
    getGridBounds: function() {
        var msg = "The getGridBounds() function is deprecated. It will be " +
                  "removed in 3.0. Please use getTilesBounds() instead.";
        OpenLayers.Console.warn(msg);
        return this.getTilesBounds();
    }
});

/**
 * Class: OpenLayers.Format.XML
 */
OpenLayers.Util.extend(OpenLayers.Format.XML.prototype, {

    /**
     * APIMethod: concatChildValues
     * *Deprecated*. Use <getChildValue> instead.
     *
     * 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;
    }

});

/**
 * Class: OpenLayers.Layer.WMS.Post
 * Instances of OpenLayers.Layer.WMS.Post are used to retrieve data from OGC
 * Web Mapping Services via HTTP-POST (application/x-www-form-urlencoded). 
 * Create a new WMS layer with the <OpenLayers.Layer.WMS.Post> constructor.
 *
 * *Deprecated*. Instead of this layer, use <OpenLayers.Layer.WMS> with
 * <OpenLayers.Tile.Image.maxGetUrlLength> configured in the layer's
 * <OpenLayers.Layer.WMS.tileOptions>.
 *
 * Inherits from:
 *  - <OpenLayers.Layer.WMS>
 */
OpenLayers.Layer.WMS.Post = OpenLayers.Class(OpenLayers.Layer.WMS, {

    /**
     * APIProperty: unsupportedBrowsers
     * {Array} Array with browsers, which should use the HTTP-GET protocol 
     * instead of HTTP-POST for fetching tiles from a WMS .
     * Defaults to ["mozilla", "firefox", "opera"], because Opera is not able 
     * to show transparent images in IFrames and Firefox/Mozilla has some ugly 
     * effects of viewport-shaking when panning the map. Both browsers, Opera
     * and Firefox/Mozilla, have no problem with long urls, which is the reason
     * for using POST instead of GET. The strings to pass to this array are
     * the ones returned by <OpenLayers.BROWSER_NAME>.
     */
    unsupportedBrowsers: ["mozilla", "firefox", "opera"],

    /**
     * Property: SUPPORTED_TRANSITIONS
     * {Array} 
     * no supported transitions for this type of layer, because it is not
     * possible to modify the initialized tiles (iframes)
     */
    SUPPORTED_TRANSITIONS: [],
    
    /**
     * Property: usePost
     * {Boolean}
     */
    usePost: null,

    /**
     * Constructor: OpenLayers.Layer.WMS.Post
     * Creates a new WMS layer object.
     *
     * Example:
     * (code)
     * var wms = new OpenLayers.Layer.WMS.Post(
     *  "NASA Global Mosaic",
     *  "http://wms.jpl.nasa.gov/wms.cgi",
     *  {layers: "modis, global_mosaic"});
     * (end)
     *
     * Parameters:
     * name - {String} A name for the layer
     * url - {String} Base url for the WMS
     *                (e.g. http://wms.jpl.nasa.gov/wms.cgi)
     * params - {Object} An object with key/value pairs representing the
     *                   GetMap query string parameters and parameter values.
     * options - {Object} Hashtable of extra options to tag onto the layer.
     */
    initialize: function(name, url, params, options) {
        var newArguments = [];
        newArguments.push(name, url, params, options);
        OpenLayers.Layer.WMS.prototype.initialize.apply(this, newArguments);

        this.usePost = OpenLayers.Util.indexOf(
            this.unsupportedBrowsers, OpenLayers.BROWSER_NAME) == -1;
    },
    
    /**
     * Method: addTile
     * addTile creates a tile, initializes it and adds it as iframe to the
     * layer div.
     *
     * Parameters:
     * bounds - {<OpenLayers.Bounds>}
     * position - {<OpenLayers.Pixel>}
     *
     * Returns:
     * {<OpenLayers.Tile.Image.IFrame>} The added OpenLayers.Tile.Image.IFrame
     */
    addTile: function(bounds,position) {
        return new OpenLayers.Tile.Image(
            this, position, bounds, null, this.tileSize, {
                maxGetUrlLength: this.usePost ? 0 : null
            });
    },

    CLASS_NAME: 'OpenLayers.Layer.WMS.Post'
});

/**
 * 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.getOptions());
        }

        //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"
});

/**
 * 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.getOptions());
        }

        //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"
});

/**
 * 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"
  }
);

/**
 * 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"
});


/**
 * Class: OpenLayers.Layer.WFS
 * *Deprecated*.  To be removed in 3.0.  Instead use OpenLayers.Layer.Vector
 *     with a Protocol.WFS and one or more Strategies.
 *
 * 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 property determines the size of the serverside query
     *    relative to the map viewport size. By default, we load an area twice
     *    as big as the map, to allow for panning without immediately reload.
     *    Setting this to 1 will cause the area of the WFS request to match
     *    the map area exactly. It is recommended to set this to some number
     *    at least slightly larger than 1, otherwise accidental clicks can
     *    cause a data reload, by moving the map only 1 pixel.
     */
    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;
        }

        // Uppercase params
        params = OpenLayers.Util.upperCaseObject(params);

        // 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 = null;

            // Cant combine "filter" and "BBOX". This is a cheap hack to help
            // people out who can't migrate to the WFS protocol immediately.
            var filter = this.params.filter || this.params.FILTER;
            if (filter) {
                params = {FILTER: filter};
            }
            else {
                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);
                if (!filter){
                    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);
        }
    },

    /**
     * Method: display
     * Call the display method of the appropriate parent class.
     */
    display: function() {
        if(this.vectorMode) {
            OpenLayers.Layer.Vector.prototype.display.apply(this,
                                                                arguments);
        } else {
            OpenLayers.Layer.Markers.prototype.display.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.getOptions());
        }

        //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();
        }
    },

    /**
     * APIMethod: getDataExtent
     * Calculates the max extent which includes all of the layer data.
     *
     * Returns:
     * {<OpenLayers.Bounds>}
     */
    getDataExtent: function () {
        var extent;
        //get all additions from superclasses
        if (this.vectorMode) {
            extent = OpenLayers.Layer.Vector.prototype.getDataExtent.apply(this);
        } else {
            extent = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(this);
        }

        return extent;
    },

    /**
     * APIMethod: setOpacity
     * Call the setOpacity method of the appropriate parent class to set the
     *     opacity.
     *
     * Parameters:
     * opacity - {Float}
     */
    setOpacity: function (opacity) {
        if (this.vectorMode) {
            OpenLayers.Layer.Vector.prototype.setOpacity.apply(this, [opacity]);
        } else {
            OpenLayers.Layer.Markers.prototype.setOpacity.apply(this, [opacity]);
        }
    },

    CLASS_NAME: "OpenLayers.Layer.WFS"
});

/**
 * Class: OpenLayers.Layer.VirtualEarth
 * *Deprecated*. Use <OpenLayers.Layer.Bing> instead.
 *
 * Instances of OpenLayers.Layer.VirtualEarth are used to display the data from
 *     the Bing Maps AJAX Control (see e.g.
 *     http://msdn.microsoft.com/library/bb429619.aspx). Create a VirtualEarth
 *     layer with the <OpenLayers.Layer.VirtualEarth> constructor.
 *
 * 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} 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
    ],

    /**
     * APIProperty: type
     * {VEMapType}
     */
    type: null,

    /**
     * APIProperty: wrapDateLine
     * {Boolean} Allow user to pan forever east/west.  Default is true.
     *     Setting this to false only restricts panning if
     *     <sphericalMercator> is true.
     */
    wrapDateLine: true,

    /**
     * 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,

    /**
     * APIProperty: animationEnabled
     * {Boolean} If set to true, the transition between zoom levels will be
     *     animated. Set to false to match the zooming experience of other
     *     layer types. Default is true.
     */
    animationEnabled: true,

    /**
     * Constructor: OpenLayers.Layer.VirtualEarth
     * Creates a new instance of a OpenLayers.Layer.VirtualEarth. If you use an
     *     instance of OpenLayers.Layer.VirtualEarth in you map, you should set
     *     the <OpenLayers.Map> option restrictedExtent to a meaningful value,
     *     e.g.:
     * (code)
     * var map = new OpenLayers.Map( 'map', {
     *     // other map options
     *     restrictedExtent : OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508)
     * } );
     *
     * var veLayer = new OpenLayers.Layer.VirtualEarth (
     *     "Virtual Earth Layer"
     * );
     *
     * map.addLayer( veLayer );
     * (end)
     *
     * 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", OpenLayers.Function.True);

            } catch (e) { }
            this.mapObject.HideDashboard();
            if(typeof this.mapObject.SetAnimationEnabled == "function") {
                this.mapObject.SetAnimationEnabled(this.animationEnabled);
            }
        }

        //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;
        }

    },

    /**
     * Method: onMapResize
     */
    onMapResize: function() {
        this.mapObject.Resize(this.map.size.w, this.map.size.h);
    },

    /**
     * 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"
});

/*
 * Copyright 2007, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. Neither the name of Google Inc. nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Sets up google.gears.*, which is *the only* supported way to access Gears.
 *
 * Circumvent this file at your own risk!
 *
 * In the future, Gears may automatically define google.gears.* without this
 * file. Gears may use these objects to transparently fix bugs and compatibility
 * issues. Applications that use the code below will continue to work seamlessly
 * when that happens.
 */

(function() {
  // We are already defined. Hooray!
  if (window.google && google.gears) {
    return;
  }

  var factory = null;

  // Firefox
  if (typeof GearsFactory != 'undefined') {
    factory = new GearsFactory();
  } else {
    // IE
    try {
      factory = new ActiveXObject('Gears.Factory');
      // privateSetGlobalObject is only required and supported on WinCE.
      if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
        factory.privateSetGlobalObject(this);
      }
    } catch (e) {
      // Safari
      if ((typeof navigator.mimeTypes != 'undefined')
           && navigator.mimeTypes["application/x-googlegears"]) {
        factory = document.createElement("object");
        factory.style.display = "none";
        factory.width = 0;
        factory.height = 0;
        factory.type = "application/x-googlegears";
        document.documentElement.appendChild(factory);
      }
    }
  }

  // *Do not* define any objects if Gears is not installed. This mimics the
  // behavior of Gears defining the objects in the future.
  if (!factory) {
    return;
  }

  // Now set up the objects, being careful not to overwrite anything.
  //
  // Note: In Internet Explorer for Windows Mobile, you can't add properties to
  // the window object. However, global objects are automatically added as
  // properties of the window object in all browsers.
  if (!window.google) {
    google = {};
  }

  if (!google.gears) {
    google.gears = {factory: factory};
  }
})();

/**
 * Class: OpenLayers.Protocol.SQL
 * Abstract SQL protocol class.  Not to be instantiated directly.  Use
 *     one of the SQL protocol subclasses instead.
 *
 * Inherits from:
 *  - <OpenLayers.Protocol>
 */
OpenLayers.Protocol.SQL = OpenLayers.Class(OpenLayers.Protocol, {

    /**
     * APIProperty: databaseName
     * {String}
     */
    databaseName: 'ol',

    /**
     * APIProperty: tableName
     * Name of the database table into which Features should be saved.
     */
    tableName: "ol_vector_features",

    /**
     * Property: postReadFiltering
     * {Boolean} Whether the filter (if there's one) must be applied after
     *      the features have been read from the database; for example the
     *      BBOX strategy passes the read method a BBOX spatial filter, if
     *      postReadFiltering is true every feature read from the database
     *      will go through the BBOX spatial filter, which can be costly;
     *      defaults to true.
     */
    postReadFiltering: true,

    /**
     * Constructor: OpenLayers.Protocol.SQL
     */
    initialize: function(options) {
        OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
    },

    /**
     * APIMethod: destroy
     * Clean up the protocol.
     */
    destroy: function() {
        OpenLayers.Protocol.prototype.destroy.apply(this);
    },

    /**
     * APIMethod: supported
     * This should be overridden by specific subclasses
     *
     * Returns:
     * {Boolean} Whether or not the browser supports the SQL backend
     */
    supported: function() {
        return false;
    },

    /**
     * Method: evaluateFilter
     * If postReadFiltering is true evaluate the filter against the feature
     * and return the result of the evaluation, otherwise return true.
     *
     * Parameters:
     * {<OpenLayers.Feature.Vector>} The feature.
     * {<OpenLayers.Filter>} The filter.
     *
     * Returns:
     * {Boolean} true if postReadFiltering if false, the result of the
     * filter evaluation otherwise.
     */
    evaluateFilter: function(feature, filter) {
        return filter && this.postReadFiltering ?
            filter.evaluate(feature) : true;
    },

    CLASS_NAME: "OpenLayers.Protocol.SQL"
});

/**
 * Class: OpenLayers.Protocol.SQL.Gears
 * This Protocol stores feature in the browser via the Gears Database module
 * <http://code.google.com/apis/gears/api_database.html>.
 *
 * The main advantage is that all the read, create, update and delete operations
 * can be done offline.
 *
 * Inherits from:
 *  - <OpenLayers.Protocol.SQL>
 */
OpenLayers.Protocol.SQL.Gears = OpenLayers.Class(OpenLayers.Protocol.SQL, {

    /**
     * Property: FID_PREFIX
     * {String}
     */
    FID_PREFIX: '__gears_fid__',

    /**
     * Property: NULL_GEOMETRY
     * {String}
     */
    NULL_GEOMETRY: '__gears_null_geometry__',

    /**
     * Property: NULL_FEATURE_STATE
     * {String}
     */
    NULL_FEATURE_STATE: '__gears_null_feature_state__',

    /**
     * Property: jsonParser
     * {<OpenLayers.Format.JSON>}
     */
    jsonParser: null,

    /**
     * Property: wktParser
     * {<OpenLayers.Format.WKT>}
     */
    wktParser: null,

    /**
     * Property: fidRegExp
     * {RegExp} Regular expression to know whether a feature was
     *      created in offline mode.
     */
    fidRegExp: null,

    /**
     * Property: saveFeatureState
     * {Boolean} Whether to save the feature state (<OpenLayers.State>)
     *      into the database, defaults to true.
     */
    saveFeatureState: true,

    /**
     * Property: typeOfFid
     * {String} The type of the feature identifier, either "number" or
     *      "string", defaults to "string".
     */
    typeOfFid: "string",

    /**
     * Property: db
     * {GearsDatabase}
     */
    db: null,

    /**
     * Constructor: OpenLayers.Protocol.SQL.Gears
     */
    initialize: function(options) {
        if (!this.supported()) {
            return;
        }
        OpenLayers.Protocol.SQL.prototype.initialize.apply(this, [options]);
        this.jsonParser = new OpenLayers.Format.JSON();
        this.wktParser = new OpenLayers.Format.WKT();

        this.fidRegExp = new RegExp('^' + this.FID_PREFIX);
        this.initializeDatabase();


    },

    /**
     * Method: initializeDatabase
     */
    initializeDatabase: function() {
        this.db = google.gears.factory.create('beta.database');
        this.db.open(this.databaseName);
        this.db.execute(
            "CREATE TABLE IF NOT EXISTS " + this.tableName +
            " (fid TEXT UNIQUE, geometry TEXT, properties TEXT," +
            "  state TEXT)");
   },

    /**
     * APIMethod: destroy
     * Clean up the protocol.
     */
    destroy: function() {
        this.db.close();
        this.db = null;

        this.jsonParser = null;
        this.wktParser = null;

        OpenLayers.Protocol.SQL.prototype.destroy.apply(this);
    },

    /**
     * APIMethod: supported
     * Determine whether a browser supports Gears
     *
     * Returns:
     * {Boolean} The browser supports Gears
     */
    supported: function() {
        return !!(window.google && google.gears);
    },

    /**
     * APIMethod: read
     * Read all features from the database and return a
     * <OpenLayers.Protocol.Response> instance. If the options parameter
     * contains a callback attribute, the function is called with the response
     * as a parameter.
     *
     * Parameters:
     * options - {Object} Optional object for configuring the request; it
     *      can have the {Boolean} property "noFeatureStateReset" which
     *      specifies if the state of features read from the Gears
     *      database must be reset to null, if "noFeatureStateReset"
     *      is undefined or false then each feature's state is reset
     *      to null, if "noFeatureStateReset" is true the feature state
     *      is preserved.
     *
     * Returns:
     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *      object.
     */
    read: function(options) {
        OpenLayers.Protocol.prototype.read.apply(this, arguments);
        options = OpenLayers.Util.applyDefaults(options, this.options);

        var feature, features = [];
        var rs = this.db.execute("SELECT * FROM " + this.tableName);
        while (rs.isValidRow()) {
            feature = this.unfreezeFeature(rs);
            if (this.evaluateFilter(feature, options.filter)) {
                if (!options.noFeatureStateReset) {
                    feature.state = null;
                }
                features.push(feature);
            }
            rs.next();
        }
        rs.close();

        var resp = new OpenLayers.Protocol.Response({
            code: OpenLayers.Protocol.Response.SUCCESS,
            requestType: "read",
            features: features
        });

        if (options && options.callback) {
            options.callback.call(options.scope, resp);
        }

        return resp;
    },

    /**
     * Method: unfreezeFeature
     *
     * Parameters:
     * row - {ResultSet}
     *
     * Returns:
     * {<OpenLayers.Feature.Vector>}
     */
    unfreezeFeature: function(row) {
        var feature;
        var wkt = row.fieldByName('geometry');
        if (wkt == this.NULL_GEOMETRY) {
            feature = new OpenLayers.Feature.Vector();
        } else {
            feature = this.wktParser.read(wkt);
        }

        feature.attributes = this.jsonParser.read(
            row.fieldByName('properties'));

        feature.fid = this.extractFidFromField(row.fieldByName('fid'));

        var state = row.fieldByName('state');
        if (state == this.NULL_FEATURE_STATE) {
            state = null;
        }
        feature.state = state;

        return feature;
    },

    /**
     * Method: extractFidFromField
     *
     * Parameters:
     * field - {String}
     *
     * Returns
     * {String} or {Number} The fid.
     */
    extractFidFromField: function(field) {
        if (!field.match(this.fidRegExp) && this.typeOfFid == "number") {
            field = parseFloat(field);
        }
        return field;
    },

    /**
     * APIMethod: create
     * Create new features into the database.
     *
     * Parameters:
     * features - {Array({<OpenLayers.Feature.Vector>})} or
     *            {<OpenLayers.Feature.Vector>} The features to create in
     *            the database.
     * options - {Object} Optional object for configuring the request.
     *
     * Returns:
     *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *          object.
     */
    create: function(features, options) {
        options = OpenLayers.Util.applyDefaults(options, this.options);

        var resp = this.createOrUpdate(features);
        resp.requestType = "create";

        if (options && options.callback) {
            options.callback.call(options.scope, resp);
        }

        return resp;
    },

    /**
     * APIMethod: update
     * Construct a request updating modified feature.
     *
     * Parameters:
     * features - {Array({<OpenLayers.Feature.Vector>})} or
     *            {<OpenLayers.Feature.Vector>} The features to update in
     *            the database.
     * options - {Object} Optional object for configuring the request.
     *
     * Returns:
     *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *          object.
     */
    update: function(features, options) {
        options = OpenLayers.Util.applyDefaults(options, this.options);

        var resp = this.createOrUpdate(features);
        resp.requestType = "update";

        if (options && options.callback) {
            options.callback.call(options.scope, resp);
        }

        return resp;
    },

    /**
     * Method: createOrUpdate
     * Construct a request for updating or creating features in the
     * database.
     *
     * Parameters:
     * features - {Array({<OpenLayers.Feature.Vector>})} or
     *      {<OpenLayers.Feature.Vector>} The feature to create or update
     *      in the database.
     *
     * Returns:
     *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *          object.
     */
    createOrUpdate: function(features) {
        if (!(OpenLayers.Util.isArray(features))) {
            features = [features];
        }

        var i, len = features.length, feature;
        var insertedFeatures = new Array(len);

        for (i = 0; i < len; i++) {
            feature = features[i];
            var params = this.freezeFeature(feature);
            this.db.execute(
                "REPLACE INTO " + this.tableName +
                " (fid, geometry, properties, state)" +
                " VALUES (?, ?, ?, ?)",
                params);

            var clone = feature.clone();
            clone.fid = this.extractFidFromField(params[0]);
            insertedFeatures[i] = clone;
        }

        return new OpenLayers.Protocol.Response({
            code: OpenLayers.Protocol.Response.SUCCESS,
            features: insertedFeatures,
            reqFeatures: features
        });
    },

    /**
     * Method: freezeFeature
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>}
     * state - {String} The feature state to store in the database.
     *
     * Returns:
     * {Array}
     */
    freezeFeature: function(feature) {
        // 2 notes:
        // - fid might not be a string
        // - getFeatureStateForFreeze needs the feature fid to it's stored
        //   in the feature here
        feature.fid = feature.fid != null ?
            "" + feature.fid : OpenLayers.Util.createUniqueID(this.FID_PREFIX);

        var geometry = feature.geometry != null ?
            feature.geometry.toString() : this.NULL_GEOMETRY;

        var properties = this.jsonParser.write(feature.attributes);

        var state = this.getFeatureStateForFreeze(feature);

        return [feature.fid, geometry, properties, state];
    },

    /**
     * Method: getFeatureStateForFreeze
     * Get the state of the feature to store into the database.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The feature.
     *
     * Returns
     * {String} The state
     */
    getFeatureStateForFreeze: function(feature) {
        var state;
        if (!this.saveFeatureState) {
            state = this.NULL_FEATURE_STATE;
        } else if (this.createdOffline(feature)) {
            // if the feature was created in offline mode, its
            // state must remain INSERT
            state = OpenLayers.State.INSERT;
        } else {
            state = feature.state;
        }
        return state;
    },

    /**
     * APIMethod: delete
     * Delete features from the database.
     *
     * Parameters:
     * features - {Array({<OpenLayers.Feature.Vector>})} or
     *            {<OpenLayers.Feature.Vector>}
     * options - {Object} Optional object for configuring the request.
     *       This object is modified and should not be reused.
     *
     * Returns:
     *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
     *          object.
     */
    "delete": function(features, options) {
        if (!(OpenLayers.Util.isArray(features))) {
            features = [features];
        }

        options = OpenLayers.Util.applyDefaults(options, this.options);

        var i, len, feature;
        for (i = 0, len = features.length; i < len; i++) {
            feature = features[i];

            // if saveFeatureState is set to true and if the feature wasn't created
            // in offline mode we don't delete it in the database but just update
            // it state column
            if (this.saveFeatureState && !this.createdOffline(feature)) {
                var toDelete = feature.clone();
                toDelete.fid = feature.fid;
                if (toDelete.geometry) {
                    toDelete.geometry.destroy();
                    toDelete.geometry = null;
                }
                toDelete.state = feature.state;
                this.createOrUpdate(toDelete);
            } else {
                this.db.execute(
                    "DELETE FROM " + this.tableName +
                    " WHERE fid = ?", [feature.fid]);
            }
        }

        var resp = new OpenLayers.Protocol.Response({
            code: OpenLayers.Protocol.Response.SUCCESS,
            requestType: "delete",
            reqFeatures: features
        });

        if (options && options.callback) {
            options.callback.call(options.scope, resp);
        }

        return resp;
    },

    /**
     * Method: createdOffline
     * Returns true if the feature had a feature id when it was created in
     *      the Gears database, false otherwise; this is determined by
     *      checking the form of the feature's fid value.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>}
     *
     * Returns:
     * {Boolean}
     */
    createdOffline: function(feature) {
        return (typeof feature.fid == "string" &&
                !!(feature.fid.match(this.fidRegExp)));
    },

    /**
     * APIMethod: 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, one per request made
     *       to the database.
     */
    commit: function(features, options) {
        var opt, resp = [], nRequests = 0, nResponses = 0;

        function callback(resp) {
            if (++nResponses < nRequests) {
                resp.last = false;
            }
            this.callUserCallback(options, resp);
        }

        var feature, toCreate = [], toUpdate = [], toDelete = [];
        for (var i = features.length - 1; i >= 0; i--) {
            feature = features[i];
            switch (feature.state) {
            case OpenLayers.State.INSERT:
                toCreate.push(feature);
                break;
            case OpenLayers.State.UPDATE:
                toUpdate.push(feature);
                break;
            case OpenLayers.State.DELETE:
                toDelete.push(feature);
                break;
            }
        }
        if (toCreate.length > 0) {
            nRequests++;
            opt = OpenLayers.Util.applyDefaults(
                {"callback": callback, "scope": this},
                options.create
            );
            resp.push(this.create(toCreate, opt));
        }
        if (toUpdate.length > 0) {
            nRequests++;
            opt = OpenLayers.Util.applyDefaults(
                {"callback": callback, "scope": this},
                options.update
            );
            resp.push(this.update(toUpdate, opt));
        }
        if (toDelete.length > 0) {
            nRequests++;
            opt = OpenLayers.Util.applyDefaults(
                {"callback": callback, "scope": this},
                options["delete"]
            );
            resp.push(this["delete"](toDelete, opt));
        }

        return resp;
    },

    /**
     * Method: clear
     * Removes all rows of the table.
     */
    clear: function() {
        this.db.execute("DELETE FROM " + this.tableName);
    },

    /**
     * Method: callUserCallback
     * This method is called from within commit each time a request is made
     * to the database, it is responsible for calling the user-supplied
     * callbacks.
     *
     * Parameters:
     * options - {Object} The map of options passed to the commit call.
     * resp - {<OpenLayers.Protocol.Response>}
     */
    callUserCallback: function(options, resp) {
        var opt = options[resp.requestType];
        if (opt && opt.callback) {
            opt.callback.call(opt.scope, resp);
        }
        if (resp.last && options.callback) {
            options.callback.call(options.scope);
        }
    },

    CLASS_NAME: "OpenLayers.Protocol.SQL.Gears"
});

/**
 * 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} 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,
        0.00002145767211914062,
        0.00001072883605957031
    ],

    /**
     * APIProperty: type
     * {YahooMapType}
     */
    type: null,

    /**
     * APIProperty: wrapDateLine
     * {Boolean} Allow user to pan forever east/west.  Default is true.
     *     Setting this to false only restricts panning if
     *     <sphericalMercator> is true.
     */
    wrapDateLine: true,

    /**
     * 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"
});

/**
 * Class: OpenLayers.Layer.GML
 * Create a vector layer by parsing a GML file. The GML file is
 *     passed in as a parameter.
 * *Deprecated*.  To be removed in 3.0.  Instead use OpenLayers.Layer.Vector
 *     with Protocol.HTTP and Strategy.Fixed. Provide the protocol with a
 *     format parameter to get the parser you want for your data.
 *
 * 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.loadGML();
        }
    },

    /**
     * Method: loadGML
     */
    loadGML: function() {
        if (!this.loaded) {
            this.events.triggerEvent("loadstart");
            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.loadGML();
    },

    /**
     * Method: requestSuccess
     * Process GML after it has been loaded.
     * Called by initialize() 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 initialize() and loadUrl() if there was a problem loading GML.
     *
     * Parameters:
     * request - {String}
     */
    requestFailure: function(request) {
        OpenLayers.Console.userError('Error in loading GML file ' +  this.url);
        this.events.triggerEvent("loadend");
    },

    CLASS_NAME: "OpenLayers.Layer.GML"
});

/**
 * 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"
});

/**
 * Class: OpenLayers.Renderer.NG
 *
 * Inherits from:
 *  - <OpenLayers.Renderer.Elements>
 */
OpenLayers.Renderer.NG = OpenLayers.Class(OpenLayers.Renderer.Elements, {

    /**
     * Constant: labelNodeType
     * {String} The node type for text label containers. To be defined by
     * subclasses.
     */
    labelNodeType: null,

    /**
     * Constructor: OpenLayers.Renderer.NG
     *
     * 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.
     */

    /**
     * Method: updateDimensions
     * To be extended by subclasses - here we set positioning related styles
     * on HTML elements, subclasses have to do the same for renderer specific
     * elements (e.g. viewBox, width and height of the rendererRoot)
     *
     * Parameters:
     * zoomChanged - {Boolean} Has the zoom changed? If so, subclasses may have
     *     to update feature styles/dimensions.
     */
    updateDimensions: function(zoomChanged) {
        var mapExtent = this.map.getExtent();
        var renderExtent = mapExtent.scale(3);
        this.setExtent(renderExtent, true);
        var res = this.getResolution();
        var div = this.rendererRoot.parentNode;
        var layerLeft = parseFloat(div.parentNode.style.left);
        var layerTop = parseFloat(div.parentNode.style.top);
        div.style.left = ((renderExtent.left - mapExtent.left) / res - layerLeft) + "px";
        div.style.top = ((mapExtent.top - renderExtent.top) / res - layerTop) + "px";
    },

    /**
     * Method: resize
     */
    setSize: function() {
        this.map.getExtent() && this.updateDimensions();
    },

    /**
     * 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) {
            var rendered = this.drawGeometry(feature.geometry, style, feature.id);
            if(rendered !== false && style.label) {
                var location = feature.geometry.getCentroid();
                this.drawText(feature.id, style, location);
            } else {
                this.removeText(feature.id);
            }
            return rendered;
        }
    },

    /**
     * Method: drawText
     * Function for drawing text labels.
     * This method is only called by the renderer itself.
     *
     * Parameters:
     * featureId - {String|DOMElement}
     * style - {Object}
     * location - {<OpenLayers.Geometry.Point>}, will be modified inline
     *
     * Returns:
     * {DOMElement} container holding the text label (to be populated by
     * subclasses)
     */
    drawText: function(featureId, style, location) {
        var label;
        if (typeof featureId !== "string") {
            label = featureId;
        } else {
            label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, this.labelNodeType);
            label._featureId = featureId;
        }
        label._style = style;
        label._x = location.x;
        label._y = location.y;
        if(style.labelXOffset || style.labelYOffset) {
            var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
            var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
            var res = this.getResolution();
            location.move(xOffset*res, yOffset*res);
        }

        if(label.parentNode !== this.textRoot) {
            this.textRoot.appendChild(label);
        }

        return label;
    },

    CLASS_NAME: "OpenLayers.Renderer.NG"
});

// Monkey-patching Layer.Vector for Renderer.NG support
(function() {
    var moveTo = OpenLayers.Layer.Vector.prototype.moveTo;
    OpenLayers.Layer.Vector.prototype.moveTo = function(bounds, zoomChanged, dragging) {
        if (OpenLayers.Renderer.NG && this.renderer instanceof OpenLayers.Renderer.NG) {
            OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
            dragging || this.renderer.updateDimensions(zoomChanged);
            if (!this.drawn) {
                this.drawn = true;
                var feature;
                for(var i=0, len=this.features.length; i<len; i++) {
                    this.renderer.locked = (i !== (len - 1));
                    feature = this.features[i];
                    this.drawFeature(feature);
                }
            }
        } else {
            moveTo.apply(this, arguments);
        }
    }
    var redraw = OpenLayers.Layer.Vector.prototype.redraw;
    OpenLayers.Layer.Vector.prototype.redraw = function() {
        if (OpenLayers.Renderer.NG && this.renderer instanceof OpenLayers.Renderer.NG) {
            this.drawn = false;
        }
        redraw.apply(this, arguments);
    }
})();

/**
 * Class: OpenLayers.Renderer.SVG2
 *
 * Inherits from:
 *  - <OpenLayers.Renderer.NG>
 */
OpenLayers.Renderer.SVG2 = OpenLayers.Class(OpenLayers.Renderer.NG, {

    /**
     * Property: xmlns
     * {String}
     */
    xmlns: "http://www.w3.org/2000/svg",

    /**
     * Property: xlinkns
     * {String}
     */
    xlinkns: "http://www.w3.org/1999/xlink",

    /**
     * Property: symbolMetrics
     * {Object} Cache for symbol metrics according to their svg coordinate
     *     space. This is an object keyed by the symbol's id, and values are
     *     an object with size, x and y properties.
     */
    symbolMetrics: null,

    /**
     * Constant: labelNodeType
     * {String} The node type for text label containers.
     */
    labelNodeType: "g",

    /**
     * Constructor: OpenLayers.Renderer.SVG2
     *
     * Parameters:
     * containerID - {String}
     */
    initialize: function(containerID) {
        if (!this.supported()) {
            return;
        }
        OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
                                                                arguments);

        this.symbolMetrics = {};
    },

    /**
     * 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: updateDimensions
     *
     * Parameters:
     * zoomChanged - {Boolean}
     */
    updateDimensions: function(zoomChanged) {
        OpenLayers.Renderer.NG.prototype.updateDimensions.apply(this, arguments);

        var res = this.getResolution();

        var width = this.extent.getWidth();
        var height = this.extent.getHeight();

        var extentString = [
            this.extent.left,
            -this.extent.top,
            width,
            height
        ].join(" ");
        this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
        this.rendererRoot.setAttributeNS(null, "width", width / res);
        this.rendererRoot.setAttributeNS(null, "height", height / res);

        if (zoomChanged === true) {
            // update styles for the new resolution
            var i, len;
            var nodes = this.vectorRoot.childNodes;
            for (i=0, len=nodes.length; i<len; ++i) {
                this.setStyle(nodes[i]);
            }
            var textNodes = this.textRoot.childNodes;
            var label;
            for (i=0, len=textNodes.length; i<len; ++i) {
                label = textNodes[i];
                this.drawText(label, label._style,
                    new OpenLayers.Geometry.Point(label._x, label._y)
                );
            }
        }
    },

    /**
     * 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 = "svg";
                } 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":
                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 resolution = this.getResolution();
        var r = node._radius;
        var widthFactor = resolution;
        if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
            node.style.visibility = "";
            if (style.graphic === false) {
                node.style.visibility = "hidden";
            } else if (style.externalGraphic) {

                if (style.graphicTitle) {
                    node.setAttributeNS(null, "title", style.graphicTitle);
                    //Standards-conformant SVG
                    // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
                    var titleNode = node.getElementsByTagName("title");
                    if (titleNode.length > 0) {
                        titleNode[0].firstChild.textContent = style.graphicTitle;
                    } else {
                        var label = this.nodeFactory(null, "title");
                        label.textContent = style.graphicTitle;
                        node.appendChild(label);
                    }
                }
                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;
                width *= resolution;
                height *= resolution;

                var xOffset = (style.graphicXOffset != undefined) ?
                    style.graphicXOffset * resolution : -(0.5 * width);
                var yOffset = (style.graphicYOffset != undefined) ?
                    style.graphicYOffset * resolution : -(0.5 * height);

                var opacity = style.graphicOpacity || style.fillOpacity;

                node.setAttributeNS(null, "x", node._x + xOffset);
                node.setAttributeNS(null, "y", node._y + yOffset);
                node.setAttributeNS(null, "width", width);
                node.setAttributeNS(null, "height", height);
                node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
                node.setAttributeNS(null, "style", "opacity: "+opacity);
                node.onclick = OpenLayers.Renderer.SVG2.preventDefault;
            } else if (this.isComplexSymbol(style.graphicName)) {
                // the symbol viewBox is three times as large as the symbol
                var offset = style.pointRadius * 3 * resolution;
                var size = offset * 2;
                var src = this.importSymbol(style.graphicName);
                widthFactor = this.symbolMetrics[src.id].size * 3 / size * resolution;

                // remove the node from the dom before we modify it. This
                // prevents various rendering issues in Safari and FF
                var parent = node.parentNode;
                var nextSibling = node.nextSibling;
                if(parent) {
                    parent.removeChild(node);
                }

                // The more appropriate way to implement this would be use/defs,
                // but due to various issues in several browsers, it is safer to
                // copy the symbols instead of referencing them.
                // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
                // and this email thread
                // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
                node.firstChild && node.removeChild(node.firstChild);
                node.appendChild(src.firstChild.cloneNode(true));
                node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));

                node.setAttributeNS(null, "width", size);
                node.setAttributeNS(null, "height", size);
                node.setAttributeNS(null, "x", node._x - offset);
                node.setAttributeNS(null, "y", node._y - offset);

                // now that the node has all its new properties, insert it
                // back into the dom where it was
                if(nextSibling) {
                    parent.insertBefore(node, nextSibling);
                } else if(parent) {
                    parent.appendChild(node);
                }
            } else {
                node.setAttributeNS(null, "r", style.pointRadius * resolution);
            }

            var rotation = style.rotation;
            if (rotation !== undefined || node._rotation !== undefined) {
                node._rotation = rotation;
                rotation |= 0;
                if (node.nodeName !== "svg") {
                    node.setAttributeNS(null, "transform",
                        ["rotate(", rotation, node._x, node._y, ")"].join(" ")
                    );
                } else {
                    var metrics = this.symbolMetrics[src.id];
                    node.firstChild.setAttributeNS(null, "transform",
                        ["rotate(", rotation, metrics.x, metrics.y, ")"].join(" ")
                    );
                }
            }
        }

        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 || "round");
            // 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");
            style.strokeDashstyle && 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;
        var str = style.strokeDashstyle;
        switch (str) {
            case 'solid':
                return 'none';
            case 'dot':
                return [widthFactor, 4 * w].join();
            case 'dash':
                return [4 * w, 4 * w].join();
            case 'dashdot':
                return [4 * w, 4 * w, widthFactor, 4 * w].join();
            case 'longdash':
                return [8 * w, 4 * w].join();
            case 'longdashdot':
                return [8 * w, 4 * w, widthFactor, 4 * w].join();
            default:
                var parts = OpenLayers.String.trim(str).split(/\s+/g);
                for (var i=0, ii=parts.length; i<ii; i++) {
                    parts[i] = parts[i] * widthFactor;
                }
                return parts.join();
        }
    },

    /**
     * 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
     *
     * Parameters:
     * suffix - {String} suffix to append to the id
     *
     * Returns:
     * {DOMElement}
     */
    createRoot: function(suffix) {
        return this.nodeFactory(this.container.id + suffix, "g");
    },

    /**
     * Method: createDefs
     *
     * Returns:
     * {DOMElement} The element to which we'll add the symbol definitions
     */
    createDefs: function() {
        var defs = this.nodeFactory(this.container.id + "_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 x = geometry.x;
        var y = -geometry.y;
        node.setAttributeNS(null, "cx", x);
        node.setAttributeNS(null, "cy", y);
        node._x = x;
        node._y = y;
        node._radius = radius;
        return node;
    },

    /**
     * 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 path = this.getComponentsString(geometry.components);
        node.setAttributeNS(null, "points", path);
        return node;
    },

    /**
     * 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 path = this.getComponentsString(geometry.components);
        node.setAttributeNS(null, "points", path);
        return node;
    },

    /**
     * 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.push("M");
            path = this.getComponentsString(
                geometry.components[j].components, " ");
            d.push(path);
        }
        d.push("z");
        node.setAttributeNS(null, "d", d.join(" "));
        node.setAttributeNS(null, "fill-rule", "evenodd");
        return node;
    },

    /**
     * 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) {
        node.setAttributeNS(null, "x", geometry.x);
        node.setAttributeNS(null, "y", -geometry.y);
        node.setAttributeNS(null, "width", geometry.width);
        node.setAttributeNS(null, "height", geometry.height);
        return node;
    },

    /**
     * Method: drawText
     * Function for drawing text labels.
     * This method is only called by the renderer itself.
     *
     * Parameters:
     * featureId - {String|DOMElement}
     * style - {Object}
     * location - {<OpenLayers.Geometry.Point>}, will be modified inline
     *
     * Returns:
     * {DOMElement} container holding the text label
     */
    drawText: function(featureId, style, location) {
        var g = OpenLayers.Renderer.NG.prototype.drawText.apply(this, arguments);
        var text = g.firstChild ||
            this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_text", "text");

        var res = this.getResolution();
        text.setAttributeNS(null, "x", location.x / res);
        text.setAttributeNS(null, "y", - location.y / res);
        g.setAttributeNS(null, "transform", "scale(" + res + ")");

        if (style.fontColor) {
            text.setAttributeNS(null, "fill", style.fontColor);
        }
        if (style.fontOpacity) {
            text.setAttributeNS(null, "opacity", style.fontOpacity);
        }
        if (style.fontFamily) {
            text.setAttributeNS(null, "font-family", style.fontFamily);
        }
        if (style.fontSize) {
            text.setAttributeNS(null, "font-size", style.fontSize);
        }
        if (style.fontWeight) {
            text.setAttributeNS(null, "font-weight", style.fontWeight);
        }
        if (style.fontStyle) {
            text.setAttributeNS(null, "font-style", style.fontStyle);
        }
        if (style.labelSelect === true) {
            text.setAttributeNS(null, "pointer-events", "visible");
            text._featureId = featureId;
        } else {
            text.setAttributeNS(null, "pointer-events", "none");
        }
        var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
        text.setAttributeNS(null, "text-anchor",
            OpenLayers.Renderer.SVG2.LABEL_ALIGN[align[0]] || "middle");

        if (OpenLayers.IS_GECKO === true) {
            text.setAttributeNS(null, "dominant-baseline",
                OpenLayers.Renderer.SVG2.LABEL_ALIGN[align[1]] || "central");
        }

        var labelRows = style.label.split('\n');
        var numRows = labelRows.length;
        while (text.childNodes.length > numRows) {
            text.removeChild(text.lastChild);
        }
        for (var i = 0; i < numRows; i++) {
            var tspan = text.childNodes[i] ||
                this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_tspan_" + i, "tspan");
            if (style.labelSelect === true) {
                tspan._featureId = featureId;
            }
            if (OpenLayers.IS_GECKO === false) {
                tspan.setAttributeNS(null, "baseline-shift",
                    OpenLayers.Renderer.SVG2.LABEL_VSHIFT[align[1]] || "-35%");
            }
            tspan.setAttribute("x", location.x / res);
            if (i == 0) {
                var vfactor = OpenLayers.Renderer.SVG2.LABEL_VFACTOR[align[1]];
                if (vfactor == null) {
                    vfactor = -.5;
                }
                tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
            } else {
                tspan.setAttribute("dy", "1em");
            }
            tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
            if (!tspan.parentNode) {
                text.appendChild(tspan);
            }
        }

        if (!text.parentNode) {
            g.appendChild(text);
        }

        return g;
    },

    /**
     * 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 len = components.length;
        var strings = new Array(len);
        for (var i=0; i<len; i++) {
            strings[i] = this.getShortString(components[i]);
        }

        return strings.join(separator || ",");
    },

    /**
     * Method: getShortString
     *
     * Parameters:
     * point - {<OpenLayers.Geometry.Point>}
     *
     * Returns:
     * {String} or false if point is outside the valid range
     */
    getShortString: function(point) {
        return point.x + "," + (-point.y);
    },

    /**
     * Method: importSymbol
     * add a new symbol definition from the rendererer's symbol hash
     *
     * Parameters:
     * graphicName - {String} name of the symbol to import
     *
     * Returns:
     * {DOMElement} - 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
        var existing = document.getElementById(id);
        if (existing != null) {
            return existing;
        }

        var symbol = OpenLayers.Renderer.symbol[graphicName];
        if (!symbol) {
            throw new Error(graphicName + ' is not a valid symbol name');
        }

        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, len=symbol.length; i<len; 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.push(x, ",", y);
        }

        node.setAttributeNS(null, "points", points.join(" "));

        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.symbolMetrics[id] = {
            size: Math.max(width, height),
            x: symbolExtent.getCenterLonLat().lon,
            y: symbolExtent.getCenterLonLat().lat
        };

        this.defs.appendChild(symbolNode);
        return symbolNode;
    },

    /**
     * Method: getFeatureIdFromEvent
     *
     * Parameters:
     * evt - {Object} An <OpenLayers.Event> object
     *
     * Returns:
     * {String} A feature id or undefined.
     */
    getFeatureIdFromEvent: function(evt) {
        var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
        if(!featureId) {
            var target = evt.target;
            featureId = target.parentNode && target != this.rendererRoot ?
                target.parentNode._featureId : undefined;
        }
        return featureId;
    },

    CLASS_NAME: "OpenLayers.Renderer.SVG2"
});

/**
 * Constant: OpenLayers.Renderer.SVG2.LABEL_ALIGN
 * {Object}
 */
OpenLayers.Renderer.SVG2.LABEL_ALIGN = {
    "l": "start",
    "r": "end",
    "b": "bottom",
    "t": "hanging"
};

/**
 * Constant: OpenLayers.Renderer.SVG2.LABEL_VSHIFT
 * {Object}
 */
OpenLayers.Renderer.SVG2.LABEL_VSHIFT = {
    // according to
    // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
    // a baseline-shift of -70% shifts the text exactly from the
    // bottom to the top of the baseline, so -35% moves the text to
    // the center of the baseline.
    "t": "-70%",
    "b": "0"
};

/**
 * Constant: OpenLayers.Renderer.SVG2.LABEL_VFACTOR
 * {Object}
 */
OpenLayers.Renderer.SVG2.LABEL_VFACTOR = {
    "t": 0,
    "b": -1
};

/**
 * Function: OpenLayers.Renderer.SVG2.preventDefault
 * Used to prevent default events (especially opening images in a new tab on
 * ctrl-click) from being executed for externalGraphic and graphicName symbols
 */
OpenLayers.Renderer.SVG2.preventDefault = function(e) {
    e.preventDefault && e.preventDefault();
};

/**
 * Class: OpenLayers.Popup.AnchoredBubble
 * This class is *deprecated*. Use {<OpenLayers.Popup.Anchored>} and
 * round corners using CSS3's border-radius property.
 *
 * 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/Protocol/WFS/v1_1_0.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */

/**
 * @requires OpenLayers/Protocol/WFS/v1.js
 * @requires OpenLayers/Format/WFST/v1_1_0.js
 */

/**
 * Class: OpenLayers.Protocol.WFS.v1_1_0
 * A WFS v1.1.0 protocol for vector layers.  Create a new instance with the
 *     <OpenLayers.Protocol.WFS.v1_1_0> constructor.
 *
 * Differences from the v1.0.0 protocol:
 *  - uses Filter Encoding 1.1.0 instead of 1.0.0
 *  - uses GML 3 instead of 2 if no format is provided
 *  
 * Inherits from:
 *  - <OpenLayers.Protocol.WFS.v1>
 */
OpenLayers.Protocol.WFS.v1_1_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
    
    /**
     * Property: version
     * {String} WFS version number.
     */
    version: "1.1.0",
    
    /**
     * Constructor: OpenLayers.Protocol.WFS.v1_1_0
     * A class for giving layers WFS v1.1.0 protocol.
     *
     * Parameters:
     * options - {Object} Optional object whose properties will be set on the
     *     instance.
     *
     * Valid options properties:
     * featureType - {String} Local (without prefix) feature typeName (required).
     * featureNS - {String} Feature namespace (optional).
     * featurePrefix - {String} Feature namespace alias (optional - only used
     *     if featureNS is provided).  Default is 'feature'.
     * geometryName - {String} Name of geometry attribute.  Default is 'the_geom'.
     * outputFormat - {String} Optional output format to use for WFS GetFeature
     *     requests. This can be any format advertized by the WFS's
     *     GetCapabilities response. If set, an appropriate readFormat also
     *     has to be provided, unless outputFormat is GML3, GML2 or JSON.
     * readFormat - {<OpenLayers.Format>} An appropriate format parser if
     *     outputFormat is none of GML3, GML2 or JSON.
     */
    initialize: function(options) {
        OpenLayers.Protocol.WFS.v1.prototype.initialize.apply(this, arguments);
        if (this.outputFormat && !this.readFormat) {
            if (this.outputFormat.toLowerCase() == "gml2") {
                this.readFormat = new OpenLayers.Format.GML.v2({
                    featureType: this.featureType,
                    featureNS: this.featureNS,
                    geometryName: this.geometryName
                });
            } else if (this.outputFormat.toLowerCase() == "json") {
                this.readFormat = new OpenLayers.Format.GeoJSON();
            }
        }
    },
   
    CLASS_NAME: "OpenLayers.Protocol.WFS.v1_1_0"
});
/* ======================================================================
    OpenLayers/Control/ArgParser.js
   ====================================================================== */

/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the 2-clause BSD license.
 * See license.txt in the OpenLayers distribution or repository for the
 * full text of the license. */


/**
 * @requires OpenLayers/Control.js
 */

/**
 * Class: OpenLayers.Control.ArgParser
 * The ArgParser control adds location bar query string parsing functionality 
 * to an OpenLayers Map.
 * When added to a Map control, on a page load/refresh, the Map will 
 * automatically take the href string and parse it for lon, lat, zoom, and 
 * layers information. 
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.ArgParser = OpenLayers.Class(OpenLayers.Control, {

    /**
     * Property: center
     * {<OpenLayers.LonLat>}
     */
    center: null,
    
    /**
     * Property: zoom
     * {int}
     */
    zoom: null,

    /**
     * Property: layers
     * {String} Each character represents the state of the corresponding layer 
     *     on the map.
     */
    layers: null,
    
    /** 
     * APIProperty: displayProjection
     * {<OpenLayers.Projection>} Requires proj4js support. 
     *     Projection used when reading the coordinates from the URL. This will
     *     reproject the map coordinates from the URL into the map's
     *     projection.
     *
     *     If you are using this functionality, be aware that any permalink
     *     which is 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.ArgParser
     *
     * Parameters:
     * options - {Object}
     */

    /**
     * Method: getParameters
     */    
    getParameters: function(url) {
        url = url || window.location.href;
        var parameters = OpenLayers.Util.getParameters(url);

        // If we have an anchor in the url use it to split the url
        var index = url.indexOf('#');
        if (index > 0) {
            // create an url to parse on the getParameters
            url = '?' + url.substring(index + 1, url.length);

            OpenLayers.Util.extend(parameters,
                    OpenLayers.Util.getParameters(url));
        }
        return parameters;
    },
    
    /**
     * 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 dont already 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 != this) &&
                 (control.CLASS_NAME == "OpenLayers.Control.ArgParser") ) {
                
                // If a second argparser is added to the map, then we 
                // override the displayProjection to be the one added to the
                // map. 
                if (control.displayProjection != this.displayProjection) {
                    this.displayProjection = control.displayProjection;
                }    
                
                break;
            }
        }
        if (i == this.map.controls.length) {

            var args = this.getParameters();
            // Be careful to set layer first, to not trigger unnecessary layer loads
            if (args.layers) {
                this.layers = args.layers;
    
                // when we add a new layer, set its visibility 
                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 = parseFloat(args.zoom);
                }
    
                // when we add a new baselayer to see when we can set the center
                this.map.events.register('changebaselayer', this, 
                                         this.setCenter);
                this.setCenter();
            }
        }
    },
   
    /** 
     * Method: setCenter
     * As soon as a baseLayer has been loaded, we center and zoom
     *   ...and remove the handler.
     */
    setCenter: function() {
        
        if (this.map.baseLayer) {
            //dont need to listen for this one anymore
            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);
        }
    },

    /** 
     * Method: configureLayers
     * As soon as all the layers are loaded, cycle through them and 
     *   hide or show them. 
     */
    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"
});
/*
  proj4js.js -- Javascript reprojection library. 
  
  Authors:      Mike Adair madairATdmsolutions.ca
                Richard Greenwood richATgreenwoodmap.com
                Didier Richard didier.richardATign.fr
                Stephen Irons
  License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html 
                Note: This program is an almost direct port of the C library
                Proj4.
*/
/* ======================================================================
    proj4js.js
   ====================================================================== */

/*
Author:       Mike Adair madairATdmsolutions.ca
              Richard Greenwood rich@greenwoodmap.com
License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html

$Id: Proj.js 2956 2007-07-09 12:17:52Z steven $
*/

/**
 * Namespace: Proj4js
 *
 * Proj4js is a JavaScript library to transform point coordinates from one 
 * coordinate system to another, including datum transformations.
 *
 * This library is a port of both the Proj.4 and GCTCP C libraries to JavaScript. 
 * Enabling these transformations in the browser allows geographic data stored 
 * in different projections to be combined in browser-based web mapping 
 * applications.
 * 
 * Proj4js must have access to coordinate system initialization strings (which
 * are the same as for PROJ.4 command line).  Thes can be included in your 
 * application using a <script> tag or Proj4js can load CS initialization 
 * strings from a local directory or a web service such as spatialreference.org.
 *
 * Similarly, Proj4js must have access to projection transform code.  These can
 * be included individually using a <script> tag in your page, built into a 
 * custom build of Proj4js or loaded dynamically at run-time.  Using the
 * -combined and -compressed versions of Proj4js includes all projection class
 * code by default.
 *
 * Note that dynamic loading of defs and code happens ascynchrously, check the
 * Proj.readyToUse flag before using the Proj object.  If the defs and code
 * required by your application are loaded through script tags, dynamic loading
 * is not required and the Proj object will be readyToUse on return from the 
 * constructor.
 * 
 * All coordinates are handled as points which have a .x and a .y property
 * which will be modified in place.
 *
 * Override Proj4js.reportError for output of alerts and warnings.
 *
 * See http://trac.osgeo.org/proj4js/wiki/UserGuide for full details.
*/

/**
 * Global namespace object for Proj4js library
 */
Proj4js = {

    /**
     * Property: defaultDatum
     * The datum to use when no others a specified
     */
    defaultDatum: 'WGS84',                  //default datum

    /** 
    * Method: transform(source, dest, point)
    * Transform a point coordinate from one map projection to another.  This is
    * really the only public method you should need to use.
    *
    * Parameters:
    * source - {Proj4js.Proj} source map projection for the transformation
    * dest - {Proj4js.Proj} destination map projection for the transformation
    * point - {Object} point to transform, may be geodetic (long, lat) or
    *     projected Cartesian (x,y), but should always have x,y properties.
    */
    transform: function(source, dest, point) {
        if (!source.readyToUse) {
            this.reportError("Proj4js initialization for:"+source.srsCode+" not yet complete");
            return point;
        }
        if (!dest.readyToUse) {
            this.reportError("Proj4js initialization for:"+dest.srsCode+" not yet complete");
            return point;
        }
        
        // Workaround for Spherical Mercator
        if ((source.srsProjNumber =="900913" && dest.datumCode != "WGS84" && !dest.datum_params) ||
            (dest.srsProjNumber == "900913" && source.datumCode != "WGS84" && !source.datum_params)) {
            var wgs84 = Proj4js.WGS84;
            this.transform(source, wgs84, point);
            source = wgs84;
        }

        // DGR, 2010/11/12
        if (source.axis!="enu") {
            this.adjust_axis(source,false,point);
        }

        // Transform source points to long/lat, if they aren't already.
        if ( source.projName=="longlat") {
            point.x *= Proj4js.common.D2R;  // convert degrees to radians
            point.y *= Proj4js.common.D2R;
        } else {
            if (source.to_meter) {
                point.x *= source.to_meter;
                point.y *= source.to_meter;
            }
            source.inverse(point); // Convert Cartesian to longlat
        }

        // Adjust for the prime meridian if necessary
        if (source.from_greenwich) { 
            point.x += source.from_greenwich; 
        }

        // Convert datums if needed, and if possible.
        point = this.datum_transform( source.datum, dest.datum, point );

        // Adjust for the prime meridian if necessary
        if (dest.from_greenwich) {
            point.x -= dest.from_greenwich;
        }

        if( dest.projName=="longlat" ) {             
            // convert radians to decimal degrees
            point.x *= Proj4js.common.R2D;
            point.y *= Proj4js.common.R2D;
        } else  {               // else project
            dest.forward(point);
            if (dest.to_meter) {
                point.x /= dest.to_meter;
                point.y /= dest.to_meter;
            }
        }

        // DGR, 2010/11/12
        if (dest.axis!="enu") {
            this.adjust_axis(dest,true,point);
        }

        return point;
    }, // transform()

    /** datum_transform()
      source coordinate system definition,
      destination coordinate system definition,
      point to transform in geodetic coordinates (long, lat, height)
    */
    datum_transform : function( source, dest, point ) {

      // Short cut if the datums are identical.
      if( source.compare_datums( dest ) ) {
          return point; // in this case, zero is sucess,
                    // whereas cs_compare_datums returns 1 to indicate TRUE
                    // confusing, should fix this
      }

      // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest
      if( source.datum_type == Proj4js.common.PJD_NODATUM
          || dest.datum_type == Proj4js.common.PJD_NODATUM) {
          return point;
      }

      // If this datum requires grid shifts, then apply it to geodetic coordinates.
      if( source.datum_type == Proj4js.common.PJD_GRIDSHIFT )
      {
        alert("ERROR: Grid shift transformations are not implemented yet.");
        /*
          pj_apply_gridshift( pj_param(source.params,"snadgrids").s, 0,
                              point_count, point_offset, x, y, z );
          CHECK_RETURN;

          src_a = SRS_WGS84_SEMIMAJOR;
          src_es = 0.006694379990;
        */
      }

      if( dest.datum_type == Proj4js.common.PJD_GRIDSHIFT )
      {
        alert("ERROR: Grid shift transformations are not implemented yet.");
        /*
          dst_a = ;
          dst_es = 0.006694379990;
        */
      }

      // Do we need to go through geocentric coordinates?
      if( source.es != dest.es || source.a != dest.a
          || source.datum_type == Proj4js.common.PJD_3PARAM
          || source.datum_type == Proj4js.common.PJD_7PARAM
          || dest.datum_type == Proj4js.common.PJD_3PARAM
          || dest.datum_type == Proj4js.common.PJD_7PARAM)
      {

        // Convert to geocentric coordinates.
        source.geodetic_to_geocentric( point );
        // CHECK_RETURN;

        // Convert between datums
        if( source.datum_type == Proj4js.common.PJD_3PARAM || source.datum_type == Proj4js.common.PJD_7PARAM ) {
          source.geocentric_to_wgs84(point);
          // CHECK_RETURN;
        }

        if( dest.datum_type == Proj4js.common.PJD_3PARAM || dest.datum_type == Proj4js.common.PJD_7PARAM ) {
          dest.geocentric_from_wgs84(point);
          // CHECK_RETURN;
        }

        // Convert back to geodetic coordinates
        dest.geocentric_to_geodetic( point );
          // CHECK_RETURN;
      }

      // Apply grid shift to destination if required
      if( dest.datum_type == Proj4js.common.PJD_GRIDSHIFT )
      {
        alert("ERROR: Grid shift transformations are not implemented yet.");
        // pj_apply_gridshift( pj_param(dest.params,"snadgrids").s, 1, point);
        // CHECK_RETURN;
      }
      return point;
    }, // cs_datum_transform

    /**
     * Function: adjust_axis
     * Normalize or de-normalized the x/y/z axes.  The normal form is "enu"
     * (easting, northing, up).
     * Parameters:
     * crs {Proj4js.Proj} the coordinate reference system
     * denorm {Boolean} when false, normalize
     * point {Object} the coordinates to adjust
     */
    adjust_axis: function(crs, denorm, point) {
        var xin= point.x, yin= point.y, zin= point.z || 0.0;
        var v, t;
        for (var i= 0; i<3; i++) {
            if (denorm && i==2 && point.z===undefined) { continue; }
                 if (i==0) { v= xin; t= 'x'; }
            else if (i==1) { v= yin; t= 'y'; }
            else           { v= zin; t= 'z'; }
            switch(crs.axis[i]) {
            case 'e':
                point[t]= v;
                break;
            case 'w':
                point[t]= -v;
                break;
            case 'n':
                point[t]= v;
                break;
            case 's':
                point[t]= -v;
                break;
            case 'u':
                if (point[t]!==undefined) { point.z= v; }
                break;
            case 'd':
                if (point[t]!==undefined) { point.z= -v; }
                break;
            default :
                alert("ERROR: unknow axis ("+crs.axis[i]+") - check definition of "+src.projName);
                return null;
            }
        }
        return point;
    },

    /**
     * Function: reportError
     * An internal method to report errors back to user. 
     * Override this in applications to report error messages or throw exceptions.
     */
    reportError: function(msg) {
      //console.log(msg);
    },

/**
 *
 * Title: Private Methods
 * The following properties and methods are intended for internal use only.
 *
 * This is a minimal implementation of JavaScript inheritance methods so that 
 * Proj4js can be used as a stand-alone library.
 * These are copies of the equivalent OpenLayers methods at v2.7
 */
 
/**
 * Function: extend
 * Copy all properties of a source object to a destination object.  Modifies
 *     the passed in destination object.  Any properties on the source object
 *     that are set to undefined will not be (re)set on the destination object.
 *
 * Parameters:
 * destination - {Object} The object that will be modified
 * source - {Object} The object with properties to be set on the destination
 *
 * Returns:
 * {Object} The destination object.
 */
    extend: function(destination, source) {
      destination = destination || {};
      if(source) {
          for(var property in source) {
              var value = source[property];
              if(value !== undefined) {
                  destination[property] = value;
              }
          }
      }
      return destination;
    },

/**
 * Constructor: Class
 * Base class used to construct all other classes. Includes support for 
 *     multiple inheritance. 
 *  
 */
    Class: function() {
      var Class = function() {
          this.initialize.apply(this, arguments);
      };
  
      var extended = {};
      var parent;
      for(var i=0; i<arguments.length; ++i) {
          if(typeof arguments[i] == "function") {
              // get the prototype of the superclass
              parent = arguments[i].prototype;
          } else {
              // in this case we're extending with the prototype
              parent = arguments[i];
          }
          Proj4js.extend(extended, parent);
      }
      Class.prototype = extended;
      
      return Class;
    },

    /**
     * Function: bind
     * Bind a function to an object.  Method to easily create closures with
     *     'this' altered.
     * 
     * Parameters:
     * func - {Function} Input function.
     * object - {Object} The object to bind to the input function (as this).
     * 
     * Returns:
     * {Function} A closure with 'this' set to the passed in object.
     */
    bind: function(func, object) {
        // create a reference to all arguments past the second one
        var args = Array.prototype.slice.apply(arguments, [2]);
        return function() {
            // Push on any additional arguments from the actual function call.
            // These will come after those sent to the bind call.
            var newArgs = args.concat(
                Array.prototype.slice.apply(arguments, [0])
            );
            return func.apply(object, newArgs);
        };
    },
    
/**
 * The following properties and methods handle dynamic loading of JSON objects.
 */
 
    /**
     * Property: scriptName
     * {String} The filename of this script without any path.
     */
    scriptName: "proj4js-combined.js",

    /**
     * Property: defsLookupService
     * AJAX service to retreive projection definition parameters from
     */
    defsLookupService: 'http://spatialreference.org/ref',

    /**
     * Property: libPath
     * internal: http server path to library code.
     */
    libPath: null,

    /**
     * Function: getScriptLocation
     * Return the path to this script.
     *
     * Returns:
     * Path to this script
     */
    getScriptLocation: function () {
        if (this.libPath) return this.libPath;
        var scriptName = this.scriptName;
        var scriptNameLen = scriptName.length;

        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);
                // is it found, at the end of the URL?
                if ((index > -1) && (index + scriptNameLen == src.length)) {
                    this.libPath = src.slice(0, -scriptNameLen);
                    break;
                }
            }
        }
        return this.libPath||"";
    },

    /**
     * Function: loadScript
     * Load a JS file from a URL into a <script> tag in the page.
     * 
     * Parameters:
     * url - {String} The URL containing the script to load
     * onload - {Function} A method to be executed when the script loads successfully
     * onfail - {Function} A method to be executed when there is an error loading the script
     * loadCheck - {Function} A boolean method that checks to see if the script 
     *            has loaded.  Typically this just checks for the existance of
     *            an object in the file just loaded.
     */
    loadScript: function(url, onload, onfail, loadCheck) {
      var script = document.createElement('script');
      script.defer = false;
      script.type = "text/javascript";
      script.id = url;
      script.src = url;
      script.onload = onload;
      script.onerror = onfail;
      script.loadCheck = loadCheck;
      if (/MSIE/.test(navigator.userAgent)) {
        script.onreadystatechange = this.checkReadyState;
      }
      document.getElementsByTagName('head')[0].appendChild(script);
    },
    
    /**
     * Function: checkReadyState
     * IE workaround since there is no onerror handler.  Calls the user defined 
     * loadCheck method to determine if the script is loaded.
     * 
     */
    checkReadyState: function() {
      if (this.readyState == 'loaded') {
        if (!this.loadCheck()) {
          this.onerror();
        } else {
          this.onload();
        }
      }
    }
};

/**
 * Class: Proj4js.Proj
 *
 * Proj objects provide transformation methods for point coordinates
 * between geodetic latitude/longitude and a projected coordinate system. 
 * once they have been initialized with a projection code.
 *
 * Initialization of Proj objects is with a projection code, usually EPSG codes,
 * which is the key that will be used with the Proj4js.defs array.
 * 
 * The code passed in will be stripped of colons and converted to uppercase
 * to locate projection definition files.
 *
 * A projection object has properties for units and title strings.
 */
Proj4js.Proj = Proj4js.Class({

  /**
   * Property: readyToUse
   * Flag to indicate if initialization is complete for this Proj object
   */
  readyToUse: false,   
  
  /**
   * Property: title
   * The title to describe the projection
   */
  title: null,  
  
  /**
   * Property: projName
   * The projection class for this projection, e.g. lcc (lambert conformal conic,
   * or merc for mercator).  These are exactly equivalent to their Proj4 
   * counterparts.
   */
  projName: null,
  /**
   * Property: units
   * The units of the projection.  Values include 'm' and 'degrees'
   */
  units: null,
  /**
   * Property: datum
   * The datum specified for the projection
   */
  datum: null,
  /**
   * Property: x0
   * The x coordinate origin
   */
  x0: 0,
  /**
   * Property: y0
   * The y coordinate origin
   */
  y0: 0,
  /**
   * Property: localCS
   * Flag to indicate if the projection is a local one in which no transforms
   * are required.
   */
  localCS: false,

  /**
  * Property: queue
  * Buffer (FIFO) to hold callbacks waiting to be called when projection loaded.
  */
  queue: null,

  /**
  * Constructor: initialize
  * Constructor for Proj4js.Proj objects
  *
  * Parameters:
  * srsCode - a code for map projection definition parameters.  These are usually
  * (but not always) EPSG codes.
  */
  initialize: function(srsCode, callback) {
      this.srsCodeInput = srsCode;
      
      //Register callbacks prior to attempting to process definition
      this.queue = [];
      if( callback ){
           this.queue.push( callback );
      }
      
      //check to see if this is a WKT string
      if ((srsCode.indexOf('GEOGCS') >= 0) ||
          (srsCode.indexOf('GEOCCS') >= 0) ||
          (srsCode.indexOf('PROJCS') >= 0) ||
          (srsCode.indexOf('LOCAL_CS') >= 0)) {
            this.parseWKT(srsCode);
            this.deriveConstants();
            this.loadProjCode(this.projName);
            return;
      }
      
      // DGR 2008-08-03 : support urn and url
      if (srsCode.indexOf('urn:') == 0) {
          //urn:ORIGINATOR:def:crs:CODESPACE:VERSION:ID
          var urn = srsCode.split(':');
          if ((urn[1] == 'ogc' || urn[1] =='x-ogc') &&
              (urn[2] =='def') &&
              (urn[3] =='crs')) {
              srsCode = urn[4]+':'+urn[urn.length-1];
          }
      } else if (srsCode.indexOf('http://') == 0) {
          //url#ID
          var url = srsCode.split('#');
          if (url[0].match(/epsg.org/)) {
            // http://www.epsg.org/#
            srsCode = 'EPSG:'+url[1];
          } else if (url[0].match(/RIG.xml/)) {
            //http://librairies.ign.fr/geoportail/resources/RIG.xml#
            //http://interop.ign.fr/registers/ign/RIG.xml#
            srsCode = 'IGNF:'+url[1];
          }
      }
      this.srsCode = srsCode.toUpperCase();
      if (this.srsCode.indexOf("EPSG") == 0) {
          this.srsCode = this.srsCode;
          this.srsAuth = 'epsg';
          this.srsProjNumber = this.srsCode.substring(5);
      // DGR 2007-11-20 : authority IGNF
      } else if (this.srsCode.indexOf("IGNF") == 0) {
          this.srsCode = this.srsCode;
          this.srsAuth = 'IGNF';
          this.srsProjNumber = this.srsCode.substring(5);
      // DGR 2008-06-19 : pseudo-authority CRS for WMS
      } else if (this.srsCode.indexOf("CRS") == 0) {
          this.srsCode = this.srsCode;
          this.srsAuth = 'CRS';
          this.srsProjNumber = this.srsCode.substring(4);
      } else {
          this.srsAuth = '';
          this.srsProjNumber = this.srsCode;
      }
      
      this.loadProjDefinition();
  },
  
/**
 * Function: loadProjDefinition
 *    Loads the coordinate system initialization string if required.
 *    Note that dynamic loading happens asynchronously so an application must 
 *    wait for the readyToUse property is set to true.
 *    To prevent dynamic loading, include the defs through a script tag in
 *    your application.
 *
 */
    loadProjDefinition: function() {
      //check in memory
      if (Proj4js.defs[this.srsCode]) {
        this.defsLoaded();
        return;
      }

      //else check for def on the server
      var url = Proj4js.getScriptLocation() + 'defs/' + this.srsAuth.toUpperCase() + this.srsProjNumber + '.js';
      Proj4js.loadScript(url, 
                Proj4js.bind(this.defsLoaded, this),
                Proj4js.bind(this.loadFromService, this),
                Proj4js.bind(this.checkDefsLoaded, this) );
    },

/**
 * Function: loadFromService
 *    Creates the REST URL for loading the definition from a web service and 
 *    loads it.
 *
 */
    loadFromService: function() {
      //else load from web service
      var url = Proj4js.defsLookupService +'/' + this.srsAuth +'/'+ this.srsProjNumber + '/proj4js/';
      Proj4js.loadScript(url, 
            Proj4js.bind(this.defsLoaded, this),
            Proj4js.bind(this.defsFailed, this),
            Proj4js.bind(this.checkDefsLoaded, this) );
    },

/**
 * Function: defsLoaded
 * Continues the Proj object initilization once the def file is loaded
 *
 */
    defsLoaded: function() {
      this.parseDefs();
      this.loadProjCode(this.projName);
    },
    
/**
 * Function: checkDefsLoaded
 *    This is the loadCheck method to see if the def object exists
 *
 */
    checkDefsLoaded: function() {
      if (Proj4js.defs[this.srsCode]) {
        return true;
      } else {
        return false;
      }
    },

 /**
 * Function: defsFailed
 *    Report an error in loading the defs file, but continue on using WGS84
 *
 */
   defsFailed: function() {
      Proj4js.reportError('failed to load projection definition for: '+this.srsCode);
      Proj4js.defs[this.srsCode] = Proj4js.defs['WGS84'];  //set it to something so it can at least continue
      this.defsLoaded();
    },

/**
 * Function: loadProjCode
 *    Loads projection class code dynamically if required.
 *     Projection code may be included either through a script tag or in
 *     a built version of proj4js
 *
 */
    loadProjCode: function(projName) {
      if (Proj4js.Proj[projName]) {
        this.initTransforms();
        return;
      }

      //the URL for the projection code
      var url = Proj4js.getScriptLocation() + 'projCode/' + projName + '.js';
      Proj4js.loadScript(url, 
              Proj4js.bind(this.loadProjCodeSuccess, this, projName),
              Proj4js.bind(this.loadProjCodeFailure, this, projName), 
              Proj4js.bind(this.checkCodeLoaded, this, projName) );
    },

 /**
 * Function: loadProjCodeSuccess
 *    Loads any proj dependencies or continue on to final initialization.
 *
 */
    loadProjCodeSuccess: function(projName) {
      if (Proj4js.Proj[projName].dependsOn){
        this.loadProjCode(Proj4js.Proj[projName].dependsOn);
      } else {
        this.initTransforms();
      }
    },

 /**
 * Function: defsFailed
 *    Report an error in loading the proj file.  Initialization of the Proj
 *    object has failed and the readyToUse flag will never be set.
 *
 */
    loadProjCodeFailure: function(projName) {
      Proj4js.reportError("failed to find projection file for: " + projName);
      //TBD initialize with identity transforms so proj will still work?
    },
    
/**
 * Function: checkCodeLoaded
 *    This is the loadCheck method to see if the projection code is loaded
 *
 */
    checkCodeLoaded: function(projName) {
      if (Proj4js.Proj[projName]) {
        return true;
      } else {
        return false;
      }
    },

/**
 * Function: initTransforms
 *    Finalize the initialization of the Proj object
 *
 */
    initTransforms: function() {
      Proj4js.extend(this, Proj4js.Proj[this.projName]);
      this.init();
      this.readyToUse = true;
      if( this.queue ) {
        var item;
        while( (item = this.queue.shift()) ) {
          item.call( this, this );
        }
      }
  },

/**
 * Function: parseWKT
 * Parses a WKT string to get initialization parameters
 *
 */
 wktRE: /^(\w+)\[(.*)\]$/,
 parseWKT: function(wkt) {
    var wktMatch = wkt.match(this.wktRE);
    if (!wktMatch) return;
    var wktObject = wktMatch[1];
    var wktContent = wktMatch[2];
    var wktTemp = wktContent.split(",");
    var wktName;
    if (wktObject.toUpperCase() == "TOWGS84") {
      wktName = wktObject;  //no name supplied for the TOWGS84 array
    } else {
      wktName = wktTemp.shift();
    }
    wktName = wktName.replace(/^\"/,"");
    wktName = wktName.replace(/\"$/,"");
    
    /*
    wktContent = wktTemp.join(",");
    var wktArray = wktContent.split("],");
    for (var i=0; i<wktArray.length-1; ++i) {
      wktArray[i] += "]";
    }
    */
    
    var wktArray = new Array();
    var bkCount = 0;
    var obj = "";
    for (var i=0; i<wktTemp.length; ++i) {
      var token = wktTemp[i];
      for (var j=0; j<token.length; ++j) {
        if (token.charAt(j) == "[") ++bkCount;
        if (token.charAt(j) == "]") --bkCount;
      }
      obj += token;
      if (bkCount === 0) {
        wktArray.push(obj);
        obj = "";
      } else {
        obj += ",";
      }
    }
    
    //do something based on the type of the wktObject being parsed
    //add in variations in the spelling as required
    switch (wktObject) {
      case 'LOCAL_CS':
        this.projName = 'identity'
        this.localCS = true;
        this.srsCode = wktName;
        break;
      case 'GEOGCS':
        this.projName = 'longlat'
        this.geocsCode = wktName;
        if (!this.srsCode) this.srsCode = wktName;
        break;
      case 'PROJCS':
        this.srsCode = wktName;
        break;
      case 'GEOCCS':
        break;
      case 'PROJECTION':
        this.projName = Proj4js.wktProjections[wktName]
        break;
      case 'DATUM':
        this.datumName = wktName;
        break;
      case 'LOCAL_DATUM':
        this.datumCode = 'none';
        break;
      case 'SPHEROID':
        this.ellps = wktName;
        this.a = parseFloat(wktArray.shift());
        this.rf = parseFloat(wktArray.shift());
        break;
      case 'PRIMEM':
        this.from_greenwich = parseFloat(wktArray.shift()); //to radians?
        break;
      case 'UNIT':
        this.units = wktName;
        this.unitsPerMeter = parseFloat(wktArray.shift());
        break;
      case 'PARAMETER':
        var name = wktName.toLowerCase();
        var value = parseFloat(wktArray.shift());
        //there may be many variations on the wktName values, add in case
        //statements as required
        switch (name) {
          case 'false_easting':
            this.x0 = value;
            break;
          case 'false_northing':
            this.y0 = value;
            break;
          case 'scale_factor':
            this.k0 = value;
            break;
          case 'central_meridian':
            this.long0 = value*Proj4js.common.D2R;
            break;
          case 'latitude_of_origin':
            this.lat0 = value*Proj4js.common.D2R;
            break;
          case 'more_here':
            break;
          default:
            break;
        }
        break;
      case 'TOWGS84':
        this.datum_params = wktArray;
        break;
      //DGR 2010-11-12: AXIS
      case 'AXIS':
        var name= wktName.toLowerCase();
        var value= wktArray.shift();
        switch (value) {
          case 'EAST' : value= 'e'; break;
          case 'WEST' : value= 'w'; break;
          case 'NORTH': value= 'n'; break;
          case 'SOUTH': value= 's'; break;
          case 'UP'   : value= 'u'; break;
          case 'DOWN' : value= 'd'; break;
          case 'OTHER':
          default     : value= ' '; break;//FIXME
        }
        if (!this.axis) { this.axis= "enu"; }
        switch(name) {
          case 'x': this.axis=                         value + this.axis.substr(1,2); break;
          case 'y': this.axis= this.axis.substr(0,1) + value + this.axis.substr(2,1); break;
          case 'z': this.axis= this.axis.substr(0,2) + value                        ; break;
          default : break;
        }
      case 'MORE_HERE':
        break;
      default:
        break;
    }
    for (var i=0; i<wktArray.length; ++i) {
      this.parseWKT(wktArray[i]);
    }
 },

/**
 * Function: parseDefs
 * Parses the PROJ.4 initialization string and sets the associated properties.
 *
 */
  parseDefs: function() {
      this.defData = Proj4js.defs[this.srsCode];
      var paramName, paramVal;
      if (!this.defData) {
        return;
      }
      var paramArray=this.defData.split("+");

      for (var prop=0; prop<paramArray.length; prop++) {
          var property = paramArray[prop].split("=");
          paramName = property[0].toLowerCase();
          paramVal = property[1];

          switch (paramName.replace(/\s/gi,"")) {  // trim out spaces
              case "": break;   // throw away nameless parameter
              case "title":  this.title = paramVal; break;
              case "proj":   this.projName =  paramVal.replace(/\s/gi,""); break;
              case "units":  this.units = paramVal.replace(/\s/gi,""); break;
              case "datum":  this.datumCode = paramVal.replace(/\s/gi,""); break;
              case "nadgrids": this.nagrids = paramVal.replace(/\s/gi,""); break;
              case "ellps":  this.ellps = paramVal.replace(/\s/gi,""); break;
              case "a":      this.a =  parseFloat(paramVal); break;  // semi-major radius
              case "b":      this.b =  parseFloat(paramVal); break;  // semi-minor radius
              // DGR 2007-11-20
              case "rf":     this.rf = parseFloat(paramVal); break; // inverse flattening rf= a/(a-b)
              case "lat_0":  this.lat0 = paramVal*Proj4js.common.D2R; break;        // phi0, central latitude
              case "lat_1":  this.lat1 = paramVal*Proj4js.common.D2R; break;        //standard parallel 1
              case "lat_2":  this.lat2 = paramVal*Proj4js.common.D2R; break;        //standard parallel 2
              case "lat_ts": this.lat_ts = paramVal*Proj4js.common.D2R; break;      // used in merc and eqc
              case "lon_0":  this.long0 = paramVal*Proj4js.common.D2R; break;       // lam0, central longitude
              case "alpha":  this.alpha =  parseFloat(paramVal)*Proj4js.common.D2R; break;  //for somerc projection
              case "lonc":   this.longc = paramVal*Proj4js.common.D2R; break;       //for somerc projection
              case "x_0":    this.x0 = parseFloat(paramVal); break;  // false easting
              case "y_0":    this.y0 = parseFloat(paramVal); break;  // false northing
              case "k_0":    this.k0 = parseFloat(paramVal); break;  // projection scale factor
              case "k":      this.k0 = parseFloat(paramVal); break;  // both forms returned
              case "r_a":    this.R_A = true; break;                 // sphere--area of ellipsoid
              case "zone":   this.zone = parseInt(paramVal); break;  // UTM Zone
              case "south":   this.utmSouth = true; break;  // UTM north/south
              case "towgs84":this.datum_params = paramVal.split(","); break;
              case "to_meter": this.to_meter = parseFloat(paramVal); break; // cartesian scaling
              case "from_greenwich": this.from_greenwich = paramVal*Proj4js.common.D2R; break;
              // DGR 2008-07-09 : if pm is not a well-known prime meridian take
              // the value instead of 0.0, then convert to radians
              case "pm":     paramVal = paramVal.replace(/\s/gi,"");
                             this.from_greenwich = Proj4js.PrimeMeridian[paramVal] ?
                                Proj4js.PrimeMeridian[paramVal] : parseFloat(paramVal);
                             this.from_greenwich *= Proj4js.common.D2R; 
                             break;
              // DGR 2010-11-12: axis
              case "axis":   paramVal = paramVal.replace(/\s/gi,"");
                             var legalAxis= "ewnsud";
                             if (paramVal.length==3 &&
                                 legalAxis.indexOf(paramVal.substr(0,1))!=-1 &&
                                 legalAxis.indexOf(paramVal.substr(1,1))!=-1 &&
                                 legalAxis.indexOf(paramVal.substr(2,1))!=-1) {
                                this.axis= paramVal;
                             } //FIXME: be silent ?
                             break
              case "no_defs": break; 
              default: //alert("Unrecognized parameter: " + paramName);
          } // switch()
      } // for paramArray
      this.deriveConstants();
  },

/**
 * Function: deriveConstants
 * Sets several derived constant values and initialization of datum and ellipse
 *     parameters.
 *
 */
  deriveConstants: function() {
      if (this.nagrids == '@null') this.datumCode = 'none';
      if (this.datumCode && this.datumCode != 'none') {
        var datumDef = Proj4js.Datum[this.datumCode];
        if (datumDef) {
          this.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
          this.ellps = datumDef.ellipse;
          this.datumName = datumDef.datumName ? datumDef.datumName : this.datumCode;
        }
      }
      if (!this.a) {    // do we have an ellipsoid?
          var ellipse = Proj4js.Ellipsoid[this.ellps] ? Proj4js.Ellipsoid[this.ellps] : Proj4js.Ellipsoid['WGS84'];
          Proj4js.extend(this, ellipse);
      }
      if (this.rf && !this.b) this.b = (1.0 - 1.0/this.rf) * this.a;
      if (this.rf === 0 || Math.abs(this.a - this.b)<Proj4js.common.EPSLN) {
        this.sphere = true;
        this.b= this.a;
      }
      this.a2 = this.a * this.a;          // used in geocentric
      this.b2 = this.b * this.b;          // used in geocentric
      this.es = (this.a2-this.b2)/this.a2;  // e ^ 2
      this.e = Math.sqrt(this.es);        // eccentricity
      if (this.R_A) {
        this.a *= 1. - this.es * (Proj4js.common.SIXTH + this.es * (Proj4js.common.RA4 + this.es * Proj4js.common.RA6));
        this.a2 = this.a * this.a;
        this.b2 = this.b * this.b;
        this.es = 0.;
      }
      this.ep2=(this.a2-this.b2)/this.b2; // used in geocentric
      if (!this.k0) this.k0 = 1.0;    //default value
      //DGR 2010-11-12: axis
      if (!this.axis) { this.axis= "enu"; }

      this.datum = new Proj4js.datum(this);
  }
});

Proj4js.Proj.longlat = {
  init: function() {
    //no-op for longlat
  },
  forward: function(pt) {
    //identity transform
    return pt;
  },
  inverse: function(pt) {
    //identity transform
    return pt;
  }
};
Proj4js.Proj.identity = Proj4js.Proj.longlat;

/**
  Proj4js.defs is a collection of coordinate system definition objects in the 
  PROJ.4 command line format.
  Generally a def is added by means of a separate .js file for example:

    <SCRIPT type="text/javascript" src="defs/EPSG26912.js"></SCRIPT>

  def is a CS definition in PROJ.4 WKT format, for example:
    +proj="tmerc"   //longlat, etc.
    +a=majorRadius
    +b=minorRadius
    +lat0=somenumber
    +long=somenumber
*/
Proj4js.defs = {
  // These are so widely used, we'll go ahead and throw them in
  // without requiring a separate .js file
  'WGS84': "+title=long/lat:WGS84 +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees",
  'EPSG:4326': "+title=long/lat:WGS84 +proj=longlat +a=6378137.0 +b=6356752.31424518 +ellps=WGS84 +datum=WGS84 +units=degrees",
  'EPSG:4269': "+title=long/lat:NAD83 +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees",
  'EPSG:3875': "+title= Google Mercator +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"
};
Proj4js.defs['EPSG:3785'] = Proj4js.defs['EPSG:3875'];  //maintain backward compat, official code is 3875
Proj4js.defs['GOOGLE'] = Proj4js.defs['EPSG:3875'];
Proj4js.defs['EPSG:900913'] = Proj4js.defs['EPSG:3875'];
Proj4js.defs['EPSG:102113'] = Proj4js.defs['EPSG:3875'];
Proj4js.defs['EPSG:3857'] = Proj4js.defs['EPSG:3875'];

Proj4js.common = {
  PI : 3.141592653589793238, //Math.PI,
  HALF_PI : 1.570796326794896619, //Math.PI*0.5,
  TWO_PI : 6.283185307179586477, //Math.PI*2,
  FORTPI : 0.78539816339744833,
  R2D : 57.29577951308232088,
  D2R : 0.01745329251994329577,
  SEC_TO_RAD : 4.84813681109535993589914102357e-6, /* SEC_TO_RAD = Pi/180/3600 */
  EPSLN : 1.0e-10,
  MAX_ITER : 20,
  // following constants from geocent.c
  COS_67P5 : 0.38268343236508977,  /* cosine of 67.5 degrees */
  AD_C : 1.0026000,                /* Toms region 1 constant */

  /* datum_type values */
  PJD_UNKNOWN  : 0,
  PJD_3PARAM   : 1,
  PJD_7PARAM   : 2,
  PJD_GRIDSHIFT: 3,
  PJD_WGS84    : 4,   // WGS84 or equivalent
  PJD_NODATUM  : 5,   // WGS84 or equivalent
  SRS_WGS84_SEMIMAJOR : 6378137.0,  // only used in grid shift transforms

  // ellipoid pj_set_ell.c
  SIXTH : .1666666666666666667, /* 1/6 */
  RA4   : .04722222222222222222, /* 17/360 */
  RA6   : .02215608465608465608, /* 67/3024 */
  RV4   : .06944444444444444444, /* 5/72 */
  RV6   : .04243827160493827160, /* 55/1296 */

// Function to compute the constant small m which is the radius of
//   a parallel of latitude, phi, divided by the semimajor axis.
// -----------------------------------------------------------------
  msfnz : function(eccent, sinphi, cosphi) {
      var con = eccent * sinphi;
      return cosphi/(Math.sqrt(1.0 - con * con));
  },

// Function to compute the constant small t for use in the forward
//   computations in the Lambert Conformal Conic and the Polar
//   Stereographic projections.
// -----------------------------------------------------------------
  tsfnz : function(eccent, phi, sinphi) {
    var con = eccent * sinphi;
    var com = .5 * eccent;
    con = Math.pow(((1.0 - con) / (1.0 + con)), com);
    return (Math.tan(.5 * (this.HALF_PI - phi))/con);
  },

// Function to compute the latitude angle, phi2, for the inverse of the
//   Lambert Conformal Conic and Polar Stereographic projections.
// ----------------------------------------------------------------
  phi2z : function(eccent, ts) {
    var eccnth = .5 * eccent;
    var con, dphi;
    var phi = this.HALF_PI - 2 * Math.atan(ts);
    for (var i = 0; i <= 15; i++) {
      con = eccent * Math.sin(phi);
      dphi = this.HALF_PI - 2 * Math.atan(ts *(Math.pow(((1.0 - con)/(1.0 + con)),eccnth))) - phi;
      phi += dphi;
      if (Math.abs(dphi) <= .0000000001) return phi;
    }
    alert("phi2z has NoConvergence");
    return (-9999);
  },

/* Function to compute constant small q which is the radius of a 
   parallel of latitude, phi, divided by the semimajor axis. 
------------------------------------------------------------*/
  qsfnz : function(eccent,sinphi) {
    var con;
    if (eccent > 1.0e-7) {
      con = eccent * sinphi;
      return (( 1.0- eccent * eccent) * (sinphi /(1.0 - con * con) - (.5/eccent)*Math.log((1.0 - con)/(1.0 + con))));
    } else {
      return(2.0 * sinphi);
    }
  },

/* Function to eliminate roundoff errors in asin
----------------------------------------------*/
  asinz : function(x) {
    if (Math.abs(x)>1.0) {
      x=(x>1.0)?1.0:-1.0;
    }
    return Math.asin(x);
  },

// following functions from gctpc cproj.c for transverse mercator projections
  e0fn : function(x) {return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));},
  e1fn : function(x) {return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));},
  e2fn : function(x) {return(0.05859375*x*x*(1.0+0.75*x));},
  e3fn : function(x) {return(x*x*x*(35.0/3072.0));},
  mlfn : function(e0,e1,e2,e3,phi) {return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));},

  srat : function(esinp, exp) {
    return(Math.pow((1.0-esinp)/(1.0+esinp), exp));
  },

// Function to return the sign of an argument
  sign : function(x) { if (x < 0.0) return(-1); else return(1);},

// Function to adjust longitude to -180 to 180; input in radians
  adjust_lon : function(x) {
    x = (Math.abs(x) < this.PI) ? x: (x - (this.sign(x)*this.TWO_PI) );
    return x;
  },

// IGNF - DGR : algorithms used by IGN France

// Function to adjust latitude to -90 to 90; input in radians
  adjust_lat : function(x) {
    x= (Math.abs(x) < this.HALF_PI) ? x: (x - (this.sign(x)*this.PI) );
    return x;
  },

// Latitude Isometrique - close to tsfnz ...
  latiso : function(eccent, phi, sinphi) {
    if (Math.abs(phi) > this.HALF_PI) return +Number.NaN;
    if (phi==this.HALF_PI) return Number.POSITIVE_INFINITY;
    if (phi==-1.0*this.HALF_PI) return -1.0*Number.POSITIVE_INFINITY;

    var con= eccent*sinphi;
    return Math.log(Math.tan((this.HALF_PI+phi)/2.0))+eccent*Math.log((1.0-con)/(1.0+con))/2.0;
  },

  fL : function(x,L) {
    return 2.0*Math.atan(x*Math.exp(L)) - this.HALF_PI;
  },

// Inverse Latitude Isometrique - close to ph2z
  invlatiso : function(eccent, ts) {
    var phi= this.fL(1.0,ts);
    var Iphi= 0.0;
    var con= 0.0;
    do {
      Iphi= phi;
      con= eccent*Math.sin(Iphi);
      phi= this.fL(Math.exp(eccent*Math.log((1.0+con)/(1.0-con))/2.0),ts)
    } while (Math.abs(phi-Iphi)>1.0e-12);
    return phi;
  },

// Needed for Gauss Schreiber
// Original:  Denis Makarov (info@binarythings.com)
// Web Site:  http://www.binarythings.com
  sinh : function(x)
  {
    var r= Math.exp(x);
    r= (r-1.0/r)/2.0;
    return r;
  },

  cosh : function(x)
  {
    var r= Math.exp(x);
    r= (r+1.0/r)/2.0;
    return r;
  },

  tanh : function(x)
  {
    var r= Math.exp(x);
    r= (r-1.0/r)/(r+1.0/r);
    return r;
  },

  asinh : function(x)
  {
    var s= (x>= 0? 1.0:-1.0);
    return s*(Math.log( Math.abs(x) + Math.sqrt(x*x+1.0) ));
  },

  acosh : function(x)
  {
    return 2.0*Math.log(Math.sqrt((x+1.0)/2.0) + Math.sqrt((x-1.0)/2.0));
  },

  atanh : function(x)
  {
    return Math.log((x-1.0)/(x+1.0))/2.0;
  },

// Grande Normale
  gN : function(a,e,sinphi)
  {
    var temp= e*sinphi;
    return a/Math.sqrt(1.0 - temp*temp);
  }

};

/** datum object
*/
Proj4js.datum = Proj4js.Class({

  initialize : function(proj) {
    this.datum_type = Proj4js.common.PJD_WGS84;   //default setting
    if (proj.datumCode && proj.datumCode == 'none') {
      this.datum_type = Proj4js.common.PJD_NODATUM;
    }
    if (proj && proj.datum_params) {
      for (var i=0; i<proj.datum_params.length; i++) {
        proj.datum_params[i]=parseFloat(proj.datum_params[i]);
      }
      if (proj.datum_params[0] != 0 || proj.datum_params[1] != 0 || proj.datum_params[2] != 0 ) {
        this.datum_type = Proj4js.common.PJD_3PARAM;
      }
      if (proj.datum_params.length > 3) {
        if (proj.datum_params[3] != 0 || proj.datum_params[4] != 0 ||
            proj.datum_params[5] != 0 || proj.datum_params[6] != 0 ) {
          this.datum_type = Proj4js.common.PJD_7PARAM;
          proj.datum_params[3] *= Proj4js.common.SEC_TO_RAD;
          proj.datum_params[4] *= Proj4js.common.SEC_TO_RAD;
          proj.datum_params[5] *= Proj4js.common.SEC_TO_RAD;
          proj.datum_params[6] = (proj.datum_params[6]/1000000.0) + 1.0;
        }
      }
    }
    if (proj) {
      this.a = proj.a;    //datum object also uses these values
      this.b = proj.b;
      this.es = proj.es;
      this.ep2 = proj.ep2;
      this.datum_params = proj.datum_params;
    }
  },

  /****************************************************************/
  // cs_compare_datums()
  //   Returns 1 (TRUE) if the two datums match, otherwise 0 (FALSE).
  compare_datums : function( dest ) {
    if( this.datum_type != dest.datum_type ) {
      return false; // false, datums are not equal
    } else if( this.a != dest.a || Math.abs(this.es-dest.es) > 0.000000000050 ) {
      // the tolerence for es is to ensure that GRS80 and WGS84
      // are considered identical
      return false;
    } else if( this.datum_type == Proj4js.common.PJD_3PARAM ) {
      return (this.datum_params[0] == dest.datum_params[0]
              && this.datum_params[1] == dest.datum_params[1]
              && this.datum_params[2] == dest.datum_params[2]);
    } else if( this.datum_type == Proj4js.common.PJD_7PARAM ) {
      return (this.datum_params[0] == dest.datum_params[0]
              && this.datum_params[1] == dest.datum_params[1]
              && this.datum_params[2] == dest.datum_params[2]
              && this.datum_params[3] == dest.datum_params[3]
              && this.datum_params[4] == dest.datum_params[4]
              && this.datum_params[5] == dest.datum_params[5]
              && this.datum_params[6] == dest.datum_params[6]);
    } else if( this.datum_type == Proj4js.common.PJD_GRIDSHIFT ) {
      return strcmp( pj_param(this.params,"snadgrids").s,
                     pj_param(dest.params,"snadgrids").s ) == 0;
    } else {
      return true; // datums are equal
    }
  }, // cs_compare_datums()

  /*
   * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
   * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
   * according to the current ellipsoid parameters.
   *
   *    Latitude  : Geodetic latitude in radians                     (input)
   *    Longitude : Geodetic longitude in radians                    (input)
   *    Height    : Geodetic height, in meters                       (input)
   *    X         : Calculated Geocentric X coordinate, in meters    (output)
   *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
   *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
   *
   */
  geodetic_to_geocentric : function(p) {
    var Longitude = p.x;
    var Latitude = p.y;
    var Height = p.z ? p.z : 0;   //Z value not always supplied
    var X;  // output
    var Y;
    var Z;

    var Error_Code=0;  //  GEOCENT_NO_ERROR;
    var Rn;            /*  Earth radius at location  */
    var Sin_Lat;       /*  Math.sin(Latitude)  */
    var Sin2_Lat;      /*  Square of Math.sin(Latitude)  */
    var Cos_Lat;       /*  Math.cos(Latitude)  */

    /*
    ** Don't blow up if Latitude is just a little out of the value
    ** range as it may just be a rounding issue.  Also removed longitude
    ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
    */
    if( Latitude < -Proj4js.common.HALF_PI && Latitude > -1.001 * Proj4js.common.HALF_PI ) {
        Latitude = -Proj4js.common.HALF_PI;
    } else if( Latitude > Proj4js.common.HALF_PI && Latitude < 1.001 * Proj4js.common.HALF_PI ) {
        Latitude = Proj4js.common.HALF_PI;
    } else if ((Latitude < -Proj4js.common.HALF_PI) || (Latitude > Proj4js.common.HALF_PI)) {
      /* Latitude out of range */
      Proj4js.reportError('geocent:lat out of range:'+Latitude);
      return null;
    }

    if (Longitude > Proj4js.common.PI) Longitude -= (2*Proj4js.common.PI);
    Sin_Lat = Math.sin(Latitude);
    Cos_Lat = Math.cos(Latitude);
    Sin2_Lat = Sin_Lat * Sin_Lat;
    Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat));
    X = (Rn + Height) * Cos_Lat * Math.cos(Longitude);
    Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude);
    Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat;

    p.x = X;
    p.y = Y;
    p.z = Z;
    return Error_Code;
  }, // cs_geodetic_to_geocentric()


  geocentric_to_geodetic : function (p) {
/* local defintions and variables */
/* end-criterium of loop, accuracy of sin(Latitude) */
var genau = 1.E-12;
var genau2 = (genau*genau);
var maxiter = 30;

    var P;        /* distance between semi-minor axis and location */
    var RR;       /* distance between center and location */
    var CT;       /* sin of geocentric latitude */
    var ST;       /* cos of geocentric latitude */
    var RX;
    var RK;
    var RN;       /* Earth radius at location */
    var CPHI0;    /* cos of start or old geodetic latitude in iterations */
    var SPHI0;    /* sin of start or old geodetic latitude in iterations */
    var CPHI;     /* cos of searched geodetic latitude */
    var SPHI;     /* sin of searched geodetic latitude */
    var SDPHI;    /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
    var At_Pole;     /* indicates location is in polar region */
    var iter;        /* # of continous iteration, max. 30 is always enough (s.a.) */

    var X = p.x;
    var Y = p.y;
    var Z = p.z ? p.z : 0.0;   //Z value not always supplied
    var Longitude;
    var Latitude;
    var Height;

    At_Pole = false;
    P = Math.sqrt(X*X+Y*Y);
    RR = Math.sqrt(X*X+Y*Y+Z*Z);

/*      special cases for latitude and longitude */
    if (P/this.a < genau) {

/*  special case, if P=0. (X=0., Y=0.) */
        At_Pole = true;
        Longitude = 0.0;

/*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
 *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
        if (RR/this.a < genau) {
            Latitude = Proj4js.common.HALF_PI;
            Height   = -this.b;
            return;
        }
    } else {
/*  ellipsoidal (geodetic) longitude
 *  interval: -PI < Longitude <= +PI */
        Longitude=Math.atan2(Y,X);
    }

/* --------------------------------------------------------------
 * Following iterative algorithm was developped by
 * "Institut f�r Erdmessung", University of Hannover, July 1988.
 * Internet: www.ife.uni-hannover.de
 * Iterative computation of CPHI,SPHI and Height.
 * Iteration of CPHI and SPHI to 10**-12 radian resp.
 * 2*10**-7 arcsec.
 * --------------------------------------------------------------
 */
    CT = Z/RR;
    ST = P/RR;
    RX = 1.0/Math.sqrt(1.0-this.es*(2.0-this.es)*ST*ST);
    CPHI0 = ST*(1.0-this.es)*RX;
    SPHI0 = CT*RX;
    iter = 0;

/* loop to find sin(Latitude) resp. Latitude
 * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
    do
    {
        iter++;
        RN = this.a/Math.sqrt(1.0-this.es*SPHI0*SPHI0);

/*  ellipsoidal (geodetic) height */
        Height = P*CPHI0+Z*SPHI0-RN*(1.0-this.es*SPHI0*SPHI0);

        RK = this.es*RN/(RN+Height);
        RX = 1.0/Math.sqrt(1.0-RK*(2.0-RK)*ST*ST);
        CPHI = ST*(1.0-RK)*RX;
        SPHI = CT*RX;
        SDPHI = SPHI*CPHI0-CPHI*SPHI0;
        CPHI0 = CPHI;
        SPHI0 = SPHI;
    }
    while (SDPHI*SDPHI > genau2 && iter < maxiter);

/*      ellipsoidal (geodetic) latitude */
    Latitude=Math.atan(SPHI/Math.abs(CPHI));

    p.x = Longitude;
    p.y = Latitude;
    p.z = Height;
    return p;
  }, // cs_geocentric_to_geodetic()

  /** Convert_Geocentric_To_Geodetic
   * The method used here is derived from 'An Improved Algorithm for
   * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996
   */
  geocentric_to_geodetic_noniter : function (p) {
    var X = p.x;
    var Y = p.y;
    var Z = p.z ? p.z : 0;   //Z value not always supplied
    var Longitude;
    var Latitude;
    var Height;

    var W;        /* distance from Z axis */
    var W2;       /* square of distance from Z axis */
    var T0;       /* initial estimate of vertical component */
    var T1;       /* corrected estimate of vertical component */
    var S0;       /* initial estimate of horizontal component */
    var S1;       /* corrected estimate of horizontal component */
    var Sin_B0;   /* Math.sin(B0), B0 is estimate of Bowring aux variable */
    var Sin3_B0;  /* cube of Math.sin(B0) */
    var Cos_B0;   /* Math.cos(B0) */
    var Sin_p1;   /* Math.sin(phi1), phi1 is estimated latitude */
    var Cos_p1;   /* Math.cos(phi1) */
    var Rn;       /* Earth radius at location */
    var Sum;      /* numerator of Math.cos(phi1) */
    var At_Pole;  /* indicates location is in polar region */

    X = parseFloat(X);  // cast from string to float
    Y = parseFloat(Y);
    Z = parseFloat(Z);

    At_Pole = false;
    if (X != 0.0)
    {
        Longitude = Math.atan2(Y,X);
    }
    else
    {
        if (Y > 0)
        {
            Longitude = Proj4js.common.HALF_PI;
        }
        else if (Y < 0)
        {
            Longitude = -Proj4js.common.HALF_PI;
        }
        else
        {
            At_Pole = true;
            Longitude = 0.0;
            if (Z > 0.0)
            {  /* north pole */
                Latitude = Proj4js.common.HALF_PI;
            }
            else if (Z < 0.0)
            {  /* south pole */
                Latitude = -Proj4js.common.HALF_PI;
            }
            else
            {  /* center of earth */
                Latitude = Proj4js.common.HALF_PI;
                Height = -this.b;
                return;
            }
        }
    }
    W2 = X*X + Y*Y;
    W = Math.sqrt(W2);
    T0 = Z * Proj4js.common.AD_C;
    S0 = Math.sqrt(T0 * T0 + W2);
    Sin_B0 = T0 / S0;
    Cos_B0 = W / S0;
    Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0;
    T1 = Z + this.b * this.ep2 * Sin3_B0;
    Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0;
    S1 = Math.sqrt(T1*T1 + Sum * Sum);
    Sin_p1 = T1 / S1;
    Cos_p1 = Sum / S1;
    Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1);
    if (Cos_p1 >= Proj4js.common.COS_67P5)
    {
        Height = W / Cos_p1 - Rn;
    }
    else if (Cos_p1 <= -Proj4js.common.COS_67P5)
    {
        Height = W / -Cos_p1 - Rn;
    }
    else
    {
        Height = Z / Sin_p1 + Rn * (this.es - 1.0);
    }
    if (At_Pole == false)
    {
        Latitude = Math.atan(Sin_p1 / Cos_p1);
    }

    p.x = Longitude;
    p.y = Latitude;
    p.z = Height;
    return p;
  }, // geocentric_to_geodetic_noniter()

  /****************************************************************/
  // pj_geocentic_to_wgs84( p )
  //  p = point to transform in geocentric coordinates (x,y,z)
  geocentric_to_wgs84 : function ( p ) {

    if( this.datum_type == Proj4js.common.PJD_3PARAM )
    {
      // if( x[io] == HUGE_VAL )
      //    continue;
      p.x += this.datum_params[0];
      p.y += this.datum_params[1];
      p.z += this.datum_params[2];

    }
    else if (this.datum_type == Proj4js.common.PJD_7PARAM)
    {
      var Dx_BF =this.datum_params[0];
      var Dy_BF =this.datum_params[1];
      var Dz_BF =this.datum_params[2];
      var Rx_BF =this.datum_params[3];
      var Ry_BF =this.datum_params[4];
      var Rz_BF =this.datum_params[5];
      var M_BF  =this.datum_params[6];
      // if( x[io] == HUGE_VAL )
      //    continue;
      var x_out = M_BF*(       p.x - Rz_BF*p.y + Ry_BF*p.z) + Dx_BF;
      var y_out = M_BF*( Rz_BF*p.x +       p.y - Rx_BF*p.z) + Dy_BF;
      var z_out = M_BF*(-Ry_BF*p.x + Rx_BF*p.y +       p.z) + Dz_BF;
      p.x = x_out;
      p.y = y_out;
      p.z = z_out;
    }
  }, // cs_geocentric_to_wgs84

  /****************************************************************/
  // pj_geocentic_from_wgs84()
  //  coordinate system definition,
  //  point to transform in geocentric coordinates (x,y,z)
  geocentric_from_wgs84 : function( p ) {

    if( this.datum_type == Proj4js.common.PJD_3PARAM )
    {
      //if( x[io] == HUGE_VAL )
      //    continue;
      p.x -= this.datum_params[0];
      p.y -= this.datum_params[1];
      p.z -= this.datum_params[2];

    }
    else if (this.datum_type == Proj4js.common.PJD_7PARAM)
    {
      var Dx_BF =this.datum_params[0];
      var Dy_BF =this.datum_params[1];
      var Dz_BF =this.datum_params[2];
      var Rx_BF =this.datum_params[3];
      var Ry_BF =this.datum_params[4];
      var Rz_BF =this.datum_params[5];
      var M_BF  =this.datum_params[6];
      var x_tmp = (p.x - Dx_BF) / M_BF;
      var y_tmp = (p.y - Dy_BF) / M_BF;
      var z_tmp = (p.z - Dz_BF) / M_BF;
      //if( x[io] == HUGE_VAL )
      //    continue;

      p.x =        x_tmp + Rz_BF*y_tmp - Ry_BF*z_tmp;
      p.y = -Rz_BF*x_tmp +       y_tmp + Rx_BF*z_tmp;
      p.z =  Ry_BF*x_tmp - Rx_BF*y_tmp +       z_tmp;
    } //cs_geocentric_from_wgs84()
  }
});

/** point object, nothing fancy, just allows values to be
    passed back and forth by reference rather than by value.
    Other point classes may be used as long as they have
    x and y properties, which will get modified in the transform method.
*/
Proj4js.Point = Proj4js.Class({

    /**
     * Constructor: Proj4js.Point
     *
     * Parameters:
     * - x {float} or {Array} either the first coordinates component or
     *     the full coordinates
     * - y {float} the second component
     * - z {float} the third component, optional.
     */
    initialize : function(x,y,z) {
      if (typeof x == 'object') {
        this.x = x[0];
        this.y = x[1];
        this.z = x[2] || 0.0;
      } else if (typeof x == 'string' && typeof y == 'undefined') {
        var coords = x.split(',');
        this.x = parseFloat(coords[0]);
        this.y = parseFloat(coords[1]);
        this.z = parseFloat(coords[2]) || 0.0;
      } else {
        this.x = x;
        this.y = y;
        this.z = z || 0.0;
      }
    },

    /**
     * APIMethod: clone
     * Build a copy of a Proj4js.Point object.
     *
     * Return:
     * {Proj4js}.Point the cloned point.
     */
    clone : function() {
      return new Proj4js.Point(this.x, this.y, this.z);
    },

    /**
     * APIMethod: toString
     * Return a readable string version of the point
     *
     * Return:
     * {String} String representation of Proj4js.Point object. 
     *           (ex. <i>"x=5,y=42"</i>)
     */
    toString : function() {
        return ("x=" + this.x + ",y=" + this.y);
    },

    /** 
     * APIMethod: toShortString
     * Return a short string version of the point.
     *
     * Return:
     * {String} Shortened String representation of Proj4js.Point object. 
     *         (ex. <i>"5, 42"</i>)
     */
    toShortString : function() {
        return (this.x + ", " + this.y);
    }
});

Proj4js.PrimeMeridian = {
    "greenwich": 0.0,               //"0dE",
    "lisbon":     -9.131906111111,   //"9d07'54.862\"W",
    "paris":       2.337229166667,   //"2d20'14.025\"E",
    "bogota":    -74.080916666667,  //"74d04'51.3\"W",
    "madrid":     -3.687938888889,  //"3d41'16.58\"W",
    "rome":       12.452333333333,  //"12d27'8.4\"E",
    "bern":        7.439583333333,  //"7d26'22.5\"E",
    "jakarta":   106.807719444444,  //"106d48'27.79\"E",
    "ferro":     -17.666666666667,  //"17d40'W",
    "brussels":    4.367975,        //"4d22'4.71\"E",
    "stockholm":  18.058277777778,  //"18d3'29.8\"E",
    "athens":     23.7163375,       //"23d42'58.815\"E",
    "oslo":       10.722916666667   //"10d43'22.5\"E"
};

Proj4js.Ellipsoid = {
  "MERIT": {a:6378137.0, rf:298.257, ellipseName:"MERIT 1983"},
  "SGS85": {a:6378136.0, rf:298.257, ellipseName:"Soviet Geodetic System 85"},
  "GRS80": {a:6378137.0, rf:298.257222101, ellipseName:"GRS 1980(IUGG, 1980)"},
  "IAU76": {a:6378140.0, rf:298.257, ellipseName:"IAU 1976"},
  "airy": {a:6377563.396, b:6356256.910, ellipseName:"Airy 1830"},
  "APL4.": {a:6378137, rf:298.25, ellipseName:"Appl. Physics. 1965"},
  "NWL9D": {a:6378145.0, rf:298.25, ellipseName:"Naval Weapons Lab., 1965"},
  "mod_airy": {a:6377340.189, b:6356034.446, ellipseName:"Modified Airy"},
  "andrae": {a:6377104.43, rf:300.0, ellipseName:"Andrae 1876 (Den., Iclnd.)"},
  "aust_SA": {a:6378160.0, rf:298.25, ellipseName:"Australian Natl & S. Amer. 1969"},
  "GRS67": {a:6378160.0, rf:298.2471674270, ellipseName:"GRS 67(IUGG 1967)"},
  "bessel": {a:6377397.155, rf:299.1528128, ellipseName:"Bessel 1841"},
  "bess_nam": {a:6377483.865, rf:299.1528128, ellipseName:"Bessel 1841 (Namibia)"},
  "clrk66": {a:6378206.4, b:6356583.8, ellipseName:"Clarke 1866"},
  "clrk80": {a:6378249.145, rf:293.4663, ellipseName:"Clarke 1880 mod."},
  "CPM": {a:6375738.7, rf:334.29, ellipseName:"Comm. des Poids et Mesures 1799"},
  "delmbr": {a:6376428.0, rf:311.5, ellipseName:"Delambre 1810 (Belgium)"},
  "engelis": {a:6378136.05, rf:298.2566, ellipseName:"Engelis 1985"},
  "evrst30": {a:6377276.345, rf:300.8017, ellipseName:"Everest 1830"},
  "evrst48": {a:6377304.063, rf:300.8017, ellipseName:"Everest 1948"},
  "evrst56": {a:6377301.243, rf:300.8017, ellipseName:"Everest 1956"},
  "evrst69": {a:6377295.664, rf:300.8017, ellipseName:"Everest 1969"},
  "evrstSS": {a:6377298.556, rf:300.8017, ellipseName:"Everest (Sabah & Sarawak)"},
  "fschr60": {a:6378166.0, rf:298.3, ellipseName:"Fischer (Mercury Datum) 1960"},
  "fschr60m": {a:6378155.0, rf:298.3, ellipseName:"Fischer 1960"},
  "fschr68": {a:6378150.0, rf:298.3, ellipseName:"Fischer 1968"},
  "helmert": {a:6378200.0, rf:298.3, ellipseName:"Helmert 1906"},
  "hough": {a:6378270.0, rf:297.0, ellipseName:"Hough"},
  "intl": {a:6378388.0, rf:297.0, ellipseName:"International 1909 (Hayford)"},
  "kaula": {a:6378163.0, rf:298.24, ellipseName:"Kaula 1961"},
  "lerch": {a:6378139.0, rf:298.257, ellipseName:"Lerch 1979"},
  "mprts": {a:6397300.0, rf:191.0, ellipseName:"Maupertius 1738"},
  "new_intl": {a:6378157.5, b:6356772.2, ellipseName:"New International 1967"},
  "plessis": {a:6376523.0, rf:6355863.0, ellipseName:"Plessis 1817 (France)"},
  "krass": {a:6378245.0, rf:298.3, ellipseName:"Krassovsky, 1942"},
  "SEasia": {a:6378155.0, b:6356773.3205, ellipseName:"Southeast Asia"},
  "walbeck": {a:6376896.0, b:6355834.8467, ellipseName:"Walbeck"},
  "WGS60": {a:6378165.0, rf:298.3, ellipseName:"WGS 60"},
  "WGS66": {a:6378145.0, rf:298.25, ellipseName:"WGS 66"},
  "WGS72": {a:6378135.0, rf:298.26, ellipseName:"WGS 72"},
  "WGS84": {a:6378137.0, rf:298.257223563, ellipseName:"WGS 84"},
  "sphere": {a:6370997.0, b:6370997.0, ellipseName:"Normal Sphere (r=6370997)"}
};

Proj4js.Datum = {
  "WGS84": {towgs84: "0,0,0", ellipse: "WGS84", datumName: "WGS84"},
  "GGRS87": {towgs84: "-199.87,74.79,246.62", ellipse: "GRS80", datumName: "Greek_Geodetic_Reference_System_1987"},
  "NAD83": {towgs84: "0,0,0", ellipse: "GRS80", datumName: "North_American_Datum_1983"},
  "NAD27": {nadgrids: "@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat", ellipse: "clrk66", datumName: "North_American_Datum_1927"},
  "potsdam": {towgs84: "606.0,23.0,413.0", ellipse: "bessel", datumName: "Potsdam Rauenberg 1950 DHDN"},
  "carthage": {towgs84: "-263.0,6.0,431.0", ellipse: "clark80", datumName: "Carthage 1934 Tunisia"},
  "hermannskogel": {towgs84: "653.0,-212.0,449.0", ellipse: "bessel", datumName: "Hermannskogel"},
  "ire65": {towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", ellipse: "mod_airy", datumName: "Ireland 1965"},
  "nzgd49": {towgs84: "59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", ellipse: "intl", datumName: "New Zealand Geodetic Datum 1949"},
  "OSGB36": {towgs84: "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", ellipse: "airy", datumName: "Airy 1830"}
};

Proj4js.WGS84 = new Proj4js.Proj('WGS84');
Proj4js.Datum['OSB36'] = Proj4js.Datum['OSGB36']; //as returned from spatialreference.org

//lookup table to go from the projection name in WKT to the Proj4js projection name
//build this out as required
Proj4js.wktProjections = {
  "Lambert Tangential Conformal Conic Projection": "lcc",
  "Mercator": "merc",
  "Popular Visualisation Pseudo Mercator": "merc",
  "Mercator_1SP": "merc",
  "Transverse_Mercator": "tmerc",
  "Transverse Mercator": "tmerc",
  "Lambert Azimuthal Equal Area": "laea",
  "Universal Transverse Mercator System": "utm"
};


/* ======================================================================
    projCode/aea.js
   ====================================================================== */

/*******************************************************************************
NAME                     ALBERS CONICAL EQUAL AREA 

PURPOSE:	Transforms input longitude and latitude to Easting and Northing
		for the Albers Conical Equal Area projection.  The longitude
		and latitude must be in radians.  The Easting and Northing
		values will be returned in meters.

PROGRAMMER              DATE
----------              ----
T. Mittan,       	Feb, 1992

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/


Proj4js.Proj.aea = {
  init : function() {

    if (Math.abs(this.lat1 + this.lat2) < Proj4js.common.EPSLN) {
       Proj4js.reportError("aeaInitEqualLatitudes");
       return;
    }
    this.temp = this.b / this.a;
    this.es = 1.0 - Math.pow(this.temp,2);
    this.e3 = Math.sqrt(this.es);

    this.sin_po=Math.sin(this.lat1);
    this.cos_po=Math.cos(this.lat1);
    this.t1=this.sin_po;
    this.con = this.sin_po;
    this.ms1 = Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);
    this.qs1 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);

    this.sin_po=Math.sin(this.lat2);
    this.cos_po=Math.cos(this.lat2);
    this.t2=this.sin_po;
    this.ms2 = Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);
    this.qs2 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);

    this.sin_po=Math.sin(this.lat0);
    this.cos_po=Math.cos(this.lat0);
    this.t3=this.sin_po;
    this.qs0 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);

    if (Math.abs(this.lat1 - this.lat2) > Proj4js.common.EPSLN) {
      this.ns0 = (this.ms1 * this.ms1 - this.ms2 *this.ms2)/ (this.qs2 - this.qs1);
    } else {
      this.ns0 = this.con;
    }
    this.c = this.ms1 * this.ms1 + this.ns0 * this.qs1;
    this.rh = this.a * Math.sqrt(this.c - this.ns0 * this.qs0)/this.ns0;
  },

/* Albers Conical Equal Area forward equations--mapping lat,long to x,y
  -------------------------------------------------------------------*/
  forward: function(p){

    var lon=p.x;
    var lat=p.y;

    this.sin_phi=Math.sin(lat);
    this.cos_phi=Math.cos(lat);

    var qs = Proj4js.common.qsfnz(this.e3,this.sin_phi,this.cos_phi);
    var rh1 =this.a * Math.sqrt(this.c - this.ns0 * qs)/this.ns0;
    var theta = this.ns0 * Proj4js.common.adjust_lon(lon - this.long0); 
    var x = rh1 * Math.sin(theta) + this.x0;
    var y = this.rh - rh1 * Math.cos(theta) + this.y0;

    p.x = x; 
    p.y = y;
    return p;
  },


  inverse: function(p) {
    var rh1,qs,con,theta,lon,lat;

    p.x -= this.x0;
    p.y = this.rh - p.y + this.y0;
    if (this.ns0 >= 0) {
      rh1 = Math.sqrt(p.x *p.x + p.y * p.y);
      con = 1.0;
    } else {
      rh1 = -Math.sqrt(p.x * p.x + p.y *p.y);
      con = -1.0;
    }
    theta = 0.0;
    if (rh1 != 0.0) {
      theta = Math.atan2(con * p.x, con * p.y);
    }
    con = rh1 * this.ns0 / this.a;
    qs = (this.c - con * con) / this.ns0;
    if (this.e3 >= 1e-10) {
      con = 1 - .5 * (1.0 -this.es) * Math.log((1.0 - this.e3) / (1.0 + this.e3))/this.e3;
      if (Math.abs(Math.abs(con) - Math.abs(qs)) > .0000000001 ) {
          lat = this.phi1z(this.e3,qs);
      } else {
          if (qs >= 0) {
             lat = .5 * PI;
          } else {
             lat = -.5 * PI;
          }
      }
    } else {
      lat = this.phi1z(e3,qs);
    }

    lon = Proj4js.common.adjust_lon(theta/this.ns0 + this.long0);
    p.x = lon;
    p.y = lat;
    return p;
  },
  
/* Function to compute phi1, the latitude for the inverse of the
   Albers Conical Equal-Area projection.
-------------------------------------------*/
  phi1z: function (eccent,qs) {
    var con, com, dphi;
    var phi = Proj4js.common.asinz(.5 * qs);
    if (eccent < Proj4js.common.EPSLN) return phi;
    
    var eccnts = eccent * eccent; 
    for (var i = 1; i <= 25; i++) {
        sinphi = Math.sin(phi);
        cosphi = Math.cos(phi);
        con = eccent * sinphi; 
        com = 1.0 - con * con;
        dphi = .5 * com * com / cosphi * (qs / (1.0 - eccnts) - sinphi / com + .5 / eccent * Math.log((1.0 - con) / (1.0 + con)));
        phi = phi + dphi;
        if (Math.abs(dphi) <= 1e-7) return phi;
    }
    Proj4js.reportError("aea:phi1z:Convergence error");
    return null;
  }
  
};



/* ======================================================================
    projCode/sterea.js
   ====================================================================== */


Proj4js.Proj.sterea = {
  dependsOn : 'gauss',

  init : function() {
    Proj4js.Proj['gauss'].init.apply(this);
    if (!this.rc) {
      Proj4js.reportError("sterea:init:E_ERROR_0");
      return;
    }
    this.sinc0 = Math.sin(this.phic0);
    this.cosc0 = Math.cos(this.phic0);
    this.R2 = 2.0 * this.rc;
    if (!this.title) this.title = "Oblique Stereographic Alternative";
  },

  forward : function(p) {
    p.x = Proj4js.common.adjust_lon(p.x-this.long0); /* adjust del longitude */
    Proj4js.Proj['gauss'].forward.apply(this, [p]);
    sinc = Math.sin(p.y);
    cosc = Math.cos(p.y);
    cosl = Math.cos(p.x);
    k = this.k0 * this.R2 / (1.0 + this.sinc0 * sinc + this.cosc0 * cosc * cosl);
    p.x = k * cosc * Math.sin(p.x);
    p.y = k * (this.cosc0 * sinc - this.sinc0 * cosc * cosl);
    p.x = this.a * p.x + this.x0;
    p.y = this.a * p.y + this.y0;
    return p;
  },

  inverse : function(p) {
    var lon,lat;
    p.x = (p.x - this.x0) / this.a; /* descale and de-offset */
    p.y = (p.y - this.y0) / this.a;

    p.x /= this.k0;
    p.y /= this.k0;
    if ( (rho = Math.sqrt(p.x*p.x + p.y*p.y)) ) {
      c = 2.0 * Math.atan2(rho, this.R2);
      sinc = Math.sin(c);
      cosc = Math.cos(c);
      lat = Math.asin(cosc * this.sinc0 + p.y * sinc * this.cosc0 / rho);
      lon = Math.atan2(p.x * sinc, rho * this.cosc0 * cosc - p.y * this.sinc0 * sinc);
    } else {
      lat = this.phic0;
      lon = 0.;
    }

    p.x = lon;
    p.y = lat;
    Proj4js.Proj['gauss'].inverse.apply(this,[p]);
    p.x = Proj4js.common.adjust_lon(p.x + this.long0); /* adjust longitude to CM */
    return p;
  }
};

/* ======================================================================
    projCode/poly.js
   ====================================================================== */

/* Function to compute, phi4, the latitude for the inverse of the
   Polyconic projection.
------------------------------------------------------------*/
function phi4z (eccent,e0,e1,e2,e3,a,b,c,phi) {
	var sinphi, sin2ph, tanph, ml, mlp, con1, con2, con3, dphi, i;

	phi = a;
	for (i = 1; i <= 15; i++) {
		sinphi = Math.sin(phi);
		tanphi = Math.tan(phi);
		c = tanphi * Math.sqrt (1.0 - eccent * sinphi * sinphi);
		sin2ph = Math.sin (2.0 * phi);
		/*
		ml = e0 * *phi - e1 * sin2ph + e2 * sin (4.0 *  *phi);
		mlp = e0 - 2.0 * e1 * cos (2.0 *  *phi) + 4.0 * e2 *  cos (4.0 *  *phi);
		*/
		ml = e0 * phi - e1 * sin2ph + e2 * Math.sin (4.0 *  phi) - e3 * Math.sin (6.0 * phi);
		mlp = e0 - 2.0 * e1 * Math.cos (2.0 *  phi) + 4.0 * e2 * Math.cos (4.0 *  phi) - 6.0 * e3 * Math.cos (6.0 *  phi);
		con1 = 2.0 * ml + c * (ml * ml + b) - 2.0 * a *  (c * ml + 1.0);
		con2 = eccent * sin2ph * (ml * ml + b - 2.0 * a * ml) / (2.0 *c);
		con3 = 2.0 * (a - ml) * (c * mlp - 2.0 / sin2ph) - 2.0 * mlp;
		dphi = con1 / (con2 + con3);
		phi += dphi;
		if (Math.abs(dphi) <= .0000000001 ) return(phi);   
	}
	Proj4js.reportError("phi4z: No convergence");
	return null;
}


/* Function to compute the constant e4 from the input of the eccentricity
   of the spheroid, x.  This constant is used in the Polar Stereographic
   projection.
--------------------------------------------------------------------*/
function e4fn(x) {
	var con, com;
	con = 1.0 + x;
	com = 1.0 - x;
	return (Math.sqrt((Math.pow(con,con))*(Math.pow(com,com))));
}





/*******************************************************************************
NAME                             POLYCONIC 

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Polyconic projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE
----------              ----
T. Mittan		Mar, 1993

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/

Proj4js.Proj.poly = {

	/* Initialize the POLYCONIC projection
	  ----------------------------------*/
	init: function() {
		var temp;			/* temporary variable		*/
		if (this.lat0=0) this.lat0=90;//this.lat0 ca

		/* Place parameters in static storage for common use
		  -------------------------------------------------*/
		this.temp = this.b / this.a;
		this.es = 1.0 - Math.pow(this.temp,2);// devait etre dans tmerc.js mais n y est pas donc je commente sinon retour de valeurs nulles 
		this.e = Math.sqrt(this.es);
		this.e0 = Proj4js.common.e0fn(this.es);
		this.e1 = Proj4js.common.e1fn(this.es);
		this.e2 = Proj4js.common.e2fn(this.es);
		this.e3 = Proj4js.common.e3fn(this.es);
		this.ml0 = Proj4js.common.mlfn(this.e0, this.e1,this.e2, this.e3, this.lat0);//si que des zeros le calcul ne se fait pas
		//if (!this.ml0) {this.ml0=0;}
	},


	/* Polyconic forward equations--mapping lat,long to x,y
	  ---------------------------------------------------*/
	forward: function(p) {
		var sinphi, cosphi;	/* sin and cos value				*/
		var al;				/* temporary values				*/
		var c;				/* temporary values				*/
		var con, ml;		/* cone constant, small m			*/
		var ms;				/* small m					*/
		var x,y;

		var lon=p.x;
		var lat=p.y;	

		con = Proj4js.common.adjust_lon(lon - this.long0);
		if (Math.abs(lat) <= .0000001) {
			x = this.x0 + this.a * con;
			y = this.y0 - this.a * this.ml0;
		} else {
			sinphi = Math.sin(lat);
			cosphi = Math.cos(lat);	   

			ml = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
			ms = Proj4js.common.msfnz(this.e,sinphi,cosphi);
			con = sinphi;
			x = this.x0 + this.a * ms * Math.sin(con)/sinphi;
			y = this.y0 + this.a * (ml - this.ml0 + ms * (1.0 - Math.cos(con))/sinphi);
		}

		p.x=x;
		p.y=y;   
		return p;
	},


	/* Inverse equations
	-----------------*/
	inverse: function(p) {
		var sin_phi, cos_phi;	/* sin and cos value				*/
		var al;					/* temporary values				*/
		var b;					/* temporary values				*/
		var c;					/* temporary values				*/
		var con, ml;			/* cone constant, small m			*/
		var iflg;				/* error flag					*/
		var lon,lat;
		p.x -= this.x0;
		p.y -= this.y0;
		al = this.ml0 + p.y/this.a;
		iflg = 0;

		if (Math.abs(al) <= .0000001) {
			lon = p.x/this.a + this.long0;
			lat = 0.0;
		} else {
			b = al * al + (p.x/this.a) * (p.x/this.a);
			iflg = phi4z(this.es,this.e0,this.e1,this.e2,this.e3,this.al,b,c,lat);
			if (iflg != 1) return(iflg);
			lon = Proj4js.common.adjust_lon((Proj4js.common.asinz(p.x * c / this.a) / Math.sin(lat)) + this.long0);
		}

		p.x=lon;
		p.y=lat;
		return p;
	}
};



/* ======================================================================
    projCode/equi.js
   ====================================================================== */

/*******************************************************************************
NAME                             EQUIRECTANGULAR 

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Equirectangular projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE
----------              ----
T. Mittan		Mar, 1993

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/
Proj4js.Proj.equi = {

  init: function() {
    if(!this.x0) this.x0=0;
    if(!this.y0) this.y0=0;
    if(!this.lat0) this.lat0=0;
    if(!this.long0) this.long0=0;
    ///this.t2;
  },



/* Equirectangular forward equations--mapping lat,long to x,y
  ---------------------------------------------------------*/
  forward: function(p) {

    var lon=p.x;				
    var lat=p.y;			

    var dlon = Proj4js.common.adjust_lon(lon - this.long0);
    var x = this.x0 +this. a * dlon *Math.cos(this.lat0);
    var y = this.y0 + this.a * lat;

    this.t1=x;
    this.t2=Math.cos(this.lat0);
    p.x=x;
    p.y=y;
    return p;
  },  //equiFwd()



/* Equirectangular inverse equations--mapping x,y to lat/long
  ---------------------------------------------------------*/
  inverse: function(p) {

    p.x -= this.x0;
    p.y -= this.y0;
    var lat = p.y /this. a;

    if ( Math.abs(lat) > Proj4js.common.HALF_PI) {
        Proj4js.reportError("equi:Inv:DataError");
    }
    var lon = Proj4js.common.adjust_lon(this.long0 + p.x / (this.a * Math.cos(this.lat0)));
    p.x=lon;
    p.y=lat;
  }//equiInv()
};


/* ======================================================================
    projCode/merc.js
   ====================================================================== */

/*******************************************************************************
NAME                            MERCATOR

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Mercator projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE
----------              ----
D. Steinwand, EROS      Nov, 1991
T. Mittan		Mar, 1993

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/

//static double r_major = a;		   /* major axis 				*/
//static double r_minor = b;		   /* minor axis 				*/
//static double lon_center = long0;	   /* Center longitude (projection center) */
//static double lat_origin =  lat0;	   /* center latitude			*/
//static double e,es;		           /* eccentricity constants		*/
//static double m1;		               /* small value m			*/
//static double false_northing = y0;   /* y offset in meters			*/
//static double false_easting = x0;	   /* x offset in meters			*/
//scale_fact = k0 

Proj4js.Proj.merc = {
  init : function() {
	//?this.temp = this.r_minor / this.r_major;
	//this.temp = this.b / this.a;
	//this.es = 1.0 - Math.sqrt(this.temp);
	//this.e = Math.sqrt( this.es );
	//?this.m1 = Math.cos(this.lat_origin) / (Math.sqrt( 1.0 - this.es * Math.sin(this.lat_origin) * Math.sin(this.lat_origin)));
	//this.m1 = Math.cos(0.0) / (Math.sqrt( 1.0 - this.es * Math.sin(0.0) * Math.sin(0.0)));
    if (this.lat_ts) {
      if (this.sphere) {
        this.k0 = Math.cos(this.lat_ts);
      } else {
        this.k0 = Proj4js.common.msfnz(this.es, Math.sin(this.lat_ts), Math.cos(this.lat_ts));
      }
    }
  },

/* Mercator forward equations--mapping lat,long to x,y
  --------------------------------------------------*/

  forward : function(p) {	
    //alert("ll2m coords : "+coords);
    var lon = p.x;
    var lat = p.y;
    // convert to radians
    if ( lat*Proj4js.common.R2D > 90.0 && 
          lat*Proj4js.common.R2D < -90.0 && 
          lon*Proj4js.common.R2D > 180.0 && 
          lon*Proj4js.common.R2D < -180.0) {
      Proj4js.reportError("merc:forward: llInputOutOfRange: "+ lon +" : " + lat);
      return null;
    }

    var x,y;
    if(Math.abs( Math.abs(lat) - Proj4js.common.HALF_PI)  <= Proj4js.common.EPSLN) {
      Proj4js.reportError("merc:forward: ll2mAtPoles");
      return null;
    } else {
      if (this.sphere) {
        x = this.x0 + this.a * this.k0 * Proj4js.common.adjust_lon(lon - this.long0);
        y = this.y0 + this.a * this.k0 * Math.log(Math.tan(Proj4js.common.FORTPI + 0.5*lat));
      } else {
        var sinphi = Math.sin(lat);
        var ts = Proj4js.common.tsfnz(this.e,lat,sinphi);
        x = this.x0 + this.a * this.k0 * Proj4js.common.adjust_lon(lon - this.long0);
        y = this.y0 - this.a * this.k0 * Math.log(ts);
      }
      p.x = x; 
      p.y = y;
      return p;
    }
  },


  /* Mercator inverse equations--mapping x,y to lat/long
  --------------------------------------------------*/
  inverse : function(p) {	

    var x = p.x - this.x0;
    var y = p.y - this.y0;
    var lon,lat;

    if (this.sphere) {
      lat = Proj4js.common.HALF_PI - 2.0 * Math.atan(Math.exp(-y / this.a * this.k0));
    } else {
      var ts = Math.exp(-y / (this.a * this.k0));
      lat = Proj4js.common.phi2z(this.e,ts);
      if(lat == -9999) {
        Proj4js.reportError("merc:inverse: lat = -9999");
        return null;
      }
    }
    lon = Proj4js.common.adjust_lon(this.long0+ x / (this.a * this.k0));

    p.x = lon;
    p.y = lat;
    return p;
  }
};


/* ======================================================================
    projCode/utm.js
   ====================================================================== */

/*******************************************************************************
NAME                            TRANSVERSE MERCATOR

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Transverse Mercator projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/


/**
  Initialize Transverse Mercator projection
*/

Proj4js.Proj.utm = {
  dependsOn : 'tmerc',

  init : function() {
    if (!this.zone) {
      Proj4js.reportError("utm:init: zone must be specified for UTM");
      return;
    }
    this.lat0 = 0.0;
    this.long0 = ((6 * Math.abs(this.zone)) - 183) * Proj4js.common.D2R;
    this.x0 = 500000.0;
    this.y0 = this.utmSouth ? 10000000.0 : 0.0;
    this.k0 = 0.9996;

    Proj4js.Proj['tmerc'].init.apply(this);
    this.forward = Proj4js.Proj['tmerc'].forward;
    this.inverse = Proj4js.Proj['tmerc'].inverse;
  }
};
/* ======================================================================
    projCode/eqdc.js
   ====================================================================== */

/*******************************************************************************
NAME                            EQUIDISTANT CONIC 

PURPOSE:	Transforms input longitude and latitude to Easting and Northing
		for the Equidistant Conic projection.  The longitude and
		latitude must be in radians.  The Easting and Northing values
		will be returned in meters.

PROGRAMMER              DATE
----------              ----
T. Mittan		Mar, 1993

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/

/* Variables common to all subroutines in this code file
  -----------------------------------------------------*/

Proj4js.Proj.eqdc = {

/* Initialize the Equidistant Conic projection
  ------------------------------------------*/
  init: function() {

    /* Place parameters in static storage for common use
      -------------------------------------------------*/

    if(!this.mode) this.mode=0;//chosen default mode
    this.temp = this.b / this.a;
    this.es = 1.0 - Math.pow(this.temp,2);
    this.e = Math.sqrt(this.es);
    this.e0 = Proj4js.common.e0fn(this.es);
    this.e1 = Proj4js.common.e1fn(this.es);
    this.e2 = Proj4js.common.e2fn(this.es);
    this.e3 = Proj4js.common.e3fn(this.es);

    this.sinphi=Math.sin(this.lat1);
    this.cosphi=Math.cos(this.lat1);

    this.ms1 = Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);
    this.ml1 = Proj4js.common.mlfn(this.e0, this.e1, this.e2,this.e3, this.lat1);

    /* format B
    ---------*/
    if (this.mode != 0) {
      if (Math.abs(this.lat1 + this.lat2) < Proj4js.common.EPSLN) {
            Proj4js.reportError("eqdc:Init:EqualLatitudes");
            //return(81);
       }
       this.sinphi=Math.sin(this.lat2);
       this.cosphi=Math.cos(this.lat2);   

       this.ms2 = Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);
       this.ml2 = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat2);
       if (Math.abs(this.lat1 - this.lat2) >= Proj4js.common.EPSLN) {
         this.ns = (this.ms1 - this.ms2) / (this.ml2 - this.ml1);
       } else {
          this.ns = this.sinphi;
       }
    } else {
      this.ns = this.sinphi;
    }
    this.g = this.ml1 + this.ms1/this.ns;
    this.ml0 = Proj4js.common.mlfn(this.e0, this.e1,this. e2, this.e3, this.lat0);
    this.rh = this.a * (this.g - this.ml0);
  },


/* Equidistant Conic forward equations--mapping lat,long to x,y
  -----------------------------------------------------------*/
  forward: function(p) {
    var lon=p.x;
    var lat=p.y;

    /* Forward equations
      -----------------*/
    var ml = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
    var rh1 = this.a * (this.g - ml);
    var theta = this.ns * Proj4js.common.adjust_lon(lon - this.long0);

    var x = this.x0  + rh1 * Math.sin(theta);
    var y = this.y0 + this.rh - rh1 * Math.cos(theta);
    p.x=x;
    p.y=y;
    return p;
  },

/* Inverse equations
  -----------------*/
  inverse: function(p) {
    p.x -= this.x0;
    p.y  = this.rh - p.y + this.y0;
    var con, rh1;
    if (this.ns >= 0) {
       var rh1 = Math.sqrt(p.x *p.x + p.y * p.y); 
       var con = 1.0;
    } else {
       rh1 = -Math.sqrt(p.x *p. x +p. y * p.y); 
       con = -1.0;
    }
    var theta = 0.0;
    if (rh1 != 0.0) theta = Math.atan2(con *p.x, con *p.y);
    var ml = this.g - rh1 /this.a;
    var lat = this.phi3z(ml,this.e0,this.e1,this.e2,this.e3);
    var lon = Proj4js.common.adjust_lon(this.long0 + theta / this.ns);

     p.x=lon;
     p.y=lat;  
     return p;
    },
    
/* Function to compute latitude, phi3, for the inverse of the Equidistant
   Conic projection.
-----------------------------------------------------------------*/
  phi3z: function(ml,e0,e1,e2,e3) {
    var phi;
    var dphi;

    phi = ml;
    for (var i = 0; i < 15; i++) {
      dphi = (ml + e1 * Math.sin(2.0 * phi) - e2 * Math.sin(4.0 * phi) + e3 * Math.sin(6.0 * phi))/ e0 - phi;
      phi += dphi;
      if (Math.abs(dphi) <= .0000000001) {
        return phi;
      }
    }
    Proj4js.reportError("PHI3Z-CONV:Latitude failed to converge after 15 iterations");
    return null;
  }

    
};
/* ======================================================================
    projCode/tmerc.js
   ====================================================================== */

/*******************************************************************************
NAME                            TRANSVERSE MERCATOR

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Transverse Mercator projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/


/**
  Initialize Transverse Mercator projection
*/

Proj4js.Proj.tmerc = {
  init : function() {
    this.e0 = Proj4js.common.e0fn(this.es);
    this.e1 = Proj4js.common.e1fn(this.es);
    this.e2 = Proj4js.common.e2fn(this.es);
    this.e3 = Proj4js.common.e3fn(this.es);
    this.ml0 = this.a * Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0);
  },

  /**
    Transverse Mercator Forward  - long/lat to x/y
    long/lat in radians
  */
  forward : function(p) {
    var lon = p.x;
    var lat = p.y;

    var delta_lon = Proj4js.common.adjust_lon(lon - this.long0); // Delta longitude
    var con;    // cone constant
    var x, y;
    var sin_phi=Math.sin(lat);
    var cos_phi=Math.cos(lat);

    if (this.sphere) {  /* spherical form */
      var b = cos_phi * Math.sin(delta_lon);
      if ((Math.abs(Math.abs(b) - 1.0)) < .0000000001)  {
        Proj4js.reportError("tmerc:forward: Point projects into infinity");
        return(93);
      } else {
        x = .5 * this.a * this.k0 * Math.log((1.0 + b)/(1.0 - b));
        con = Math.acos(cos_phi * Math.cos(delta_lon)/Math.sqrt(1.0 - b*b));
        if (lat < 0) con = - con;
        y = this.a * this.k0 * (con - this.lat0);
      }
    } else {
      var al  = cos_phi * delta_lon;
      var als = Math.pow(al,2);
      var c   = this.ep2 * Math.pow(cos_phi,2);
      var tq  = Math.tan(lat);
      var t   = Math.pow(tq,2);
      con = 1.0 - this.es * Math.pow(sin_phi,2);
      var n   = this.a / Math.sqrt(con);
      var ml  = this.a * Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);

      x = this.k0 * n * al * (1.0 + als / 6.0 * (1.0 - t + c + als / 20.0 * (5.0 - 18.0 * t + Math.pow(t,2) + 72.0 * c - 58.0 * this.ep2))) + this.x0;
      y = this.k0 * (ml - this.ml0 + n * tq * (als * (0.5 + als / 24.0 * (5.0 - t + 9.0 * c + 4.0 * Math.pow(c,2) + als / 30.0 * (61.0 - 58.0 * t + Math.pow(t,2) + 600.0 * c - 330.0 * this.ep2))))) + this.y0;

    }
    p.x = x; p.y = y;
    return p;
  }, // tmercFwd()

  /**
    Transverse Mercator Inverse  -  x/y to long/lat
  */
  inverse : function(p) {
    var con, phi;  /* temporary angles       */
    var delta_phi; /* difference between longitudes    */
    var i;
    var max_iter = 6;      /* maximun number of iterations */
    var lat, lon;

    if (this.sphere) {   /* spherical form */
      var f = Math.exp(p.x/(this.a * this.k0));
      var g = .5 * (f - 1/f);
      var temp = this.lat0 + p.y/(this.a * this.k0);
      var h = Math.cos(temp);
      con = Math.sqrt((1.0 - h * h)/(1.0 + g * g));
      lat = Proj4js.common.asinz(con);
      if (temp < 0)
        lat = -lat;
      if ((g == 0) && (h == 0)) {
        lon = this.long0;
      } else {
        lon = Proj4js.common.adjust_lon(Math.atan2(g,h) + this.long0);
      }
    } else {    // ellipsoidal form
      var x = p.x - this.x0;
      var y = p.y - this.y0;

      con = (this.ml0 + y / this.k0) / this.a;
      phi = con;
      for (i=0;true;i++) {
        delta_phi=((con + this.e1 * Math.sin(2.0*phi) - this.e2 * Math.sin(4.0*phi) + this.e3 * Math.sin(6.0*phi)) / this.e0) - phi;
        phi += delta_phi;
        if (Math.abs(delta_phi) <= Proj4js.common.EPSLN) break;
        if (i >= max_iter) {
          Proj4js.reportError("tmerc:inverse: Latitude failed to converge");
          return(95);
        }
      } // for()
      if (Math.abs(phi) < Proj4js.common.HALF_PI) {
        // sincos(phi, &sin_phi, &cos_phi);
        var sin_phi=Math.sin(phi);
        var cos_phi=Math.cos(phi);
        var tan_phi = Math.tan(phi);
        var c = this.ep2 * Math.pow(cos_phi,2);
        var cs = Math.pow(c,2);
        var t = Math.pow(tan_phi,2);
        var ts = Math.pow(t,2);
        con = 1.0 - this.es * Math.pow(sin_phi,2);
        var n = this.a / Math.sqrt(con);
        var r = n * (1.0 - this.es) / con;
        var d = x / (n * this.k0);
        var ds = Math.pow(d,2);
        lat = phi - (n * tan_phi * ds / r) * (0.5 - ds / 24.0 * (5.0 + 3.0 * t + 10.0 * c - 4.0 * cs - 9.0 * this.ep2 - ds / 30.0 * (61.0 + 90.0 * t + 298.0 * c + 45.0 * ts - 252.0 * this.ep2 - 3.0 * cs)));
        lon = Proj4js.common.adjust_lon(this.long0 + (d * (1.0 - ds / 6.0 * (1.0 + 2.0 * t + c - ds / 20.0 * (5.0 - 2.0 * c + 28.0 * t - 3.0 * cs + 8.0 * this.ep2 + 24.0 * ts))) / cos_phi));
      } else {
        lat = Proj4js.common.HALF_PI * Proj4js.common.sign(y);
        lon = this.long0;
      }
    }
    p.x = lon;
    p.y = lat;
    return p;
  } // tmercInv()
};
/* ======================================================================
    defs/GOOGLE.js
   ====================================================================== */

Proj4js.defs["GOOGLE"]="+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";
Proj4js.defs["EPSG:900913"]=Proj4js.defs["GOOGLE"];
/* ======================================================================
    projCode/gstmerc.js
   ====================================================================== */

Proj4js.Proj.gstmerc = {
  init : function() {

    // array of:  a, b, lon0, lat0, k0, x0, y0
      var temp= this.b / this.a;
      this.e= Math.sqrt(1.0 - temp*temp);
      this.lc= this.long0;
      this.rs= Math.sqrt(1.0+this.e*this.e*Math.pow(Math.cos(this.lat0),4.0)/(1.0-this.e*this.e));
      var sinz= Math.sin(this.lat0);
      var pc= Math.asin(sinz/this.rs);
      var sinzpc= Math.sin(pc);
      this.cp= Proj4js.common.latiso(0.0,pc,sinzpc)-this.rs*Proj4js.common.latiso(this.e,this.lat0,sinz);
      this.n2= this.k0*this.a*Math.sqrt(1.0-this.e*this.e)/(1.0-this.e*this.e*sinz*sinz);
      this.xs= this.x0;
      this.ys= this.y0-this.n2*pc;

      if (!this.title) this.title = "Gauss Schreiber transverse mercator";
    },


    // forward equations--mapping lat,long to x,y
    // -----------------------------------------------------------------
    forward : function(p) {

      var lon= p.x;
      var lat= p.y;

      var L= this.rs*(lon-this.lc);
      var Ls= this.cp+(this.rs*Proj4js.common.latiso(this.e,lat,Math.sin(lat)));
      var lat1= Math.asin(Math.sin(L)/Proj4js.common.cosh(Ls));
      var Ls1= Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));
      p.x= this.xs+(this.n2*Ls1);
      p.y= this.ys+(this.n2*Math.atan(Proj4js.common.sinh(Ls)/Math.cos(L)));
      return p;
    },

  // inverse equations--mapping x,y to lat/long
  // -----------------------------------------------------------------
  inverse : function(p) {

    var x= p.x;
    var y= p.y;

    var L= Math.atan(Proj4js.common.sinh((x-this.xs)/this.n2)/Math.cos((y-this.ys)/this.n2));
    var lat1= Math.asin(Math.sin((y-this.ys)/this.n2)/Proj4js.common.cosh((x-this.xs)/this.n2));
    var LC= Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));
    p.x= this.lc+L/this.rs;
    p.y= Proj4js.common.invlatiso(this.e,(LC-this.cp)/this.rs);
    return p;
  }

};
/* ======================================================================
    projCode/ortho.js
   ====================================================================== */

/*******************************************************************************
NAME                             ORTHOGRAPHIC 

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Orthographic projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE
----------              ----
T. Mittan		Mar, 1993

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/

Proj4js.Proj.ortho = {

  /* Initialize the Orthographic projection
    -------------------------------------*/
  init: function(def) {
    //double temp;			/* temporary variable		*/

    /* Place parameters in static storage for common use
      -------------------------------------------------*/;
    this.sin_p14=Math.sin(this.lat0);
    this.cos_p14=Math.cos(this.lat0);	
  },


  /* Orthographic forward equations--mapping lat,long to x,y
    ---------------------------------------------------*/
  forward: function(p) {
    var sinphi, cosphi;	/* sin and cos value				*/
    var dlon;		/* delta longitude value			*/
    var coslon;		/* cos of longitude				*/
    var ksp;		/* scale factor					*/
    var g;		
    var lon=p.x;
    var lat=p.y;	
    /* Forward equations
      -----------------*/
    dlon = Proj4js.common.adjust_lon(lon - this.long0);

    sinphi=Math.sin(lat);
    cosphi=Math.cos(lat);	

    coslon = Math.cos(dlon);
    g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon;
    ksp = 1.0;
    if ((g > 0) || (Math.abs(g) <= Proj4js.common.EPSLN)) {
      var x = this.a * ksp * cosphi * Math.sin(dlon);
      var y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);
    } else {
      Proj4js.reportError("orthoFwdPointError");
    }
    p.x=x;
    p.y=y;
    return p;
  },


  inverse: function(p) {
    var rh;		/* height above ellipsoid			*/
    var z;		/* angle					*/
    var sinz,cosz;	/* sin of z and cos of z			*/
    var temp;
    var con;
    var lon , lat;
    /* Inverse equations
      -----------------*/
    p.x -= this.x0;
    p.y -= this.y0;
    rh = Math.sqrt(p.x * p.x + p.y * p.y);
    if (rh > this.a + .0000001) {
      Proj4js.reportError("orthoInvDataError");
    }
    z = Proj4js.common.asinz(rh / this.a);

    sinz=Math.sin(z);
    cosz=Math.cos(z);

    lon = this.long0;
    if (Math.abs(rh) <= Proj4js.common.EPSLN) {
      lat = this.lat0; 
    }
    lat = Proj4js.common.asinz(cosz * this.sin_p14 + (p.y * sinz * this.cos_p14)/rh);
    con = Math.abs(this.lat0) - Proj4js.common.HALF_PI;
    if (Math.abs(con) <= Proj4js.common.EPSLN) {
       if (this.lat0 >= 0) {
          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x, -p.y));
       } else {
          lon = Proj4js.common.adjust_lon(this.long0 -Math.atan2(-p.x, p.y));
       }
    }
    con = cosz - this.sin_p14 * Math.sin(lat);
    p.x=lon;
    p.y=lat;
    return p;
  }
};


/* ======================================================================
    projCode/somerc.js
   ====================================================================== */

/*******************************************************************************
NAME                       SWISS OBLIQUE MERCATOR

PURPOSE:	Swiss projection.
WARNING:  X and Y are inverted (weird) in the swiss coordinate system. Not
   here, since we want X to be horizontal and Y vertical.

ALGORITHM REFERENCES
1. "Formules et constantes pour le Calcul pour la
 projection cylindrique conforme à axe oblique et pour la transformation entre
 des systèmes de référence".
 http://www.swisstopo.admin.ch/internet/swisstopo/fr/home/topics/survey/sys/refsys/switzerland.parsysrelated1.31216.downloadList.77004.DownloadFile.tmp/swissprojectionfr.pdf

*******************************************************************************/

Proj4js.Proj.somerc = {

  init: function() {
    var phy0 = this.lat0;
    this.lambda0 = this.long0;
    var sinPhy0 = Math.sin(phy0);
    var semiMajorAxis = this.a;
    var invF = this.rf;
    var flattening = 1 / invF;
    var e2 = 2 * flattening - Math.pow(flattening, 2);
    var e = this.e = Math.sqrt(e2);
    this.R = this.k0 * semiMajorAxis * Math.sqrt(1 - e2) / (1 - e2 * Math.pow(sinPhy0, 2.0));
    this.alpha = Math.sqrt(1 + e2 / (1 - e2) * Math.pow(Math.cos(phy0), 4.0));
    this.b0 = Math.asin(sinPhy0 / this.alpha);
    this.K = Math.log(Math.tan(Math.PI / 4.0 + this.b0 / 2.0))
            - this.alpha
            * Math.log(Math.tan(Math.PI / 4.0 + phy0 / 2.0))
            + this.alpha
            * e / 2
            * Math.log((1 + e * sinPhy0)
            / (1 - e * sinPhy0));
  },


  forward: function(p) {
    var Sa1 = Math.log(Math.tan(Math.PI / 4.0 - p.y / 2.0));
    var Sa2 = this.e / 2.0
            * Math.log((1 + this.e * Math.sin(p.y))
            / (1 - this.e * Math.sin(p.y)));
    var S = -this.alpha * (Sa1 + Sa2) + this.K;

        // spheric latitude
    var b = 2.0 * (Math.atan(Math.exp(S)) - Math.PI / 4.0);

        // spheric longitude
    var I = this.alpha * (p.x - this.lambda0);

        // psoeudo equatorial rotation
    var rotI = Math.atan(Math.sin(I)
            / (Math.sin(this.b0) * Math.tan(b) +
               Math.cos(this.b0) * Math.cos(I)));

    var rotB = Math.asin(Math.cos(this.b0) * Math.sin(b) -
                         Math.sin(this.b0) * Math.cos(b) * Math.cos(I));

    p.y = this.R / 2.0
            * Math.log((1 + Math.sin(rotB)) / (1 - Math.sin(rotB)))
            + this.y0;
    p.x = this.R * rotI + this.x0;
    return p;
  },

  inverse: function(p) {
    var Y = p.x - this.x0;
    var X = p.y - this.y0;

    var rotI = Y / this.R;
    var rotB = 2 * (Math.atan(Math.exp(X / this.R)) - Math.PI / 4.0);

    var b = Math.asin(Math.cos(this.b0) * Math.sin(rotB)
            + Math.sin(this.b0) * Math.cos(rotB) * Math.cos(rotI));
    var I = Math.atan(Math.sin(rotI)
            / (Math.cos(this.b0) * Math.cos(rotI) - Math.sin(this.b0)
            * Math.tan(rotB)));

    var lambda = this.lambda0 + I / this.alpha;

    var S = 0.0;
    var phy = b;
    var prevPhy = -1000.0;
    var iteration = 0;
    while (Math.abs(phy - prevPhy) > 0.0000001)
    {
      if (++iteration > 20)
      {
        Proj4js.reportError("omercFwdInfinity");
        return;
      }
      //S = Math.log(Math.tan(Math.PI / 4.0 + phy / 2.0));
      S = 1.0
              / this.alpha
              * (Math.log(Math.tan(Math.PI / 4.0 + b / 2.0)) - this.K)
              + this.e
              * Math.log(Math.tan(Math.PI / 4.0
              + Math.asin(this.e * Math.sin(phy))
              / 2.0));
      prevPhy = phy;
      phy = 2.0 * Math.atan(Math.exp(S)) - Math.PI / 2.0;
    }

    p.x = lambda;
    p.y = phy;
    return p;
  }
};
/* ======================================================================
    projCode/stere.js
   ====================================================================== */


// Initialize the Stereographic projection

Proj4js.Proj.stere = {
  ssfn_: function(phit, sinphi, eccen) {
  	sinphi *= eccen;
  	return (Math.tan (.5 * (Proj4js.common.HALF_PI + phit)) * Math.pow((1. - sinphi) / (1. + sinphi), .5 * eccen));
  },
  TOL:	1.e-8,
  NITER:	8,
  CONV:	1.e-10,
  S_POLE:	0,
  N_POLE:	1,
  OBLIQ:	2,
  EQUIT:	3,

  init : function() {
  	this.phits = this.lat_ts ? this.lat_ts : Proj4js.common.HALF_PI;
    var t = Math.abs(this.lat0);
  	if ((Math.abs(t) - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
  		this.mode = this.lat0 < 0. ? this.S_POLE : this.N_POLE;
  	} else {
  		this.mode = t > Proj4js.common.EPSLN ? this.OBLIQ : this.EQUIT;
    }
  	this.phits = Math.abs(this.phits);
  	if (this.es) {
  		var X;

  		switch (this.mode) {
  		case this.N_POLE:
  		case this.S_POLE:
  			if (Math.abs(this.phits - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
  				this.akm1 = 2. * this.k0 / Math.sqrt(Math.pow(1+this.e,1+this.e)*Math.pow(1-this.e,1-this.e));
  			} else {
          t = Math.sin(this.phits);
  				this.akm1 = Math.cos(this.phits) / Proj4js.common.tsfnz(this.e, this.phits, t);
  				t *= this.e;
  				this.akm1 /= Math.sqrt(1. - t * t);
  			}
  			break;
  		case this.EQUIT:
  			this.akm1 = 2. * this.k0;
  			break;
  		case this.OBLIQ:
  			t = Math.sin(this.lat0);
  			X = 2. * Math.atan(this.ssfn_(this.lat0, t, this.e)) - Proj4js.common.HALF_PI;
  			t *= this.e;
  			this.akm1 = 2. * this.k0 * Math.cos(this.lat0) / Math.sqrt(1. - t * t);
  			this.sinX1 = Math.sin(X);
  			this.cosX1 = Math.cos(X);
  			break;
  		}
  	} else {
  		switch (this.mode) {
  		case this.OBLIQ:
  			this.sinph0 = Math.sin(this.lat0);
  			this.cosph0 = Math.cos(this.lat0);
  		case this.EQUIT:
  			this.akm1 = 2. * this.k0;
  			break;
  		case this.S_POLE:
  		case this.N_POLE:
  			this.akm1 = Math.abs(this.phits - Proj4js.common.HALF_PI) >= Proj4js.common.EPSLN ?
  			   Math.cos(this.phits) / Math.tan(Proj4js.common.FORTPI - .5 * this.phits) :
  			   2. * this.k0 ;
  			break;
  		}
  	}
  }, 

// Stereographic forward equations--mapping lat,long to x,y
  forward: function(p) {
    var lon = p.x;
    lon = Proj4js.common.adjust_lon(lon - this.long0);
    var lat = p.y;
    var x, y;
    
    if (this.sphere) {
    	var  sinphi, cosphi, coslam, sinlam;

    	sinphi = Math.sin(lat);
    	cosphi = Math.cos(lat);
    	coslam = Math.cos(lon);
    	sinlam = Math.sin(lon);
    	switch (this.mode) {
    	case this.EQUIT:
    		y = 1. + cosphi * coslam;
    		if (y <= Proj4js.common.EPSLN) {
          F_ERROR;
        }
        y = this.akm1 / y;
    		x = y * cosphi * sinlam;
        y *= sinphi;
    		break;
    	case this.OBLIQ:
    		y = 1. + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
    		if (y <= Proj4js.common.EPSLN) {
          F_ERROR;
        }
        y = this.akm1 / y;
    		x = y * cosphi * sinlam;
    		y *= this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
    		break;
    	case this.N_POLE:
    		coslam = -coslam;
    		lat = -lat;
        //Note  no break here so it conitnues through S_POLE
    	case this.S_POLE:
    		if (Math.abs(lat - Proj4js.common.HALF_PI) < this.TOL) {
          F_ERROR;
        }
        y = this.akm1 * Math.tan(Proj4js.common.FORTPI + .5 * lat);
    		x = sinlam * y;
    		y *= coslam;
    		break;
    	}
    } else {
    	coslam = Math.cos(lon);
    	sinlam = Math.sin(lon);
    	sinphi = Math.sin(lat);
    	if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
        X = 2. * Math.atan(this.ssfn_(lat, sinphi, this.e));
    		sinX = Math.sin(X - Proj4js.common.HALF_PI);
    		cosX = Math.cos(X);
    	}
    	switch (this.mode) {
    	case this.OBLIQ:
    		A = this.akm1 / (this.cosX1 * (1. + this.sinX1 * sinX + this.cosX1 * cosX * coslam));
    		y = A * (this.cosX1 * sinX - this.sinX1 * cosX * coslam);
    		x = A * cosX;
    		break;
    	case this.EQUIT:
    		A = 2. * this.akm1 / (1. + cosX * coslam);
    		y = A * sinX;
    		x = A * cosX;
    		break;
    	case this.S_POLE:
    		lat = -lat;
    		coslam = - coslam;
    		sinphi = -sinphi;
    	case this.N_POLE:
    		x = this.akm1 * Proj4js.common.tsfnz(this.e, lat, sinphi);
    		y = - x * coslam;
    		break;
    	}
    	x = x * sinlam;
    }
    p.x = x*this.a + this.x0;
    p.y = y*this.a + this.y0;
    return p;
  },


//* Stereographic inverse equations--mapping x,y to lat/long
  inverse: function(p) {
    var x = (p.x - this.x0)/this.a;   /* descale and de-offset */
    var y = (p.y - this.y0)/this.a;
    var lon, lat;

    var cosphi, sinphi, tp=0.0, phi_l=0.0, rho, halfe=0.0, pi2=0.0;
    var i;

    if (this.sphere) {
    	var  c, rh, sinc, cosc;

      rh = Math.sqrt(x*x + y*y);
      c = 2. * Math.atan(rh / this.akm1);
    	sinc = Math.sin(c);
    	cosc = Math.cos(c);
    	lon = 0.;
    	switch (this.mode) {
    	case this.EQUIT:
    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
    			lat = 0.;
    		} else {
    			lat = Math.asin(y * sinc / rh);
        }
    		if (cosc != 0. || x != 0.) lon = Math.atan2(x * sinc, cosc * rh);
    		break;
    	case this.OBLIQ:
    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
    			lat = this.phi0;
    		} else {
    			lat = Math.asin(cosc * sinph0 + y * sinc * cosph0 / rh);
        }
        c = cosc - sinph0 * Math.sin(lat);
    		if (c != 0. || x != 0.) {
    			lon = Math.atan2(x * sinc * cosph0, c * rh);
        }
    		break;
    	case this.N_POLE:
    		y = -y;
    	case this.S_POLE:
    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
    			lat = this.phi0;
    		} else {
    			lat = Math.asin(this.mode == this.S_POLE ? -cosc : cosc);
        }
    		lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
    		break;
    	}
        p.x = Proj4js.common.adjust_lon(lon + this.long0);
        p.y = lat;
    } else {
    	rho = Math.sqrt(x*x + y*y);
    	switch (this.mode) {
    	case this.OBLIQ:
    	case this.EQUIT:
        tp = 2. * Math.atan2(rho * this.cosX1 , this.akm1);
    		cosphi = Math.cos(tp);
    		sinphi = Math.sin(tp);
        if( rho == 0.0 ) {
    		  phi_l = Math.asin(cosphi * this.sinX1);
        } else {
    		  phi_l = Math.asin(cosphi * this.sinX1 + (y * sinphi * this.cosX1 / rho));
        }

    		tp = Math.tan(.5 * (Proj4js.common.HALF_PI + phi_l));
    		x *= sinphi;
    		y = rho * this.cosX1 * cosphi - y * this.sinX1* sinphi;
    		pi2 = Proj4js.common.HALF_PI;
    		halfe = .5 * this.e;
    		break;
    	case this.N_POLE:
    		y = -y;
    	case this.S_POLE:
        tp = - rho / this.akm1;
    		phi_l = Proj4js.common.HALF_PI - 2. * Math.atan(tp);
    		pi2 = -Proj4js.common.HALF_PI;
    		halfe = -.5 * this.e;
    		break;
    	}
    	for (i = this.NITER; i--; phi_l = lat) { //check this
    		sinphi = this.e * Math.sin(phi_l);
    		lat = 2. * Math.atan(tp * Math.pow((1.+sinphi)/(1.-sinphi), halfe)) - pi2;
    		if (Math.abs(phi_l - lat) < this.CONV) {
    			if (this.mode == this.S_POLE) lat = -lat;
    			lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
          p.x = Proj4js.common.adjust_lon(lon + this.long0);
          p.y = lat;
    			return p;
    		}
    	}
    }
  }
}; 
/* ======================================================================
    projCode/nzmg.js
   ====================================================================== */

/*******************************************************************************
NAME                            NEW ZEALAND MAP GRID

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the New Zealand Map Grid projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.


ALGORITHM REFERENCES

1.  Department of Land and Survey Technical Circular 1973/32
      http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf

2.  OSG Technical Report 4.1
      http://www.linz.govt.nz/docs/miscellaneous/nzmg.pdf


IMPLEMENTATION NOTES

The two references use different symbols for the calculated values. This
implementation uses the variable names similar to the symbols in reference [1].

The alogrithm uses different units for delta latitude and delta longitude.
The delta latitude is assumed to be in units of seconds of arc x 10^-5.
The delta longitude is the usual radians. Look out for these conversions.

The algorithm is described using complex arithmetic. There were three
options:
   * find and use a Javascript library for complex arithmetic
   * write my own complex library
   * expand the complex arithmetic by hand to simple arithmetic

This implementation has expanded the complex multiplication operations
into parallel simple arithmetic operations for the real and imaginary parts.
The imaginary part is way over to the right of the display; this probably
violates every coding standard in the world, but, to me, it makes it much
more obvious what is going on.

The following complex operations are used:
   - addition
   - multiplication
   - division
   - complex number raised to integer power
   - summation

A summary of complex arithmetic operations:
   (from http://en.wikipedia.org/wiki/Complex_arithmetic)
   addition:       (a + bi) + (c + di) = (a + c) + (b + d)i
   subtraction:    (a + bi) - (c + di) = (a - c) + (b - d)i
   multiplication: (a + bi) x (c + di) = (ac - bd) + (bc + ad)i
   division:       (a + bi) / (c + di) = [(ac + bd)/(cc + dd)] + [(bc - ad)/(cc + dd)]i

The algorithm needs to calculate summations of simple and complex numbers. This is
implemented using a for-loop, pre-loading the summed value to zero.

The algorithm needs to calculate theta^2, theta^3, etc while doing a summation.
There are three possible implementations:
   - use Math.pow in the summation loop - except for complex numbers
   - precalculate the values before running the loop
   - calculate theta^n = theta^(n-1) * theta during the loop
This implementation uses the third option for both real and complex arithmetic.

For example
   psi_n = 1;
   sum = 0;
   for (n = 1; n <=6; n++) {
      psi_n1 = psi_n * psi;       // calculate psi^(n+1)
      psi_n = psi_n1;
      sum = sum + A[n] * psi_n;
   }


TEST VECTORS

NZMG E, N:         2487100.638      6751049.719     metres
NZGD49 long, lat:      172.739194       -34.444066  degrees

NZMG E, N:         2486533.395      6077263.661     metres
NZGD49 long, lat:      172.723106       -40.512409  degrees

NZMG E, N:         2216746.425      5388508.765     metres
NZGD49 long, lat:      169.172062       -46.651295  degrees

Note that these test vectors convert from NZMG metres to lat/long referenced
to NZGD49, not the more usual WGS84. The difference is about 70m N/S and about
10m E/W.

These test vectors are provided in reference [1]. Many more test
vectors are available in
   http://www.linz.govt.nz/docs/topography/topographicdata/placenamesdatabase/nznamesmar08.zip
which is a catalog of names on the 260-series maps.


EPSG CODES

NZMG     EPSG:27200
NZGD49   EPSG:4272

http://spatialreference.org/ defines these as
  Proj4js.defs["EPSG:4272"] = "+proj=longlat +ellps=intl +datum=nzgd49 +no_defs ";
  Proj4js.defs["EPSG:27200"] = "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +datum=nzgd49 +units=m +no_defs ";


LICENSE
  Copyright: Stephen Irons 2008
  Released under terms of the LGPL as per: http://www.gnu.org/copyleft/lesser.html

*******************************************************************************/


/**
  Initialize New Zealand Map Grip projection
*/

Proj4js.Proj.nzmg = {

  /**
   * iterations: Number of iterations to refine inverse transform.
   *     0 -> km accuracy
   *     1 -> m accuracy -- suitable for most mapping applications
   *     2 -> mm accuracy
   */
  iterations: 1,

  init : function() {
    this.A = new Array();
    this.A[1]  = +0.6399175073;
    this.A[2]  = -0.1358797613;
    this.A[3]  = +0.063294409;
    this.A[4]  = -0.02526853;
    this.A[5]  = +0.0117879;
    this.A[6]  = -0.0055161;
    this.A[7]  = +0.0026906;
    this.A[8]  = -0.001333;
    this.A[9]  = +0.00067;
    this.A[10] = -0.00034;

    this.B_re = new Array();        this.B_im = new Array();
    this.B_re[1] = +0.7557853228;   this.B_im[1] =  0.0;
    this.B_re[2] = +0.249204646;    this.B_im[2] = +0.003371507;
    this.B_re[3] = -0.001541739;    this.B_im[3] = +0.041058560;
    this.B_re[4] = -0.10162907;     this.B_im[4] = +0.01727609;
    this.B_re[5] = -0.26623489;     this.B_im[5] = -0.36249218;
    this.B_re[6] = -0.6870983;      this.B_im[6] = -1.1651967;

    this.C_re = new Array();        this.C_im = new Array();
    this.C_re[1] = +1.3231270439;   this.C_im[1] =  0.0;
    this.C_re[2] = -0.577245789;    this.C_im[2] = -0.007809598;
    this.C_re[3] = +0.508307513;    this.C_im[3] = -0.112208952;
    this.C_re[4] = -0.15094762;     this.C_im[4] = +0.18200602;
    this.C_re[5] = +1.01418179;     this.C_im[5] = +1.64497696;
    this.C_re[6] = +1.9660549;      this.C_im[6] = +2.5127645;

    this.D = new Array();
    this.D[1] = +1.5627014243;
    this.D[2] = +0.5185406398;
    this.D[3] = -0.03333098;
    this.D[4] = -0.1052906;
    this.D[5] = -0.0368594;
    this.D[6] = +0.007317;
    this.D[7] = +0.01220;
    this.D[8] = +0.00394;
    this.D[9] = -0.0013;
  },

  /**
    New Zealand Map Grid Forward  - long/lat to x/y
    long/lat in radians
  */
  forward : function(p) {
    var lon = p.x;
    var lat = p.y;

    var delta_lat = lat - this.lat0;
    var delta_lon = lon - this.long0;

    // 1. Calculate d_phi and d_psi    ...                          // and d_lambda
    // For this algorithm, delta_latitude is in seconds of arc x 10-5, so we need to scale to those units. Longitude is radians.
    var d_phi = delta_lat / Proj4js.common.SEC_TO_RAD * 1E-5;       var d_lambda = delta_lon;
    var d_phi_n = 1;  // d_phi^0

    var d_psi = 0;
    for (n = 1; n <= 10; n++) {
      d_phi_n = d_phi_n * d_phi;
      d_psi = d_psi + this.A[n] * d_phi_n;
    }

    // 2. Calculate theta
    var th_re = d_psi;                                              var th_im = d_lambda;

    // 3. Calculate z
    var th_n_re = 1;                                                var th_n_im = 0;  // theta^0
    var th_n_re1;                                                   var th_n_im1;

    var z_re = 0;                                                   var z_im = 0;
    for (n = 1; n <= 6; n++) {
      th_n_re1 = th_n_re*th_re - th_n_im*th_im;                     th_n_im1 = th_n_im*th_re + th_n_re*th_im;
      th_n_re = th_n_re1;                                           th_n_im = th_n_im1;
      z_re = z_re + this.B_re[n]*th_n_re - this.B_im[n]*th_n_im;    z_im = z_im + this.B_im[n]*th_n_re + this.B_re[n]*th_n_im;
    }

    // 4. Calculate easting and northing
    x = (z_im * this.a) + this.x0;
    y = (z_re * this.a) + this.y0;

    p.x = x; p.y = y;

    return p;
  },


  /**
    New Zealand Map Grid Inverse  -  x/y to long/lat
  */
  inverse : function(p) {

    var x = p.x;
    var y = p.y;

    var delta_x = x - this.x0;
    var delta_y = y - this.y0;

    // 1. Calculate z
    var z_re = delta_y / this.a;                                              var z_im = delta_x / this.a;

    // 2a. Calculate theta - first approximation gives km accuracy
    var z_n_re = 1;                                                           var z_n_im = 0;  // z^0
    var z_n_re1;                                                              var z_n_im1;

    var th_re = 0;                                                            var th_im = 0;
    for (n = 1; n <= 6; n++) {
      z_n_re1 = z_n_re*z_re - z_n_im*z_im;                                    z_n_im1 = z_n_im*z_re + z_n_re*z_im;
      z_n_re = z_n_re1;                                                       z_n_im = z_n_im1;
      th_re = th_re + this.C_re[n]*z_n_re - this.C_im[n]*z_n_im;              th_im = th_im + this.C_im[n]*z_n_re + this.C_re[n]*z_n_im;
    }

    // 2b. Iterate to refine the accuracy of the calculation
    //        0 iterations gives km accuracy
    //        1 iteration gives m accuracy -- good enough for most mapping applications
    //        2 iterations bives mm accuracy
    for (i = 0; i < this.iterations; i++) {
       var th_n_re = th_re;                                                      var th_n_im = th_im;
       var th_n_re1;                                                             var th_n_im1;

       var num_re = z_re;                                                        var num_im = z_im;
       for (n = 2; n <= 6; n++) {
         th_n_re1 = th_n_re*th_re - th_n_im*th_im;                               th_n_im1 = th_n_im*th_re + th_n_re*th_im;
         th_n_re = th_n_re1;                                                     th_n_im = th_n_im1;
         num_re = num_re + (n-1)*(this.B_re[n]*th_n_re - this.B_im[n]*th_n_im);  num_im = num_im + (n-1)*(this.B_im[n]*th_n_re + this.B_re[n]*th_n_im);
       }

       th_n_re = 1;                                                              th_n_im = 0;
       var den_re = this.B_re[1];                                                var den_im = this.B_im[1];
       for (n = 2; n <= 6; n++) {
         th_n_re1 = th_n_re*th_re - th_n_im*th_im;                               th_n_im1 = th_n_im*th_re + th_n_re*th_im;
         th_n_re = th_n_re1;                                                     th_n_im = th_n_im1;
         den_re = den_re + n * (this.B_re[n]*th_n_re - this.B_im[n]*th_n_im);    den_im = den_im + n * (this.B_im[n]*th_n_re + this.B_re[n]*th_n_im);
       }

       // Complex division
       var den2 = den_re*den_re + den_im*den_im;
       th_re = (num_re*den_re + num_im*den_im) / den2;                           th_im = (num_im*den_re - num_re*den_im) / den2;
    }

    // 3. Calculate d_phi              ...                                    // and d_lambda
    var d_psi = th_re;                                                        var d_lambda = th_im;
    var d_psi_n = 1;  // d_psi^0

    var d_phi = 0;
    for (n = 1; n <= 9; n++) {
       d_psi_n = d_psi_n * d_psi;
       d_phi = d_phi + this.D[n] * d_psi_n;
    }

    // 4. Calculate latitude and longitude
    // d_phi is calcuated in second of arc * 10^-5, so we need to scale back to radians. d_lambda is in radians.
    var lat = this.lat0 + (d_phi * Proj4js.common.SEC_TO_RAD * 1E5);
    var lon = this.long0 +  d_lambda;

    p.x = lon;
    p.y = lat;

    return p;
  }
};
/* ======================================================================
    projCode/mill.js
   ====================================================================== */

/*******************************************************************************
NAME                    MILLER CYLINDRICAL 

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Miller Cylindrical projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE            
----------              ----           
T. Mittan		March, 1993

This function was adapted from the Lambert Azimuthal Equal Area projection
code (FORTRAN) in the General Cartographic Transformation Package software
which is available from the U.S. Geological Survey National Mapping Division.
 
ALGORITHM REFERENCES

1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.

2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

3.  "Software Documentation for GCTP General Cartographic Transformation
    Package", U.S. Geological Survey National Mapping Division, May 1982.
*******************************************************************************/

Proj4js.Proj.mill = {

/* Initialize the Miller Cylindrical projection
  -------------------------------------------*/
  init: function() {
    //no-op
  },


  /* Miller Cylindrical forward equations--mapping lat,long to x,y
    ------------------------------------------------------------*/
  forward: function(p) {
    var lon=p.x;
    var lat=p.y;
    /* Forward equations
      -----------------*/
    var dlon = Proj4js.common.adjust_lon(lon -this.long0);
    var x = this.x0 + this.a * dlon;
    var y = this.y0 + this.a * Math.log(Math.tan((Proj4js.common.PI / 4.0) + (lat / 2.5))) * 1.25;

    p.x=x;
    p.y=y;
    return p;
  },//millFwd()

  /* Miller Cylindrical inverse equations--mapping x,y to lat/long
    ------------------------------------------------------------*/
  inverse: function(p) {
    p.x -= this.x0;
    p.y -= this.y0;

    var lon = Proj4js.common.adjust_lon(this.long0 + p.x /this.a);
    var lat = 2.5 * (Math.atan(Math.exp(0.8*p.y/this.a)) - Proj4js.common.PI / 4.0);

    p.x=lon;
    p.y=lat;
    return p;
  }//millInv()
};
/* ======================================================================
    projCode/gnom.js
   ====================================================================== */

/*****************************************************************************
NAME                             GNOMONIC

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Gnomonic Projection.
                Implementation based on the existing sterea and ortho
                implementations.

PROGRAMMER              DATE
----------              ----
Richard Marsden         November 2009

ALGORITHM REFERENCES

1.  Snyder, John P., "Flattening the Earth - Two Thousand Years of Map 
    Projections", University of Chicago Press 1993

2.  Wolfram Mathworld "Gnomonic Projection"
    http://mathworld.wolfram.com/GnomonicProjection.html
    Accessed: 12th November 2009
******************************************************************************/

Proj4js.Proj.gnom = {

  /* Initialize the Gnomonic projection
    -------------------------------------*/
  init: function(def) {

    /* Place parameters in static storage for common use
      -------------------------------------------------*/
    this.sin_p14=Math.sin(this.lat0);
    this.cos_p14=Math.cos(this.lat0);
    // Approximation for projecting points to the horizon (infinity)
    this.infinity_dist = 1000 * this.a;
    this.rc = 1;
  },


  /* Gnomonic forward equations--mapping lat,long to x,y
    ---------------------------------------------------*/
  forward: function(p) {
    var sinphi, cosphi;	/* sin and cos value				*/
    var dlon;		/* delta longitude value			*/
    var coslon;		/* cos of longitude				*/
    var ksp;		/* scale factor					*/
    var g;		
    var lon=p.x;
    var lat=p.y;	
    /* Forward equations
      -----------------*/
    dlon = Proj4js.common.adjust_lon(lon - this.long0);

    sinphi=Math.sin(lat);
    cosphi=Math.cos(lat);	

    coslon = Math.cos(dlon);
    g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon;
    ksp = 1.0;
    if ((g > 0) || (Math.abs(g) <= Proj4js.common.EPSLN)) {
      x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon) / g;
      y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon) / g;
    } else {
      Proj4js.reportError("orthoFwdPointError");

      // Point is in the opposing hemisphere and is unprojectable
      // We still need to return a reasonable point, so we project 
      // to infinity, on a bearing 
      // equivalent to the northern hemisphere equivalent
      // This is a reasonable approximation for short shapes and lines that 
      // straddle the horizon.

      x = this.x0 + this.infinity_dist * cosphi * Math.sin(dlon);
      y = this.y0 + this.infinity_dist * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);

    }
    p.x=x;
    p.y=y;
    return p;
  },


  inverse: function(p) {
    var rh;		/* Rho */
    var z;		/* angle */
    var sinc, cosc;
    var c;
    var lon , lat;

    /* Inverse equations
      -----------------*/
    p.x = (p.x - this.x0) / this.a;
    p.y = (p.y - this.y0) / this.a;

    p.x /= this.k0;
    p.y /= this.k0;

    if ( (rh = Math.sqrt(p.x * p.x + p.y * p.y)) ) {
      c = Math.atan2(rh, this.rc);
      sinc = Math.sin(c);
      cosc = Math.cos(c);

      lat = Proj4js.common.asinz(cosc*this.sin_p14 + (p.y*sinc*this.cos_p14) / rh);
      lon = Math.atan2(p.x*sinc, rh*this.cos_p14*cosc - p.y*this.sin_p14*sinc);
      lon = Proj4js.common.adjust_lon(this.long0+lon);
    } else {
      lat = this.phic0;
      lon = 0.0;
    }
 
    p.x=lon;
    p.y=lat;
    return p;
  }
};


/* ======================================================================
    projCode/sinu.js
   ====================================================================== */

/*******************************************************************************
NAME                  		SINUSOIDAL

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Sinusoidal projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE            
----------              ----           
D. Steinwand, EROS      May, 1991     

This function was adapted from the Sinusoidal projection code (FORTRAN) in the 
General Cartographic Transformation Package software which is available from 
the U.S. Geological Survey National Mapping Division.
 
ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  "Software Documentation for GCTP General Cartographic Transformation
    Package", U.S. Geological Survey National Mapping Division, May 1982.
*******************************************************************************/

Proj4js.Proj.sinu = {

	/* Initialize the Sinusoidal projection
	  ------------------------------------*/
	init: function() {
		/* Place parameters in static storage for common use
		  -------------------------------------------------*/
		this.R = 6370997.0; //Radius of earth
	},

	/* Sinusoidal forward equations--mapping lat,long to x,y
	-----------------------------------------------------*/
	forward: function(p) {
		var x,y,delta_lon;	
		var lon=p.x;
		var lat=p.y;	
		/* Forward equations
		-----------------*/
		delta_lon = Proj4js.common.adjust_lon(lon - this.long0);
		x = this.R * delta_lon * Math.cos(lat) + this.x0;
		y = this.R * lat + this.y0;

		p.x=x;
		p.y=y;	
		return p;
	},

	inverse: function(p) {
		var lat,temp,lon;	

		/* Inverse equations
		  -----------------*/
		p.x -= this.x0;
		p.y -= this.y0;
		lat = p.y / this.R;
		if (Math.abs(lat) > Proj4js.common.HALF_PI) {
		    Proj4js.reportError("sinu:Inv:DataError");
		}
		temp = Math.abs(lat) - Proj4js.common.HALF_PI;
		if (Math.abs(temp) > Proj4js.common.EPSLN) {
			temp = this.long0+ p.x / (this.R *Math.cos(lat));
			lon = Proj4js.common.adjust_lon(temp);
		} else {
			lon = this.long0;
		}
		  
		p.x=lon;
		p.y=lat;
		return p;
	}
};


/* ======================================================================
    projCode/vandg.js
   ====================================================================== */

/*******************************************************************************
NAME                    VAN DER GRINTEN 

PURPOSE:	Transforms input Easting and Northing to longitude and
		latitude for the Van der Grinten projection.  The
		Easting and Northing must be in meters.  The longitude
		and latitude values will be returned in radians.

PROGRAMMER              DATE            
----------              ----           
T. Mittan		March, 1993

This function was adapted from the Van Der Grinten projection code
(FORTRAN) in the General Cartographic Transformation Package software
which is available from the U.S. Geological Survey National Mapping Division.
 
ALGORITHM REFERENCES

1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.

2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

3.  "Software Documentation for GCTP General Cartographic Transformation
    Package", U.S. Geological Survey National Mapping Division, May 1982.
*******************************************************************************/

Proj4js.Proj.vandg = {

/* Initialize the Van Der Grinten projection
  ----------------------------------------*/
	init: function() {
		this.R = 6370997.0; //Radius of earth
	},

	forward: function(p) {

		var lon=p.x;
		var lat=p.y;	

		/* Forward equations
		-----------------*/
		var dlon = Proj4js.common.adjust_lon(lon - this.long0);
		var x,y;

		if (Math.abs(lat) <= Proj4js.common.EPSLN) {
			x = this.x0  + this.R * dlon;
			y = this.y0;
		}
		var theta = Proj4js.common.asinz(2.0 * Math.abs(lat / Proj4js.common.PI));
		if ((Math.abs(dlon) <= Proj4js.common.EPSLN) || (Math.abs(Math.abs(lat) - Proj4js.common.HALF_PI) <= Proj4js.common.EPSLN)) {
			x = this.x0;
			if (lat >= 0) {
				y = this.y0 + Proj4js.common.PI * this.R * Math.tan(.5 * theta);
			} else {
				y = this.y0 + Proj4js.common.PI * this.R * - Math.tan(.5 * theta);
			}
			//  return(OK);
		}
		var al = .5 * Math.abs((Proj4js.common.PI / dlon) - (dlon / Proj4js.common.PI));
		var asq = al * al;
		var sinth = Math.sin(theta);
		var costh = Math.cos(theta);

		var g = costh / (sinth + costh - 1.0);
		var gsq = g * g;
		var m = g * (2.0 / sinth - 1.0);
		var msq = m * m;
		var con = Proj4js.common.PI * this.R * (al * (g - msq) + Math.sqrt(asq * (g - msq) * (g - msq) - (msq + asq) * (gsq - msq))) / (msq + asq);
		if (dlon < 0) {
		 con = -con;
		}
		x = this.x0 + con;
		con = Math.abs(con / (Proj4js.common.PI * this.R));
		if (lat >= 0) {
		 y = this.y0 + Proj4js.common.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con);
		} else {
		 y = this.y0 - Proj4js.common.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con);
		}
		p.x = x;
		p.y = y;
		return p;
	},

/* Van Der Grinten inverse equations--mapping x,y to lat/long
  ---------------------------------------------------------*/
	inverse: function(p) {
		var dlon;
		var xx,yy,xys,c1,c2,c3;
		var al,asq;
		var a1;
		var m1;
		var con;
		var th1;
		var d;

		/* inverse equations
		-----------------*/
		p.x -= this.x0;
		p.y -= this.y0;
		con = Proj4js.common.PI * this.R;
		xx = p.x / con;
		yy =p.y / con;
		xys = xx * xx + yy * yy;
		c1 = -Math.abs(yy) * (1.0 + xys);
		c2 = c1 - 2.0 * yy * yy + xx * xx;
		c3 = -2.0 * c1 + 1.0 + 2.0 * yy * yy + xys * xys;
		d = yy * yy / c3 + (2.0 * c2 * c2 * c2 / c3 / c3 / c3 - 9.0 * c1 * c2 / c3 /c3) / 27.0;
		a1 = (c1 - c2 * c2 / 3.0 / c3) / c3;
		m1 = 2.0 * Math.sqrt( -a1 / 3.0);
		con = ((3.0 * d) / a1) / m1;
		if (Math.abs(con) > 1.0) {
			if (con >= 0.0) {
				con = 1.0;
			} else {
				con = -1.0;
			}
		}
		th1 = Math.acos(con) / 3.0;
		if (p.y >= 0) {
			lat = (-m1 *Math.cos(th1 + Proj4js.common.PI / 3.0) - c2 / 3.0 / c3) * Proj4js.common.PI;
		} else {
			lat = -(-m1 * Math.cos(th1 + Proj4js.common.PI / 3.0) - c2 / 3.0 / c3) * Proj4js.common.PI;
		}

		if (Math.abs(xx) < Proj4js.common.EPSLN) {
			lon = this.long0;
		}
		lon = Proj4js.common.adjust_lon(this.long0 + Proj4js.common.PI * (xys - 1.0 + Math.sqrt(1.0 + 2.0 * (xx * xx - yy * yy) + xys * xys)) / 2.0 / xx);

		p.x=lon;
		p.y=lat;
		return p;
	}
};
/* ======================================================================
    projCode/cea.js
   ====================================================================== */

/*******************************************************************************
NAME                    LAMBERT CYLINDRICAL EQUAL AREA

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Lambert Cylindrical Equal Area projection.
                This class of projection includes the Behrmann and 
                Gall-Peters Projections.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE            
----------              ----
R. Marsden              August 2009
Winwaed Software Tech LLC, http://www.winwaed.com

This function was adapted from the Miller Cylindrical Projection in the Proj4JS
library.

Note: This implementation assumes a Spherical Earth. The (commented) code 
has been included for the ellipsoidal forward transform, but derivation of 
the ellispoidal inverse transform is beyond me. Note that most of the 
Proj4JS implementations do NOT currently support ellipsoidal figures. 
Therefore this is not seen as a problem - especially this lack of support 
is explicitly stated here.
 
ALGORITHM REFERENCES

1.  "Cartographic Projection Procedures for the UNIX Environment - 
     A User's Manual" by Gerald I. Evenden, USGS Open File Report 90-284
    and Release 4 Interim Reports (2003)

2.  Snyder, John P., "Flattening the Earth - Two Thousand Years of Map 
    Projections", Univ. Chicago Press, 1993
*******************************************************************************/

Proj4js.Proj.cea = {

/* Initialize the Cylindrical Equal Area projection
  -------------------------------------------*/
  init: function() {
    //no-op
  },


  /* Cylindrical Equal Area forward equations--mapping lat,long to x,y
    ------------------------------------------------------------*/
  forward: function(p) {
    var lon=p.x;
    var lat=p.y;
    /* Forward equations
      -----------------*/
    dlon = Proj4js.common.adjust_lon(lon -this.long0);
    var x = this.x0 + this.a * dlon * Math.cos(this.lat_ts);
    var y = this.y0 + this.a * Math.sin(lat) / Math.cos(this.lat_ts);
   /* Elliptical Forward Transform
      Not implemented due to a lack of a matchign inverse function
    {
      var Sin_Lat = Math.sin(lat);
      var Rn = this.a * (Math.sqrt(1.0e0 - this.es * Sin_Lat * Sin_Lat ));
      x = this.x0 + this.a * dlon * Math.cos(this.lat_ts);
      y = this.y0 + Rn * Math.sin(lat) / Math.cos(this.lat_ts);
    }
   */


    p.x=x;
    p.y=y;
    return p;
  },//ceaFwd()

  /* Cylindrical Equal Area inverse equations--mapping x,y to lat/long
    ------------------------------------------------------------*/
  inverse: function(p) {
    p.x -= this.x0;
    p.y -= this.y0;

    var lon = Proj4js.common.adjust_lon( this.long0 + (p.x / this.a) / Math.cos(this.lat_ts) );

    var lat = Math.asin( (p.y/this.a) * Math.cos(this.lat_ts) );

    p.x=lon;
    p.y=lat;
    return p;
  }//ceaInv()
};
/* ======================================================================
    projCode/eqc.js
   ====================================================================== */

/* similar to equi.js FIXME proj4 uses eqc */
Proj4js.Proj.eqc = {
  init : function() {

      if(!this.x0) this.x0=0;
      if(!this.y0) this.y0=0;
      if(!this.lat0) this.lat0=0;
      if(!this.long0) this.long0=0;
      if(!this.lat_ts) this.lat_ts=0;
      if (!this.title) this.title = "Equidistant Cylindrical (Plate Carre)";

      this.rc= Math.cos(this.lat_ts);
    },


    // forward equations--mapping lat,long to x,y
    // -----------------------------------------------------------------
    forward : function(p) {

      var lon= p.x;
      var lat= p.y;

      var dlon = Proj4js.common.adjust_lon(lon - this.long0);
      var dlat = Proj4js.common.adjust_lat(lat - this.lat0 );
      p.x= this.x0 + (this.a*dlon*this.rc);
      p.y= this.y0 + (this.a*dlat        );
      return p;
    },

  // inverse equations--mapping x,y to lat/long
  // -----------------------------------------------------------------
  inverse : function(p) {

    var x= p.x;
    var y= p.y;

    p.x= Proj4js.common.adjust_lon(this.long0 + ((x - this.x0)/(this.a*this.rc)));
    p.y= Proj4js.common.adjust_lat(this.lat0  + ((y - this.y0)/(this.a        )));
    return p;
  }

};
/* ======================================================================
    projCode/cass.js
   ====================================================================== */

/*******************************************************************************
NAME                            CASSINI

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Cassini projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.
    Ported from PROJ.4.


ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
*******************************************************************************/


//Proj4js.defs["EPSG:28191"] = "+proj=cass +lat_0=31.73409694444445 +lon_0=35.21208055555556 +x_0=170251.555 +y_0=126867.909 +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +units=m +no_defs";

// Initialize the Cassini projection
// -----------------------------------------------------------------

Proj4js.Proj.cass = {
  init : function() {
    if (!this.sphere) {
      this.en = this.pj_enfn(this.es)
      this.m0 = this.pj_mlfn(this.lat0, Math.sin(this.lat0), Math.cos(this.lat0), this.en);
    }
  },

  C1:	.16666666666666666666,
  C2:	.00833333333333333333,
  C3:	.04166666666666666666,
  C4:	.33333333333333333333,
  C5:	.06666666666666666666,


/* Cassini forward equations--mapping lat,long to x,y
  -----------------------------------------------------------------------*/
  forward: function(p) {

    /* Forward equations
      -----------------*/
    var x,y;
    var lam=p.x;
    var phi=p.y;
    lam = Proj4js.common.adjust_lon(lam - this.long0);
    
    if (this.sphere) {
      x = Math.asin(Math.cos(phi) * Math.sin(lam));
      y = Math.atan2(Math.tan(phi) , Math.cos(lam)) - this.phi0;
    } else {
        //ellipsoid
      this.n = Math.sin(phi);
      this.c = Math.cos(phi);
      y = this.pj_mlfn(phi, this.n, this.c, this.en);
      this.n = 1./Math.sqrt(1. - this.es * this.n * this.n);
      this.tn = Math.tan(phi); 
      this.t = this.tn * this.tn;
      this.a1 = lam * this.c;
      this.c *= this.es * this.c / (1 - this.es);
      this.a2 = this.a1 * this.a1;
      x = this.n * this.a1 * (1. - this.a2 * this.t * (this.C1 - (8. - this.t + 8. * this.c) * this.a2 * this.C2));
      y -= this.m0 - this.n * this.tn * this.a2 * (.5 + (5. - this.t + 6. * this.c) * this.a2 * this.C3);
    }
    
    p.x = this.a*x + this.x0;
    p.y = this.a*y + this.y0;
    return p;
  },//cassFwd()

/* Inverse equations
  -----------------*/
  inverse: function(p) {
    p.x -= this.x0;
    p.y -= this.y0;
    var x = p.x/this.a;
    var y = p.y/this.a;
    
    if (this.sphere) {
      this.dd = y + this.lat0;
      phi = Math.asin(Math.sin(this.dd) * Math.cos(x));
      lam = Math.atan2(Math.tan(x), Math.cos(this.dd));
    } else {
      /* ellipsoid */
      ph1 = this.pj_inv_mlfn(this.m0 + y, this.es, this.en);
      this.tn = Math.tan(ph1); 
      this.t = this.tn * this.tn;
      this.n = Math.sin(ph1);
      this.r = 1. / (1. - this.es * this.n * this.n);
      this.n = Math.sqrt(this.r);
      this.r *= (1. - this.es) * this.n;
      this.dd = x / this.n;
      this.d2 = this.dd * this.dd;
      phi = ph1 - (this.n * this.tn / this.r) * this.d2 * (.5 - (1. + 3. * this.t) * this.d2 * this.C3);
      lam = this.dd * (1. + this.t * this.d2 * (-this.C4 + (1. + 3. * this.t) * this.d2 * this.C5)) / Math.cos(ph1);
    }
    p.x = Proj4js.common.adjust_lon(this.long0+lam);
    p.y = phi;
    return p;
  },//lamazInv()


  //code from the PROJ.4 pj_mlfn.c file;  this may be useful for other projections
  pj_enfn: function(es) {
    en = new Array();
    en[0] = this.C00 - es * (this.C02 + es * (this.C04 + es * (this.C06 + es * this.C08)));
    en[1] = es * (this.C22 - es * (this.C04 + es * (this.C06 + es * this.C08)));
    var t = es * es;
    en[2] = t * (this.C44 - es * (this.C46 + es * this.C48));
    t *= es;
    en[3] = t * (this.C66 - es * this.C68);
    en[4] = t * es * this.C88;
    return en;
  },
  
  pj_mlfn: function(phi, sphi, cphi, en) {
    cphi *= sphi;
    sphi *= sphi;
    return(en[0] * phi - cphi * (en[1] + sphi*(en[2]+ sphi*(en[3] + sphi*en[4]))));
  },
  
  pj_inv_mlfn: function(arg, es, en) {
    k = 1./(1.-es);
    phi = arg;
    for (i = Proj4js.common.MAX_ITER; i ; --i) { /* rarely goes over 2 iterations */
      s = Math.sin(phi);
      t = 1. - es * s * s;
      //t = this.pj_mlfn(phi, s, Math.cos(phi), en) - arg;
      //phi -= t * (t * Math.sqrt(t)) * k;
      t = (this.pj_mlfn(phi, s, Math.cos(phi), en) - arg) * (t * Math.sqrt(t)) * k;
      phi -= t;
      if (Math.abs(t) < Proj4js.common.EPSLN)
        return phi;
    }
    Proj4js.reportError("cass:pj_inv_mlfn: Convergence error");
    return phi;
  },

/* meridinal distance for ellipsoid and inverse
**	8th degree - accurate to < 1e-5 meters when used in conjuction
**		with typical major axis values.
**	Inverse determines phi to EPS (1e-11) radians, about 1e-6 seconds.
*/
  C00: 1.0,
  C02: .25,
  C04: .046875,
  C06: .01953125,
  C08: .01068115234375,
  C22: .75,
  C44: .46875,
  C46: .01302083333333333333,
  C48: .00712076822916666666,
  C66: .36458333333333333333,
  C68: .00569661458333333333,
  C88: .3076171875

}
/* ======================================================================
    projCode/gauss.js
   ====================================================================== */


Proj4js.Proj.gauss = {

  init : function() {
    sphi = Math.sin(this.lat0);
    cphi = Math.cos(this.lat0);  
    cphi *= cphi;
    this.rc = Math.sqrt(1.0 - this.es) / (1.0 - this.es * sphi * sphi);
    this.C = Math.sqrt(1.0 + this.es * cphi * cphi / (1.0 - this.es));
    this.phic0 = Math.asin(sphi / this.C);
    this.ratexp = 0.5 * this.C * this.e;
    this.K = Math.tan(0.5 * this.phic0 + Proj4js.common.FORTPI) / (Math.pow(Math.tan(0.5*this.lat0 + Proj4js.common.FORTPI), this.C) * Proj4js.common.srat(this.e*sphi, this.ratexp));
  },

  forward : function(p) {
    var lon = p.x;
    var lat = p.y;

    p.y = 2.0 * Math.atan( this.K * Math.pow(Math.tan(0.5 * lat + Proj4js.common.FORTPI), this.C) * Proj4js.common.srat(this.e * Math.sin(lat), this.ratexp) ) - Proj4js.common.HALF_PI;
    p.x = this.C * lon;
    return p;
  },

  inverse : function(p) {
    var DEL_TOL = 1e-14;
    var lon = p.x / this.C;
    var lat = p.y;
    num = Math.pow(Math.tan(0.5 * lat + Proj4js.common.FORTPI)/this.K, 1./this.C);
    for (var i = Proj4js.common.MAX_ITER; i>0; --i) {
      lat = 2.0 * Math.atan(num * Proj4js.common.srat(this.e * Math.sin(p.y), -0.5 * this.e)) - Proj4js.common.HALF_PI;
      if (Math.abs(lat - p.y) < DEL_TOL) break;
      p.y = lat;
    }	
    /* convergence failed */
    if (!i) {
      Proj4js.reportError("gauss:inverse:convergence failed");
      return null;
    }
    p.x = lon;
    p.y = lat;
    return p;
  }
};

/* ======================================================================
    projCode/omerc.js
   ====================================================================== */

/*******************************************************************************
NAME                       OBLIQUE MERCATOR (HOTINE) 

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Oblique Mercator projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE
----------              ----
T. Mittan		Mar, 1993

ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.
*******************************************************************************/

Proj4js.Proj.omerc = {

  /* Initialize the Oblique Mercator  projection
    ------------------------------------------*/
  init: function() {
    if (!this.mode) this.mode=0;
    if (!this.lon1)   {this.lon1=0;this.mode=1;}
    if (!this.lon2)   this.lon2=0;
    if (!this.lat2)    this.lat2=0;

    /* Place parameters in static storage for common use
      -------------------------------------------------*/
    var temp = this.b/ this.a;
    var es = 1.0 - Math.pow(temp,2);
    var e = Math.sqrt(es);

    this.sin_p20=Math.sin(this.lat0);
    this.cos_p20=Math.cos(this.lat0);

    this.con = 1.0 - this.es * this.sin_p20 * this.sin_p20;
    this.com = Math.sqrt(1.0 - es);
    this.bl = Math.sqrt(1.0 + this.es * Math.pow(this.cos_p20,4.0)/(1.0 - es));
    this.al = this.a * this.bl * this.k0 * this.com / this.con;
    if (Math.abs(this.lat0) < Proj4js.common.EPSLN) {
       this.ts = 1.0;
       this.d = 1.0;
       this.el = 1.0;
    } else {
       this.ts = Proj4js.common.tsfnz(this.e,this.lat0,this.sin_p20);
       this.con = Math.sqrt(this.con);
       this.d = this.bl * this.com / (this.cos_p20 * this.con);
       if ((this.d * this.d - 1.0) > 0.0) {
          if (this.lat0 >= 0.0) {
             this.f = this.d + Math.sqrt(this.d * this.d - 1.0);
          } else {
             this.f = this.d - Math.sqrt(this.d * this.d - 1.0);
          }
       } else {
         this.f = this.d;
       }
       this.el = this.f * Math.pow(this.ts,this.bl);
    }

    //this.longc=52.60353916666667;

    if (this.mode != 0) {
       this.g = .5 * (this.f - 1.0/this.f);
       this.gama = Proj4js.common.asinz(Math.sin(this.alpha) / this.d);
       this.longc= this.longc - Proj4js.common.asinz(this.g * Math.tan(this.gama))/this.bl;

       /* Report parameters common to format B
       -------------------------------------*/
       //genrpt(azimuth * R2D,"Azimuth of Central Line:    ");
       //cenlon(lon_origin);
      // cenlat(lat_origin);

       this.con = Math.abs(this.lat0);
       if ((this.con > Proj4js.common.EPSLN) && (Math.abs(this.con - Proj4js.common.HALF_PI) > Proj4js.common.EPSLN)) {
            this.singam=Math.sin(this.gama);
            this.cosgam=Math.cos(this.gama);

            this.sinaz=Math.sin(this.alpha);
            this.cosaz=Math.cos(this.alpha);

            if (this.lat0>= 0) {
               this.u =  (this.al / this.bl) * Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz);
            } else {
               this.u =  -(this.al / this.bl) *Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz);
            }
          } else {
            Proj4js.reportError("omerc:Init:DataError");
          }
       } else {
       this.sinphi =Math. sin(this.at1);
       this.ts1 = Proj4js.common.tsfnz(this.e,this.lat1,this.sinphi);
       this.sinphi = Math.sin(this.lat2);
       this.ts2 = Proj4js.common.tsfnz(this.e,this.lat2,this.sinphi);
       this.h = Math.pow(this.ts1,this.bl);
       this.l = Math.pow(this.ts2,this.bl);
       this.f = this.el/this.h;
       this.g = .5 * (this.f - 1.0/this.f);
       this.j = (this.el * this.el - this.l * this.h)/(this.el * this.el + this.l * this.h);
       this.p = (this.l - this.h) / (this.l + this.h);
       this.dlon = this.lon1 - this.lon2;
       if (this.dlon < -Proj4js.common.PI) this.lon2 = this.lon2 - 2.0 * Proj4js.common.PI;
       if (this.dlon > Proj4js.common.PI) this.lon2 = this.lon2 + 2.0 * Proj4js.common.PI;
       this.dlon = this.lon1 - this.lon2;
       this.longc = .5 * (this.lon1 + this.lon2) -Math.atan(this.j * Math.tan(.5 * this.bl * this.dlon)/this.p)/this.bl;
       this.dlon  = Proj4js.common.adjust_lon(this.lon1 - this.longc);
       this.gama = Math.atan(Math.sin(this.bl * this.dlon)/this.g);
       this.alpha = Proj4js.common.asinz(this.d * Math.sin(this.gama));

       /* Report parameters common to format A
       -------------------------------------*/

       if (Math.abs(this.lat1 - this.lat2) <= Proj4js.common.EPSLN) {
          Proj4js.reportError("omercInitDataError");
          //return(202);
       } else {
          this.con = Math.abs(this.lat1);
       }
       if ((this.con <= Proj4js.common.EPSLN) || (Math.abs(this.con - HALF_PI) <= Proj4js.common.EPSLN)) {
           Proj4js.reportError("omercInitDataError");
                //return(202);
       } else {
         if (Math.abs(Math.abs(this.lat0) - Proj4js.common.HALF_PI) <= Proj4js.common.EPSLN) {
            Proj4js.reportError("omercInitDataError");
            //return(202);
         }
       }

       this.singam=Math.sin(this.gam);
       this.cosgam=Math.cos(this.gam);

       this.sinaz=Math.sin(this.alpha);
       this.cosaz=Math.cos(this.alpha);  


       if (this.lat0 >= 0) {
          this.u =  (this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz);
       } else {
          this.u = -(this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz);
       }
     }
  },


  /* Oblique Mercator forward equations--mapping lat,long to x,y
    ----------------------------------------------------------*/
  forward: function(p) {
    var theta;		/* angle					*/
    var sin_phi, cos_phi;/* sin and cos value				*/
    var b;		/* temporary values				*/
    var c, t, tq;	/* temporary values				*/
    var con, n, ml;	/* cone constant, small m			*/
    var q,us,vl;
    var ul,vs;
    var s;
    var dlon;
    var ts1;

    var lon=p.x;
    var lat=p.y;
    /* Forward equations
      -----------------*/
    sin_phi = Math.sin(lat);
    dlon = Proj4js.common.adjust_lon(lon - this.longc);
    vl = Math.sin(this.bl * dlon);
    if (Math.abs(Math.abs(lat) - Proj4js.common.HALF_PI) > Proj4js.common.EPSLN) {
       ts1 = Proj4js.common.tsfnz(this.e,lat,sin_phi);
       q = this.el / (Math.pow(ts1,this.bl));
       s = .5 * (q - 1.0 / q);
       t = .5 * (q + 1.0/ q);
       ul = (s * this.singam - vl * this.cosgam) / t;
       con = Math.cos(this.bl * dlon);
       if (Math.abs(con) < .0000001) {
          us = this.al * this.bl * dlon;
       } else {
          us = this.al * Math.atan((s * this.cosgam + vl * this.singam) / con)/this.bl;
          if (con < 0) us = us + Proj4js.common.PI * this.al / this.bl;
       }
    } else {
       if (lat >= 0) {
          ul = this.singam;
       } else {
          ul = -this.singam;
       }
       us = this.al * lat / this.bl;
    }
    if (Math.abs(Math.abs(ul) - 1.0) <= Proj4js.common.EPSLN) {
       //alert("Point projects into infinity","omer-for");
       Proj4js.reportError("omercFwdInfinity");
       //return(205);
    }
    vs = .5 * this.al * Math.log((1.0 - ul)/(1.0 + ul)) / this.bl;
    us = us - this.u;
    var x = this.x0 + vs * this.cosaz + us * this.sinaz;
    var y = this.y0 + us * this.cosaz - vs * this.sinaz;

    p.x=x;
    p.y=y;
    return p;
  },

  inverse: function(p) {
    var delta_lon;	/* Delta longitude (Given longitude - center 	*/
    var theta;		/* angle					*/
    var delta_theta;	/* adjusted longitude				*/
    var sin_phi, cos_phi;/* sin and cos value				*/
    var b;		/* temporary values				*/
    var c, t, tq;	/* temporary values				*/
    var con, n, ml;	/* cone constant, small m			*/
    var vs,us,q,s,ts1;
    var vl,ul,bs;
    var dlon;
    var  flag;

    /* Inverse equations
      -----------------*/
    p.x -= this.x0;
    p.y -= this.y0;
    flag = 0;
    vs = p.x * this.cosaz - p.y * this.sinaz;
    us = p.y * this.cosaz + p.x * this.sinaz;
    us = us + this.u;
    q = Math.exp(-this.bl * vs / this.al);
    s = .5 * (q - 1.0/q);
    t = .5 * (q + 1.0/q);
    vl = Math.sin(this.bl * us / this.al);
    ul = (vl * this.cosgam + s * this.singam)/t;
    if (Math.abs(Math.abs(ul) - 1.0) <= Proj4js.common.EPSLN)
       {
       lon = this.longc;
       if (ul >= 0.0) {
          lat = Proj4js.common.HALF_PI;
       } else {
         lat = -Proj4js.common.HALF_PI;
       }
    } else {
       con = 1.0 / this.bl;
       ts1 =Math.pow((this.el / Math.sqrt((1.0 + ul) / (1.0 - ul))),con);
       lat = Proj4js.common.phi2z(this.e,ts1);
       //if (flag != 0)
          //return(flag);
       //~ con = Math.cos(this.bl * us /al);
       theta = this.longc - Math.atan2((s * this.cosgam - vl * this.singam) , con)/this.bl;
       lon = Proj4js.common.adjust_lon(theta);
    }
    p.x=lon;
    p.y=lat;
    return p;
  }
};
/* ======================================================================
    projCode/lcc.js
   ====================================================================== */

/*******************************************************************************
NAME                            LAMBERT CONFORMAL CONIC

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Lambert Conformal Conic projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.


ALGORITHM REFERENCES

1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
*******************************************************************************/


//<2104> +proj=lcc +lat_1=10.16666666666667 +lat_0=10.16666666666667 +lon_0=-71.60561777777777 +k_0=1 +x0=-17044 +x0=-23139.97 +ellps=intl +units=m +no_defs  no_defs

// Initialize the Lambert Conformal conic projection
// -----------------------------------------------------------------

//Proj4js.Proj.lcc = Class.create();
Proj4js.Proj.lcc = {
  init : function() {

    // array of:  r_maj,r_min,lat1,lat2,c_lon,c_lat,false_east,false_north
    //double c_lat;                   /* center latitude                      */
    //double c_lon;                   /* center longitude                     */
    //double lat1;                    /* first standard parallel              */
    //double lat2;                    /* second standard parallel             */
    //double r_maj;                   /* major axis                           */
    //double r_min;                   /* minor axis                           */
    //double false_east;              /* x offset in meters                   */
    //double false_north;             /* y offset in meters                   */

      if (!this.lat2){this.lat2=this.lat0;}//if lat2 is not defined
      if (!this.k0) this.k0 = 1.0;

    // Standard Parallels cannot be equal and on opposite sides of the equator
      if (Math.abs(this.lat1+this.lat2) < Proj4js.common.EPSLN) {
        Proj4js.reportError("lcc:init: Equal Latitudes");
        return;
      }

      var temp = this.b / this.a;
      this.e = Math.sqrt(1.0 - temp*temp);

      var sin1 = Math.sin(this.lat1);
      var cos1 = Math.cos(this.lat1);
      var ms1 = Proj4js.common.msfnz(this.e, sin1, cos1);
      var ts1 = Proj4js.common.tsfnz(this.e, this.lat1, sin1);

      var sin2 = Math.sin(this.lat2);
      var cos2 = Math.cos(this.lat2);
      var ms2 = Proj4js.common.msfnz(this.e, sin2, cos2);
      var ts2 = Proj4js.common.tsfnz(this.e, this.lat2, sin2);

      var ts0 = Proj4js.common.tsfnz(this.e, this.lat0, Math.sin(this.lat0));

      if (Math.abs(this.lat1 - this.lat2) > Proj4js.common.EPSLN) {
        this.ns = Math.log(ms1/ms2)/Math.log(ts1/ts2);
      } else {
        this.ns = sin1;
      }
      this.f0 = ms1 / (this.ns * Math.pow(ts1, this.ns));
      this.rh = this.a * this.f0 * Math.pow(ts0, this.ns);
      if (!this.title) this.title = "Lambert Conformal Conic";
    },


    // Lambert Conformal conic forward equations--mapping lat,long to x,y
    // -----------------------------------------------------------------
    forward : function(p) {

      var lon = p.x;
      var lat = p.y;

    // convert to radians
      if ( lat <= 90.0 && lat >= -90.0 && lon <= 180.0 && lon >= -180.0) {
        //lon = lon * Proj4js.common.D2R;
        //lat = lat * Proj4js.common.D2R;
      } else {
        Proj4js.reportError("lcc:forward: llInputOutOfRange: "+ lon +" : " + lat);
        return null;
      }

      var con  = Math.abs( Math.abs(lat) - Proj4js.common.HALF_PI);
      var ts, rh1;
      if (con > Proj4js.common.EPSLN) {
        ts = Proj4js.common.tsfnz(this.e, lat, Math.sin(lat) );
        rh1 = this.a * this.f0 * Math.pow(ts, this.ns);
      } else {
        con = lat * this.ns;
        if (con <= 0) {
          Proj4js.reportError("lcc:forward: No Projection");
          return null;
        }
        rh1 = 0;
      }
      var theta = this.ns * Proj4js.common.adjust_lon(lon - this.long0);
      p.x = this.k0 * (rh1 * Math.sin(theta)) + this.x0;
      p.y = this.k0 * (this.rh - rh1 * Math.cos(theta)) + this.y0;

      return p;
    },

  // Lambert Conformal Conic inverse equations--mapping x,y to lat/long
  // -----------------------------------------------------------------
  inverse : function(p) {

    var rh1, con, ts;
    var lat, lon;
    var x = (p.x - this.x0)/this.k0;
    var y = (this.rh - (p.y - this.y0)/this.k0);
    if (this.ns > 0) {
      rh1 = Math.sqrt (x * x + y * y);
      con = 1.0;
    } else {
      rh1 = -Math.sqrt (x * x + y * y);
      con = -1.0;
    }
    var theta = 0.0;
    if (rh1 != 0) {
      theta = Math.atan2((con * x),(con * y));
    }
    if ((rh1 != 0) || (this.ns > 0.0)) {
      con = 1.0/this.ns;
      ts = Math.pow((rh1/(this.a * this.f0)), con);
      lat = Proj4js.common.phi2z(this.e, ts);
      if (lat == -9999) return null;
    } else {
      lat = -Proj4js.common.HALF_PI;
    }
    lon = Proj4js.common.adjust_lon(theta/this.ns + this.long0);

    p.x = lon;
    p.y = lat;
    return p;
  }
};




/* ======================================================================
    projCode/laea.js
   ====================================================================== */

/*******************************************************************************
NAME                  LAMBERT AZIMUTHAL EQUAL-AREA
 
PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the Lambert Azimuthal Equal-Area projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE            
----------              ----           
D. Steinwand, EROS      March, 1991   

This function was adapted from the Lambert Azimuthal Equal Area projection
code (FORTRAN) in the General Cartographic Transformation Package software
which is available from the U.S. Geological Survey National Mapping Division.
 
ALGORITHM REFERENCES

1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.

2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.

3.  "Software Documentation for GCTP General Cartographic Transformation
    Package", U.S. Geological Survey National Mapping Division, May 1982.
*******************************************************************************/

Proj4js.Proj.laea = {
  S_POLE: 1,
  N_POLE: 2,
  EQUIT: 3,
  OBLIQ: 4,


/* Initialize the Lambert Azimuthal Equal Area projection
  ------------------------------------------------------*/
  init: function() {
    var t = Math.abs(this.lat0);
    if (Math.abs(t - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
      this.mode = this.lat0 < 0. ? this.S_POLE : this.N_POLE;
    } else if (Math.abs(t) < Proj4js.common.EPSLN) {
      this.mode = this.EQUIT;
    } else {
      this.mode = this.OBLIQ;
    }
    if (this.es > 0) {
      var sinphi;
  
      this.qp = Proj4js.common.qsfnz(this.e, 1.0);
      this.mmf = .5 / (1. - this.es);
      this.apa = this.authset(this.es);
      switch (this.mode) {
        case this.N_POLE:
        case this.S_POLE:
          this.dd = 1.;
          break;
        case this.EQUIT:
          this.rq = Math.sqrt(.5 * this.qp);
          this.dd = 1. / this.rq;
          this.xmf = 1.;
          this.ymf = .5 * this.qp;
          break;
        case this.OBLIQ:
          this.rq = Math.sqrt(.5 * this.qp);
          sinphi = Math.sin(this.lat0);
          this.sinb1 = Proj4js.common.qsfnz(this.e, sinphi) / this.qp;
          this.cosb1 = Math.sqrt(1. - this.sinb1 * this.sinb1);
          this.dd = Math.cos(this.lat0) / (Math.sqrt(1. - this.es * sinphi * sinphi) * this.rq * this.cosb1);
          this.ymf = (this.xmf = this.rq) / this.dd;
          this.xmf *= this.dd;
          break;
      }
    } else {
      if (this.mode == this.OBLIQ) {
        this.sinph0 = Math.sin(this.lat0);
        this.cosph0 = Math.cos(this.lat0);
      }
    }
  },

/* Lambert Azimuthal Equal Area forward equations--mapping lat,long to x,y
  -----------------------------------------------------------------------*/
  forward: function(p) {

    /* Forward equations
      -----------------*/
    var x,y;
    var lam=p.x;
    var phi=p.y;
    lam = Proj4js.common.adjust_lon(lam - this.long0);
    
    if (this.sphere) {
        var coslam, cosphi, sinphi;
      
        sinphi = Math.sin(phi);
        cosphi = Math.cos(phi);
        coslam = Math.cos(lam);
        switch (this.mode) {
          case this.OBLIQ:
          case this.EQUIT:
            y = (this.mode == this.EQUIT) ? 1. + cosphi * coslam : 1. + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
            if (y <= Proj4js.common.EPSLN) {
              Proj4js.reportError("laea:fwd:y less than eps");
              return null;
            }
            y = Math.sqrt(2. / y);
            x = y * cosphi * Math.sin(lam);
            y *= (this.mode == this.EQUIT) ? sinphi : this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
            break;
          case this.N_POLE:
            coslam = -coslam;
          case this.S_POLE:
            if (Math.abs(phi + this.phi0) < Proj4js.common.EPSLN) {
              Proj4js.reportError("laea:fwd:phi < eps");
              return null;
            }
            y = Proj4js.common.FORTPI - phi * .5;
            y = 2. * ((this.mode == this.S_POLE) ? Math.cos(y) : Math.sin(y));
            x = y * Math.sin(lam);
            y *= coslam;
            break;
        }
    } else {
        var coslam, sinlam, sinphi, q, sinb=0.0, cosb=0.0, b=0.0;
      
        coslam = Math.cos(lam);
        sinlam = Math.sin(lam);
        sinphi = Math.sin(phi);
        q = Proj4js.common.qsfnz(this.e, sinphi);
        if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
          sinb = q / this.qp;
          cosb = Math.sqrt(1. - sinb * sinb);
        }
        switch (this.mode) {
          case this.OBLIQ:
            b = 1. + this.sinb1 * sinb + this.cosb1 * cosb * coslam;
            break;
          case this.EQUIT:
            b = 1. + cosb * coslam;
            break;
          case this.N_POLE:
            b = Proj4js.common.HALF_PI + phi;
            q = this.qp - q;
            break;
          case this.S_POLE:
            b = phi - Proj4js.common.HALF_PI;
            q = this.qp + q;
            break;
        }
        if (Math.abs(b) < Proj4js.common.EPSLN) {
            Proj4js.reportError("laea:fwd:b < eps");
            return null;
        }
        switch (this.mode) {
          case this.OBLIQ:
          case this.EQUIT:
            b = Math.sqrt(2. / b);
            if (this.mode == this.OBLIQ) {
              y = this.ymf * b * (this.cosb1 * sinb - this.sinb1 * cosb * coslam);
            } else {
              y = (b = Math.sqrt(2. / (1. + cosb * coslam))) * sinb * this.ymf;
            }
            x = this.xmf * b * cosb * sinlam;
            break;
          case this.N_POLE:
          case this.S_POLE:
            if (q >= 0.) {
              x = (b = Math.sqrt(q)) * sinlam;
              y = coslam * ((this.mode == this.S_POLE) ? b : -b);
            } else {
              x = y = 0.;
            }
            break;
        }
    }

    //v 1.0
    /*
    var sin_lat=Math.sin(lat);
    var cos_lat=Math.cos(lat);

    var sin_delta_lon=Math.sin(delta_lon);
    var cos_delta_lon=Math.cos(delta_lon);

    var g =this.sin_lat_o * sin_lat +this.cos_lat_o * cos_lat * cos_delta_lon;
    if (g == -1.0) {
      Proj4js.reportError("laea:fwd:Point projects to a circle of radius "+ 2.0 * R);
      return null;
    }
    var ksp = this.a * Math.sqrt(2.0 / (1.0 + g));
    var x = ksp * cos_lat * sin_delta_lon + this.x0;
    var y = ksp * (this.cos_lat_o * sin_lat - this.sin_lat_o * cos_lat * cos_delta_lon) + this.y0;
    */
    p.x = this.a*x + this.x0;
    p.y = this.a*y + this.y0;
    return p;
  },//lamazFwd()

/* Inverse equations
  -----------------*/
  inverse: function(p) {
    p.x -= this.x0;
    p.y -= this.y0;
    var x = p.x/this.a;
    var y = p.y/this.a;
    
    if (this.sphere) {
        var  cosz=0.0, rh, sinz=0.0;
      
        rh = Math.sqrt(x*x + y*y);
        var phi = rh * .5;
        if (phi > 1.) {
          Proj4js.reportError("laea:Inv:DataError");
          return null;
        }
        phi = 2. * Math.asin(phi);
        if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
          sinz = Math.sin(phi);
          cosz = Math.cos(phi);
        }
        switch (this.mode) {
        case this.EQUIT:
          phi = (Math.abs(rh) <= Proj4js.common.EPSLN) ? 0. : Math.asin(y * sinz / rh);
          x *= sinz;
          y = cosz * rh;
          break;
        case this.OBLIQ:
          phi = (Math.abs(rh) <= Proj4js.common.EPSLN) ? this.phi0 : Math.asin(cosz * sinph0 + y * sinz * cosph0 / rh);
          x *= sinz * cosph0;
          y = (cosz - Math.sin(phi) * sinph0) * rh;
          break;
        case this.N_POLE:
          y = -y;
          phi = Proj4js.common.HALF_PI - phi;
          break;
        case this.S_POLE:
          phi -= Proj4js.common.HALF_PI;
          break;
        }
        lam = (y == 0. && (this.mode == this.EQUIT || this.mode == this.OBLIQ)) ? 0. : Math.atan2(x, y);
    } else {
        var cCe, sCe, q, rho, ab=0.0;
      
        switch (this.mode) {
          case this.EQUIT:
          case this.OBLIQ:
            x /= this.dd;
            y *=  this.dd;
            rho = Math.sqrt(x*x + y*y);
            if (rho < Proj4js.common.EPSLN) {
              p.x = 0.;
              p.y = this.phi0;
              return p;
            }
            sCe = 2. * Math.asin(.5 * rho / this.rq);
            cCe = Math.cos(sCe);
            x *= (sCe = Math.sin(sCe));
            if (this.mode == this.OBLIQ) {
              ab = cCe * this.sinb1 + y * sCe * this.cosb1 / rho
              q = this.qp * ab;
              y = rho * this.cosb1 * cCe - y * this.sinb1 * sCe;
            } else {
              ab = y * sCe / rho;
              q = this.qp * ab;
              y = rho * cCe;
            }
            break;
          case this.N_POLE:
            y = -y;
          case this.S_POLE:
            q = (x * x + y * y);
            if (!q ) {
              p.x = 0.;
              p.y = this.phi0;
              return p;
            }
            /*
            q = this.qp - q;
            */
            ab = 1. - q / this.qp;
            if (this.mode == this.S_POLE) {
              ab = - ab;
            }
            break;
        }
        lam = Math.atan2(x, y);
        phi = this.authlat(Math.asin(ab), this.apa);
    }

    /*
    var Rh = Math.Math.sqrt(p.x *p.x +p.y * p.y);
    var temp = Rh / (2.0 * this.a);

    if (temp > 1) {
      Proj4js.reportError("laea:Inv:DataError");
      return null;
    }

    var z = 2.0 * Proj4js.common.asinz(temp);
    var sin_z=Math.sin(z);
    var cos_z=Math.cos(z);

    var lon =this.long0;
    if (Math.abs(Rh) > Proj4js.common.EPSLN) {
       var lat = Proj4js.common.asinz(this.sin_lat_o * cos_z +this. cos_lat_o * sin_z *p.y / Rh);
       var temp =Math.abs(this.lat0) - Proj4js.common.HALF_PI;
       if (Math.abs(temp) > Proj4js.common.EPSLN) {
          temp = cos_z -this.sin_lat_o * Math.sin(lat);
          if(temp!=0.0) lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x*sin_z*this.cos_lat_o,temp*Rh));
       } else if (this.lat0 < 0.0) {
          lon = Proj4js.common.adjust_lon(this.long0 - Math.atan2(-p.x,p.y));
       } else {
          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x, -p.y));
       }
    } else {
      lat = this.lat0;
    }
    */
    //return(OK);
    p.x = Proj4js.common.adjust_lon(this.long0+lam);
    p.y = phi;
    return p;
  },//lamazInv()
  
/* determine latitude from authalic latitude */
  P00: .33333333333333333333,
  P01: .17222222222222222222,
  P02: .10257936507936507936,
  P10: .06388888888888888888,
  P11: .06640211640211640211,
  P20: .01641501294219154443,
  
  authset: function(es) {
    var t;
    var APA = new Array();
    APA[0] = es * this.P00;
    t = es * es;
    APA[0] += t * this.P01;
    APA[1] = t * this.P10;
    t *= es;
    APA[0] += t * this.P02;
    APA[1] += t * this.P11;
    APA[2] = t * this.P20;
    return APA;
  },
  
  authlat: function(beta, APA) {
    var t = beta+beta;
    return(beta + APA[0] * Math.sin(t) + APA[1] * Math.sin(t+t) + APA[2] * Math.sin(t+t+t));
  }
  
};



/* ======================================================================
    projCode/aeqd.js
   ====================================================================== */

Proj4js.Proj.aeqd = {

  init : function() {
    this.sin_p12=Math.sin(this.lat0);
    this.cos_p12=Math.cos(this.lat0);
  },

  forward: function(p) {
    var lon=p.x;
    var lat=p.y;
    var ksp;

    var sinphi=Math.sin(p.y);
    var cosphi=Math.cos(p.y); 
    var dlon = Proj4js.common.adjust_lon(lon - this.long0);
    var coslon = Math.cos(dlon);
    var g = this.sin_p12 * sinphi + this.cos_p12 * cosphi * coslon;
    if (Math.abs(Math.abs(g) - 1.0) < Proj4js.common.EPSLN) {
       ksp = 1.0;
       if (g < 0.0) {
         Proj4js.reportError("aeqd:Fwd:PointError");
         return;
       }
    } else {
       var z = Math.acos(g);
       ksp = z/Math.sin(z);
    }
    p.x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon);
    p.y = this.y0 + this.a * ksp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * coslon);
    return p;
  },

  inverse: function(p){
    p.x -= this.x0;
    p.y -= this.y0;

    var rh = Math.sqrt(p.x * p.x + p.y *p.y);
    if (rh > (2.0 * Proj4js.common.HALF_PI * this.a)) {
       Proj4js.reportError("aeqdInvDataError");
       return;
    }
    var z = rh / this.a;

    var sinz=Math.sin(z);
    var cosz=Math.cos(z);

    var lon = this.long0;
    var lat;
    if (Math.abs(rh) <= Proj4js.common.EPSLN) {
      lat = this.lat0;
    } else {
      lat = Proj4js.common.asinz(cosz * this.sin_p12 + (p.y * sinz * this.cos_p12) / rh);
      var con = Math.abs(this.lat0) - Proj4js.common.HALF_PI;
      if (Math.abs(con) <= Proj4js.common.EPSLN) {
        if (lat0 >= 0.0) {
          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x , -p.y));
        } else {
          lon = Proj4js.common.adjust_lon(this.long0 - Math.atan2(-p.x , p.y));
        }
      } else {
        con = cosz - this.sin_p12 * Math.sin(lat);
        if ((Math.abs(con) < Proj4js.common.EPSLN) && (Math.abs(p.x) < Proj4js.common.EPSLN)) {
           //no-op, just keep the lon value as is
        } else {
          var temp = Math.atan2((p.x * sinz * this.cos_p12), (con * rh));
          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2((p.x * sinz * this.cos_p12), (con * rh)));
        }
      }
    }

    p.x = lon;
    p.y = lat;
    return p;
  } 
};
/* ======================================================================
    projCode/moll.js
   ====================================================================== */

/*******************************************************************************
NAME                            MOLLWEIDE

PURPOSE:	Transforms input longitude and latitude to Easting and
		Northing for the MOllweide projection.  The
		longitude and latitude must be in radians.  The Easting
		and Northing values will be returned in meters.

PROGRAMMER              DATE
----------              ----
D. Steinwand, EROS      May, 1991;  Updated Sept, 1992; Updated Feb, 1993
S. Nelson, EDC		Jun, 2993;	Made corrections in precision and
					number of iterations.

ALGORITHM REFERENCES

1.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
    U.S. Geological Survey Professional Paper 1453 , United State Government
    Printing Office, Washington D.C., 1989.

2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
    State Government Printing Office, Washington D.C., 1987.
*******************************************************************************/

Proj4js.Proj.moll = {

  /* Initialize the Mollweide projection
    ------------------------------------*/
  init: function(){
    //no-op
  },

  /* Mollweide forward equations--mapping lat,long to x,y
    ----------------------------------------------------*/
  forward: function(p) {

    /* Forward equations
      -----------------*/
    var lon=p.x;
    var lat=p.y;

    var delta_lon = Proj4js.common.adjust_lon(lon - this.long0);
    var theta = lat;
    var con = Proj4js.common.PI * Math.sin(lat);

    /* Iterate using the Newton-Raphson method to find theta
      -----------------------------------------------------*/
    for (var i=0;true;i++) {
       var delta_theta = -(theta + Math.sin(theta) - con)/ (1.0 + Math.cos(theta));
       theta += delta_theta;
       if (Math.abs(delta_theta) < Proj4js.common.EPSLN) break;
       if (i >= 50) {
          Proj4js.reportError("moll:Fwd:IterationError");
         //return(241);
       }
    }
    theta /= 2.0;

    /* If the latitude is 90 deg, force the x coordinate to be "0 + false easting"
       this is done here because of precision problems with "cos(theta)"
       --------------------------------------------------------------------------*/
    if (Proj4js.common.PI/2 - Math.abs(lat) < Proj4js.common.EPSLN) delta_lon =0;
    var x = 0.900316316158 * this.a * delta_lon * Math.cos(theta) + this.x0;
    var y = 1.4142135623731 * this.a * Math.sin(theta) + this.y0;

    p.x=x;
    p.y=y;
    return p;
  },

  inverse: function(p){
    var theta;
    var arg;

    /* Inverse equations
      -----------------*/
    p.x-= this.x0;
    //~ p.y -= this.y0;
    var arg = p.y /  (1.4142135623731 * this.a);

    /* Because of division by zero problems, 'arg' can not be 1.0.  Therefore
       a number very close to one is used instead.
       -------------------------------------------------------------------*/
    if(Math.abs(arg) > 0.999999999999) arg=0.999999999999;
    var theta =Math.asin(arg);
    var lon = Proj4js.common.adjust_lon(this.long0 + (p.x / (0.900316316158 * this.a * Math.cos(theta))));
    if(lon < (-Proj4js.common.PI)) lon= -Proj4js.common.PI;
    if(lon > Proj4js.common.PI) lon= Proj4js.common.PI;
    arg = (2.0 * theta + Math.sin(2.0 * theta)) / Proj4js.common.PI;
    if(Math.abs(arg) > 1.0)arg=1.0;
    var lat = Math.asin(arg);
    //return(OK);

    p.x=lon;
    p.y=lat;
    return p;
  }
};

/**
 * Fusion.Error
 *
 * $Id: Error.js 2827 2014-02-08 17:41:35Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/*****************************************************************************
 * Class: Fusion.Error
 *
 * General purpose Error object
 */

Fusion.Error = OpenLayers.Class({
    type: null,
    message: null,
    initialize: function(type, message) {
        this.type = type;
        this.message = message;
    },
    
    alert: function() {
        var type = this.typeToString(this.type);
        alert(OpenLayers.i18n('fusionError', {'type':type, 'message': this.message}));
    },
    
    toString: function() {
        var type = this.typeToString(this.type);
        return type + ": " + this.message;
    },
    
    typeToString: function(type) {
        switch (type) {
            case Fusion.Error.FATAL:
                return 'FATAL';
            case Fusion.Error.WARNING:
                return 'WARNING';
            case Fusion.Error.NOTICE:
                return 'NOTICE';
            default:
                return 'UNKNOWN ('+type+')';
        }
    }
});

Fusion.DetailedError = OpenLayers.Class({
    message: null,
    name: null,
    incomplete: true,
    partial: true,
    stack: [],
    url: null,
    useragent: null,
    initialize: function(o) {
        this.message = o.message;
        this.name = o.name || "Error";
        this.incomplete = o.incomplete;
        this.partial = o.partial;
        this.stack = o.stack;
        this.url = o.url;
        this.useragent = o.useragent;
    }
});

Fusion.Error.FATAL = 0;
Fusion.Error.WARNING = 1;
Fusion.Error.NOTICE = 2;


/**
 * Class: Fusion.Lib.EventMgr
 *
 * an internal class for managing generic events.  Classes that wish to
 * publish and trigger events that other objects can listen for need to
 * inherit from Fusion.Lib.EventMgr.
 *
 * To publish an event, call registerEventID with some unique numeric or
 * string value.  Other objects can then call registerForEvent with the
 * eventID and a function to call when the event is triggered.
 *
 * To trigger an event, call triggerEvent with the eventID and any additional
 * arguments that should be passed to listeners.
 */
Fusion.Lib.EventMgr = OpenLayers.Class({
    /* an array of eventIDs and associated listener functions */
    events : null,
    
    initialize: function() { if (!this.events) {this.events = []; }},

    /**
     * Method: destroy
     *
     */
    destroy: function() {
       this.events = []; 
    },
    
    /**
     * Method: registerEventID
     * 
     * register an event ID so that others can use it.  This should really
     * only be called by 'this' object.
     *
     * Parameters:
     * eventID - the event ID to register
     */
    registerEventID : function( eventID ) {
        if (!this.events) {this.events = []; }
        if (!eventID) {
            Fusion.reportError(new Fusion.Error(Fusion.Error.WARNING, 
                          OpenLayers.i18n('registerEventError')));
        }
        var ev = new String(eventID);
        if (!this.events[eventID]) {
            this.events[eventID] = [];
        }
    },

    /**
     * Method: registerForEvent
     * 
     * register for receiving a callback when an event happens. If you
     * want the callback to be a method on an instance of some object, 
     * use the {<OpenLayers.Function.bind>} function as in:
     *
     * otherObj.registerForEvent(SOME_EVENT, OpenLayers.Function.bind(this.callback,this));
     *
     * Parameters:
     * eventID - the event ID to register for
     * f - the function to call when the event happens.  
     */
    registerForEvent : function(eventID, f) {
        var ev = new String(eventID);
        this.events[eventID].push(f);
    },

    /**
     * Method: deregisterForEvent
     * 
     * deregister a callback function when you no longer want to
     * recieve it.  Note that if you used {<OpenLayers.Function.bind>} when registering,
     * you need to pass EXACTLY THE SAME FUNCTION when
     * deregistering.  Typically, this means you need to assign the
     * result of {<OpenLayers.Function.bind>} to an instance variable and pass that instance
     * variable to both {<Fusion.Lib.EventMgr.registerForEvent>} and {<Fusion.Lib.EventMgr.deregisterForEvent>}.
     *
     * For instance:
     *
     * this.callbackFn = OpenLayers.Function.bind(this.callback, this);
     * otherObj.registerForEvent(SOME_EVENT, this.callbackFn);
     * otherObj.deregisterForEvent(SOME_EVENT, this.callbackFn);
     *
     * Parameters:
     * eventID - the event ID to deregister
     * f - the function that used when registering.
     */
    deregisterForEvent : function(eventID, f) {
        var ev = new String(eventID);
        var bResult = false;
        if (!this.events[eventID]){
            return false;
        }

        for (var i=0;i<this.events[eventID].length;i++) {
            if (this.events[eventID][i]== f) {
                this.events[eventID].splice(i,1);
                bResult = true;
            }
        }
        return bResult;
    },       

    /**
     * Method: triggerEvent
     * 
     * trigger an event and call all registered listener functions.
     * This is intended to be called by 'this'.  The eventID param
     * is mandatory.  Any additional arguments will be passed to the
     * listener function.
     *
     * Parameters:
     * eventID - the event ID to trigger
     */
    triggerEvent : function( eventID ) {
        var ev = new String(eventID);
        if (!this.events || !this.events[eventID]) {
            return false;
        }

        for (var i=0; i<this.events[eventID].length; i++) {
            this.events[eventID][i].apply(null, arguments);
        }
        return true;
    }
});
        
//window.Fusion = OpenLayers.Class(Fusion.Lib.EventMgr, Fusion.prototype);
//OpenLayers.Util.extend(Fusion, Fusion.Lib.EventMgr.prototype);
Fusion.events = [];
Fusion.registerEventID = Fusion.Lib.EventMgr.prototype.registerEventID;
Fusion.registerForEvent = Fusion.Lib.EventMgr.prototype.registerForEvent;
Fusion.triggerEvent = Fusion.Lib.EventMgr.prototype.triggerEvent;

/**
 * Constant: Fusion.Event.FUSION_INITIALIZED
 */
Fusion.Event.FUSION_INITIALIZED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.FUSION_ERROR
 */
Fusion.Event.FUSION_ERROR = Fusion.Event.lastEventId++;
Fusion.registerEventID(Fusion.Event.FUSION_INITIALIZED);
Fusion.registerEventID(Fusion.Event.FUSION_ERROR);
/**
 * Fusion.Lib.ApplicationDefinition
 *
 * $Id: ApplicationDefinition.js 2827 2014-02-08 17:41:35Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition
 *
 * Utility class to parse an application definition
 *
 */

Fusion.Lib.ApplicationDefinition = OpenLayers.Class({
    /**
     * Property: mapGroups
     *
     * array of map groups, parsed from ApplicationDefinition.  A MapGroup
     * consists of one or more Maps that can be combined into the same
     * OpenLayers Map object
     */
    mapGroups: null,

    /**
     * Property: widgetSets
     *
     * array of widget sets (each one corresponding to a map) parsed
     * from the ApplicationDefinition.
     */
    widgetSets: null,

    /**
     * Property: {Object} oBroker
     *
     * A Broker object that can communicate with a MapGuide instance
     * in the case we are running against a MapGuide server
     */
    oBroker: null,

    /**
     * Property: {Array} searchDefinitions
     *
     * An array of search definitions
     */
    searchDefinitions: null,

    /**
     * Property: {Array} searchCategories
     *
     * An array of search categories
     */
    searchCategories: null,

    /**
     * Constructor: ApplicationDefinition
     *
     * construct a new instance of the ApplicationDefinition object.  While
     * not enforced, this is intended to be a singleton.
     *
     * Parameter: sessionId
     *
     * an optional session id to initialize the application with, passed to
     * the map widgets when they are created.
     */

    initialize: function(sessionId) {
        //console.log('ApplicationDefinition initialize');
        this.sessionId = sessionId;
        this.oBroker = Fusion.getBroker();
        this.applicationDefinition =  Fusion.getApplicationDefinitionURL();

        this.widgetSets = [];
        this.mapGroups = {};
        this.searchDefinitions = [];
        this.searchCategories = [];
        this.parse();
    },

    /**
     * Function: parse
     *
     * start parsing the ApplicationDefinition file.  This happens
     * asynchronously since the ApplicationDefinition has to be
     * retrieved from the server or the MapGuide repository.  When
     * parsing is complete, an APPLICATIONDEFINITION_PARSED event
     * will be emitted.  This function returns true if parsing
     * will start, false if it will not (due to a missing
     * application definition for instance).
     */
    parse : function() {
        if (this.applicationDefinition == '') {
            //TODO: emit an error
            return null;
        }
        /* if the application definition is not in the mapguide server,
           just load the xml*/

        if ( (this.applicationDefinition.match('Library://') == null) &&
             (this.applicationDefinition.match('Session:') == null) ) {
            if (Fusion.appDefJson) {
                this.parseAppDef(Fusion.appDefJson);
            } else {
                Fusion.getXmlAsJson(this.applicationDefinition,
                              OpenLayers.Function.bind(this.getAppDefCB, this));
            }
        } else {
          //these need to be called via setTimeout so that the execution thread
          //is broken in IE and so that the ApplicationDefinition constructor
          //will return a valid object to Fusion in loadConfig before the loadState
          //gets incremented.
            if (!this.sessionId) {
              window.setTimeout(OpenLayers.Function.bind(this.createSessionThenGetAppDef,this),5);
            } else {
              window.setTimeout(OpenLayers.Function.bind(this.getAppDef,this),5);
            }
        }
        return true;
    },

    createSessionThenGetAppDef: function() {
      var sl = Fusion.getScriptLanguage();
      var scriptURL = 'layers/' + 'MapGuide' + '/' + sl + '/CreateSession.' + sl;
      var options = {
        onSuccess: OpenLayers.Function.bind(this.createSessionThenGetAppDefCB, this),
        onFailure: OpenLayers.Function.bind(Fusion.ajaxException, Fusion)
      };
      Fusion.ajaxRequest(scriptURL, options);
    },

    createSessionThenGetAppDefCB : function(xhr) {
      if (xhr && typeof(xhr) == "object" && xhr.responseText) {
        var o = Fusion.parseJSON(xhr.responseText);
        this.sessionId = o.sessionId;
        Fusion.sessionId = this.sessionId;
      }
      //We must have a session id established in order to continue here
      if (this.sessionId)
        this.getAppDef();
      else
        Fusion.reportFatalError("Call to CreateSession.php failed");
    },

    getAppDef: function(){
      var r = new Fusion.Lib.MGRequest.MGGetResourceContent(this.applicationDefinition);
      r.parameters.session = this.sessionId;
      r.parameters.format = 'application/json';
      this.oBroker.dispatchRequest(r,
              OpenLayers.Function.bind(this.getAppDefCB, this));
    },

    getAppDefCB: function(xhr) {
        var o = Fusion.parseJSON(xhr.responseText);
        this.parseAppDef(o);
        Fusion.setLoadState(Fusion.LOAD_WIDGETS);
    },

    /**
     * Function: parseAppDef
     *
     * parse the ApplicationDefinition file into the appropriate Fusion objects
     *
     * Parameter: {XmlHttpRequest} xhr
     *
     * the XmlHttpRequest object
     */
    parseAppDef: function(json) {
        var appDef = json.ApplicationDefinition;

        /* Set the application title */
        if (appDef.Title) {
            var title = appDef.Title[0];
            document.title = title;
        }

        /* process Map nodes */
        if (appDef.MapSet) {
            var mapSet = appDef.MapSet[0];
            if (mapSet.MapGroup instanceof Array) {
                for (var i=0; i<mapSet.MapGroup.length; i++) {
                    var mapGroup = new Fusion.Lib.ApplicationDefinition.MapGroup(mapSet.MapGroup[i]);
                    this.mapGroups[mapGroup.mapId] = mapGroup;
                }
            }
        } else {
            Fusion.reportFatalError(OpenLayers.i18n('appDefParseError'));
        }

        /* process WIDGET sets */
        if (appDef.WidgetSet) {
            for (var i=0; i<appDef.WidgetSet.length; i++) {
                var widgetSet = new Fusion.Lib.ApplicationDefinition.WidgetSet(appDef.WidgetSet[i]);
                this.widgetSets.push(widgetSet);
            }
        } else {
            Fusion.reportFatalError(OpenLayers.i18n('widgetSetParseError'));
        }

        /* process extensions */
        if (appDef.Extension) {
            var extension = appDef.Extension[0];
            /* process search definitions */
            if (extension.SearchDefinitions instanceof Array) {
                var categories = extension.SearchDefinitions[0];
                if (categories.SearchCategory instanceof Array) {
                    for (var i=0; i<categories.SearchCategory.length; i++) {
                        var oCategory = {};
                        var category = categories.SearchCategory[i];
                        oCategory.id = category['@id'];
                        oCategory.name = category['@name'];
                        oCategory.layer = category.Layer ? category.Layer[0] : '';
                        oCategory.searchDefinitions = [];
                        this.searchCategories[oCategory.id] = oCategory;
                        var defns = category.SearchDefinition;
                        for (var k=0; k<defns.length; k++) {
                            var defn = new Fusion.Lib.ApplicationDefinition.SearchDefinition(defns[k]);
                            defn.category = oCategory;
                            oCategory.searchDefinitions[defn.id] = defn;
                            this.searchDefinitions[defn.id] = defn;
                        }
                    }
                }
            }

        }
    },

    /**
     * Function: create
     *
     * Create the application definition.  This actually triggers initializing
     * every widget and container.
     */
    create: function() {
        for (var i=0; i<this.widgetSets.length; i++) {
            this.widgetSets[i].create(this);
        }
    },

    /**
     * Function: getMapByName
     *
     * return a map widget with the given name
     *
     * Parameter: {String} name
     *
     * The map name to return
     *
     * Returns: {Object} a map object or null if not found.
     */
    getMapByName : function(name) {
        var map = null;
        for (var i=0; i<this.widgetSets.length; i++) {
            map = this.widgetSets[i].getMapByName(name);
            if (map) {
                break;
            }
        }
        return map;
    },

    /**
     * Function: getMapById
     *
     * return a map widget with the given id
     *
     * Parameter: {String} id
     *
     * The map id to return.  ID is distinct from map.name in that id is the
     * id of the HTML tag where the map widget is inserted.
     *
     * Returns: {Object} a map object or null if not found.
     */
    getMapById : function(id) {
        var map = null;
        for (var i=0; i<this.widgetSets.length; i++) {
            map = this.widgetSets[i].mapWidget;
            if (map.mapId == id) {
                break;
            }
        }
        return map;
    },

    /**
     * Function: getMapByIndice
     *
     * return the map widget at the given index
     *
     * Parameter: {String} indice
     *
     * The map indice to return
     *
     * Returns: {Object} a map object or null if not found.
     */
     getMapByIndice : function(indice) {
         var map = null;
         if (this.widgetSets.length > indice) {
             map = this.widgetSets[indice].getMapWidget();
         }
         return map;
     },

    /**
     * Function: getMapGroup
     *
     * return the specified map group from the application definition
     *
     * Parameter: {String} mapgroup
     *
     * The id of the MapGroup to return
     *
     * Returns: {Object} a MapGroup appdef or null if not found.
     */
     getMapGroup : function(mapGroupId) {
         return this.mapGroups[mapGroupId];
     },

     /**
      * Function getWidgetsByType
      *
      * returns an array of widgets by type.
      *
      * Parameter: {String} type
      *
      * the type of widget to get references to
      *
      * Returns: {Array} an array of widgets, which may be empty
      */
     getWidgetsByType: function(type) {
         var widgets = [];
         for (var i=0; i<this.widgetSets.length; i++) {
             widgets = widgets.concat(this.widgetSets[i].getWidgetsByType(type));
         }
         return widgets;
     }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.MapGroup
 *
 * Holds an internal representation of MapGroup objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.MapGroup = OpenLayers.Class({
    initialView: null,
    maps: null,

    initialize: function(jsonNode) {
        this.mapId = jsonNode['@id'][0];
        this.maps = [];
        /* parse InitialView */
        if (jsonNode.InitialView) {
            var iv = jsonNode.InitialView[0];
            if (iv.CenterX && iv.CenterY && iv.Scale) {
                this.setInitialView({x:parseFloat(iv.CenterX[0]),
                                     y:parseFloat(iv.CenterY[0]),
                                     scale:parseFloat(iv.Scale[0])});
            } else if (iv.MinX && iv.MinY && iv.MaxX && iv.MaxY) {
                this.setInitialView({minX:parseFloat(iv.MinX[0]),
                                     minY:parseFloat(iv.MinY[0]),
                                     maxX:parseFloat(iv.MaxX[0]),
                                     maxY:parseFloat(iv.MaxY[0])});
            } else {
                //TODO: emit warning that the initial view was incomplete
            }
        }
        /* parse maps */
        if (jsonNode.Map instanceof Array) {
            for (var i=0; i<jsonNode.Map.length; i++) {
                var map = new Fusion.Lib.ApplicationDefinition.Map(jsonNode.Map[i]);
                var links = {groups:[], layers:[]};
                var mapEvents = {layerEvents:{},groupEvents:{}};
                if (jsonNode.Map[i].Extension) {
                    var extension = jsonNode.Map[i].Extension[0];
                    if (extension.Links) {
                        /* process Groups */
                        if (extension.Links[0].Group instanceof Array) {
                            for (var j=0; j<extension.Links[0].Group.length; j++) {
                                var group = extension.Links[0].Group[j];
                                links.groups.push({name:group.Name[0],url:group.Url[0]});
                            }
                        }
                        if (extension.Links[0].Layer instanceof Array) {
                            for (var j=0; j<extension.Links[0].Layer.length; j++) {
                                var layer = extension.Links[0].Layer[j];
                                links.layers.push({name:layer.Name[0],url:layer.Url[0]});
                            }
                        }
                    }
                    /* process layer events */
                    //TODO: Should this be called MapEvents?
                    if (extension.MapEvents) {
                        if (extension.MapEvents[0].Layer instanceof Array) {
                            for (var j=0; j<extension.MapEvents[0].Layer.length; j++) {
                                var layer = extension.MapEvents[0].Layer[j];
                                var layerObj = {};
                                layerObj.name = layer.Name[0];
                                layerObj.onEnable = [];
                                if (layer.OnEnable instanceof Array) {
                                    layerObj.onEnable = this.parseMapEventSubBlock(layer.OnEnable[0]);
                                }
                                layerObj.onDisable = [];
                                if (layer.OnDisable instanceof Array) {
                                    layerObj.onDisable = this.parseMapEventSubBlock(layer.OnDisable[0]);
                                }
                                mapEvents.layerEvents[layerObj.name] = layerObj;
                            }
                        }
                        if (extension.MapEvents[0].Group instanceof Array) {
                            for (var j=0; j<extension.MapEvents[0].Group.length; j++) {
                                var group = extension.MapEvents[0].Group[j];
                                var groupObj = {};
                                groupObj.name = group.Name[0];
                                groupObj.onEnable = [];
                                if (layer.OnEnable instanceof Array) {
                                    groupObj.onEnable = this.parseMapEventSubBlock(group.OnEnable[0]);
                                }
                                groupObj.onDisable = [];
                                if (layer.OnDisable instanceof Array) {
                                    groupObj.onDisable = this.parseMapEventSubBlock(group.OnDisable[0]);
                                }
                                mapEvents.groupEvents[groupObj.name] = groupObj;
                            }
                        }
                    }
                }
                map.mapInfo = {links: links, mapEvents: mapEvents};
                this.maps.push(map);
            }
        } else {
            //TODO: do we need a warning that there are no layers in this map?
        }
    },

    parseMapEventSubBlock: function(block) {
        var a = [];
        if (block.Layer && block.Layer instanceof Array) {
            for (var i=0; i<block.Layer.length; i++) {
                var layer = block.Layer[i];
                a.push({type: 'layer', name:layer.Name[0], enable: layer.Enable[0] == 'true' ? true : false});
            }
        }
        if (block.Group && block.Group instanceof Array) {
            for (var i=0; i<block.Group.length; i++) {
                var group = block.Group[i];
                a.push({type: 'group', name:group.Name[0], enable: group.Enable[0] == 'true' ? true : false});
            }
        }
        return a;
    },

    getInitialView: function() {
        return this.initialView;
    },

    setInitialView: function(view) {
        this.initialView = view;
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.Map
 *
 * Holds an internal representation of Map objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.Map = OpenLayers.Class({
    type: null,
    singleTile: false,
    extension: null,
    
    initialize: function(jsonNode) {
        /* TODO: type can be any supported OpenLayers type */
        this.id = jsonNode['@id'] ? jsonNode['@id'][0] : null;
        this.type = jsonNode.Type[0];
        if (jsonNode.SingleTile) {
            var b = jsonNode.SingleTile[0].toLowerCase();
            this.singleTile = (b == "true") ? true : false;
        }
        if (jsonNode.Extension) {
            this.extension = jsonNode.Extension[0];
        } else {
            this.extension = {};
        }
        this.resourceId = this.extension.ResourceId ? this.extension.ResourceId[0] : '';

        var tagOptions = this.extension.Options;
        this.layerOptions = {};
        if (tagOptions && tagOptions[0]) {
          for (var key in tagOptions[0]) {
            var val = tagOptions[0][key][0];
            if (val.toFloat().toString() == val) {
              this.layerOptions[key] = val.toFloat();
            } else {
              if (val.toLowerCase() == 'true') {
                this.layerOptions[key] = true;
              } else if (val.toLowerCase() == 'false') {
                this.layerOptions[key] = false;
              } else {
                this.layerOptions[key] = val;
              }
            }
            if (key == 'maxExtent' || key == 'minExtent' || key == 'restrictedExtent') {
              this.layerOptions[key] = OpenLayers.Bounds.fromString(this.layerOptions[key]);
            }
            if (key == 'resolutions' || key == 'scales') {
              this.layerOptions[key] = this.layerOptions[key].split(',');
              for (var i=0; i<this.layerOptions[key].length; i++) {
                this.layerOptions[key][i] = this.layerOptions[key][i].toFloat();
              }
            }
          }
        }
        var tagParams = this.extension.Parameters;
        this.layerParams = {};
        if (tagParams && tagParams[0]) {
          for (var key in tagParams[0]) {
            var val = tagParams[0][key][0];
            if (val.toFloat().toString() == val) {
              val = val.toFloat();
            }
            this.layerParams[key] = val;
          }
        }
        
        //projection info from the extension
        //this.layerOptions.projection = "EPSG:4326";  //default to WGS84 lat long
        if (this.extension.ProjectionCode) {
          this.layerOptions.projection = this.extension.ProjectionCode[0];
        }
        if (this.extension.ProjectionDef) {
          var projDef = this.extension.ProjectionDef[0];
          if (!this.layerOptions.projection) {
            this.layerOptions.projection = "APP-DEF-PROJ";
          }
          Proj4js.defs[this.layerOptions.projection] = projDef;
        }
        if (!this.layerOptions.projection) {
          this.layerOptions.projection = "EPSG:4326";
        }

        switch (this.type) {
          case 'MapGuide':
          case 'MapServer':
            if ( !Fusion.Layers[this.type] ) {
                  Fusion.require('layers/' + this.type + '/' + this.type + '.js');
            }
            break;
          case "Google":
          case "Yahoo":
          case "VirtualEarth":
              this.layerOptions.isBaseLayer = true;
              this.layerOptions.sphericalMercator = true;
              this.layerOptions.displayProjection = "EPSG:4326";
              //no break here continue below to set EPSG:3785 as projCode
          default:
            if (this.layerOptions.sphericalMercator) {
              this.layerOptions.projection = "EPSG:900913";  //commercial map layer projection spherical mercator
            } else {
              this.layerOptions.projection = "EPSG:4326";  //WGS84 lat long
            }
            if ( !Fusion.Layers.Generic ) {
              Fusion.require('layers/Generic/Generic.js');
            }
            break;
        }
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.WidgetSet
 *
 * Holds an internal representation of WidgetSet objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.WidgetSet = OpenLayers.Class({
    containers: null,
    containersByName: null,
    widgetTags: null,
    widgetTagsByName: null,
    widgetInstances: null,
    mapWidget: null,
    mapId: null,
    initialize: function(jsonNode) {
        this.containers = [];
        this.widgetTags = [];
        this.widgetInstances = [];
        this.widgetTagsByName = {};
        this.containersByName = {};
        /* process map widgets */
        if (jsonNode.MapWidget) {
            for (var i=0; i<jsonNode.MapWidget.length; i++) {
                var widget = new Fusion.Lib.ApplicationDefinition.Widget(jsonNode.MapWidget[i]);
                widget.widgetSet = this;
                this.mapWidgetTag = widget;
                this.mapId = jsonNode.MapWidget[i].MapId[0];
            }
        }

        /* process widgets */
        if (jsonNode.Widget) {
            for (var i=0; i<jsonNode.Widget.length; i++) {
                var widget = new Fusion.Lib.ApplicationDefinition.Widget(jsonNode.Widget[i]);
                widget.widgetSet = this;
                this.widgetTags.push(widget);
                this.widgetTagsByName[widget.name] = widget;
            }
        }
        /* process containers */
        if (jsonNode.Container) {
            for (var i=0; i<jsonNode.Container.length; i++) {
                var container = new Fusion.Lib.ApplicationDefinition.Container(jsonNode.Container[i]);
                this.containers.push(container);
                this.containersByName[container.name] = container;
            }
        }

    },

    /**
     * Function: addWidgetInstance
     *
     * keep track of live widgets created in this widgetSet
     *
     * Parameter: {<Fusion.Widget>} widget
     *
     * the widget to add
     */
    addWidgetInstance: function(widget) {
        this.widgetInstances.push(widget);
    },

    /**
     * Function: getMapWidget
     *
     * return the map widget for this widget set
     *
     * Returns: {<Fusion.Lib.Map>} a map widget or null
     */
    getMapWidget: function() {
        return this.mapWidget;
    },

    /**
     * Function: create
     *
     * create all the things required by this widgetSet, including
     * containers and widgets.
     *
     * Parameter: {<Fusion.Lib.ApplicationDefinition>}
     *
     * the application definition that this widgetSet is part of
     */
    create: function(appDef) {

        //allow over-ride of mapId via URL parameter
        var mapGroup = null;
        var paramMapId = Fusion.getQueryParam('mapid');
        if (paramMapId.length>0) {
          mapGroup = appDef.getMapGroup(paramMapId);
          if (mapGroup) {
            this.mapId = paramMapId;
          } else {
            Fusion.reportError(new Fusion.Error(Fusion.Error.WARNING,
              "can't find MapGroup: " + paramMapId + ' - reverting to default map'));
          }
        }

        if (!mapGroup) {
          mapGroup = appDef.getMapGroup(this.mapId);
        }

        //create the Map widget for this WidgetSet
        this.mapWidget = new Fusion.Widget.Map(this.mapWidgetTag,mapGroup,this);
        this.mapWidget.setMenu();
        $(this.mapWidgetTag.name).widget = this.mapWidget;

        //create the floating map message
        //not sure why this is here and it creates an area on the map where 
        //the mouse can't interact with the map
        // is this a debug widget? madair
        
        this.mapWidget.message = new Fusion.MapMessage(this.mapWidget.oMapOL.viewPortDiv);
        this.mapWidget.registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, (function(){
                if (this.message != null){
                    this.message.refreshLayout();
                }
            }).bind(this.mapWidget));
        
            
        //create all the other widgets for the widget set
        for (var i=0; i<this.widgetTags.length; i++) {
            this.widgetTags[i].create(this);
        }

        //create all the containers for the widget set
        for (var i=0; i<this.containers.length; i++) {
            this.containers[i].create(this);
        }
    },
    /**
     * Function: getMapByName
     *
     * return the map widget from this widget set if the map's name
     * matches the requested name, or null.
     *
     * Parameter: {String} name
     *
     * The map name to check
     *
     * Returns: {Object} a map object or null.
     */
    getMapByName : function(name) {
        var map = null;
        for (var i=0; i<this.mapWidget.aMaps.length; ++i) {
          var mapLayer = this.mapWidget.aMaps[i];
          if (mapLayer.getMapName() == name) {
              map = mapLayer;
          }
        }
        return map;
    },


    /**
     * Function getWidgetsByType
     *
     * returns an array of widgets by type.
     *
     * Parameter: {String} type
     *
     * the type of widget to get references to
     *
     * Returns: {Array} an array of widgets, which may be empty
     */
    getWidgetsByType: function(type) {
        var widgets = [];
        for (var i=0; i<this.widgetInstances.length; i++) {
            if (this.widgetInstances[i].type == type) {
                widgets.push(this.widgetInstances[i]);
            }
        }
        return widgets;
    },

    getWidgetByName: function(name) {
        return this.widgetTagsByName[name];
    },

    getContainerByName: function(name) {
        return this.containersByName[name];
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.Container
 *
 * Holds an internal representation of Container objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.Container = OpenLayers.Class({
    name: null,
    type: null,
    validPositions: ['top', 'left', 'bottom', 'right'],
    position: 'top',
    items: null,
    initialize: function(jsonNode) {
        this.type = jsonNode.Type[0];
        this.name = jsonNode.Name[0];
        var position = jsonNode.Position ? jsonNode.Position[0].toLowerCase() : this.position;
        for (var i=0; i<this.validPositions.length; i++) {
            if (this.validPositions[i] == position) {
                this.position = position;
                break;
            }
        }
        this.items = [];
        if (jsonNode.Item) {
            for (var i=0; i<jsonNode.Item.length; i++) {
                var item = new Fusion.Lib.ApplicationDefinition.Item(jsonNode.Item[i]);
                this.items.push(item);
            }
        } else {
            //TODO: is this a problem if there are no items?
        }
    },

    create: function(widgetSet) {
        var container;
        if (this.type == 'Toolbar' || this.type == 'Statusbar') {
            if ($(this.name)) {
                if ($(this.name).retrieve('jxToolbar')) {
                    container = $(this.name).retrieve('jxToolbar');
                } else {
                  container = new Jx.Toolbar({parent: this.name, position: this.position, scroll:true});
                }
                $(this.name).container = container;
                this.createWidgets(widgetSet, container);
            }
        } else if (this.type == 'Splitterbar') {
            if ($(this.name)) {
                container = new Jx.Splitter(this.name, {splitInto: this.items.length});
                for (var i=0; i<this.items.length; i++) {
                    container.elements[i].id = this.name + '_' + i;
                }
                $(this.name).container = container;
                this.createWidgets(widgetSet, container);
            }
        }
        if (container && container.domObj.jxLayout) {
            container.domObj.jxLayout.resize({forceResize: true});
        }
    },

    createWidgets: function(widgetSet, container) {
        for (var i=0; i<this.items.length; i++) {
            this.items[i].create(widgetSet, container, this.name + '_' + i);
        }
    }

});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.Widget
 *
 * Holds an internal representation of Widget objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.Widget = OpenLayers.Class({
    name: null,
    type: null,
    statusText: null,
    location: null,
    imageUrl: null,
    imageClass: null,
    tooltip: null,
    label: null,
    disabled: null,
    extension: null,
    initialize: function(jsonNode) {
        if (jsonNode) {
            this.type = jsonNode.Type[0];
            this.name = jsonNode.Name ? jsonNode.Name[0] : '';
            this.statusText = jsonNode.StatusText ? jsonNode.StatusText[0] : '';
            this.statusText = OpenLayers.i18n(this.statusText);
            //TODO: this may be an extension
            this.location = jsonNode.Location ? jsonNode.Location[0] : 'widgets/';
            if (this.location.slice(-1) != '/') {
                this.location += '/';
            }
            this.imageUrl = jsonNode.ImageUrl ? jsonNode.ImageUrl[0] : '';
            this.imageClass = jsonNode.ImageClass ? jsonNode.ImageClass[0] : '';
            this.tooltip = jsonNode.Tooltip ? jsonNode.Tooltip[0] : '';
            this.tooltip = OpenLayers.i18n(this.tooltip);
            this.label = jsonNode.Label ? jsonNode.Label[0] : '';
            this.label = OpenLayers.i18n(this.label);
            this.disabled = jsonNode.Disabled ? (jsonNode.Disabled[0].toLowerCase() == 'true' ? true : false) : false;

            //console.log('Widget: ' + this.type + ', ' + this.name + ', ' + this.description);

            if (jsonNode.Extension) {
                this.extension = jsonNode.Extension[0];
            } else {
                this.extension = {};
            }
            //require the widget code
            if ( !Fusion.Widget[this.type] ) {
              Fusion.require(this.location + this.type + '.js');
            }
        }
    },

    getMapWidget: function() {
        if (this.widgetSet) {
            return this.widgetSet.getMapWidget();
        } else {
            return null;
        }
    },

    /**
     * Function: create
     *
     * creates a new instance of the widget, optionally using a
     * different name during instantiation to accomodate
     * containers
     *
     * Parameter: name
     *
     * An optional name to use for the widget, overrides the
     * original name temporarily if passed.
     *
     * Returns: an instance of the widget represented by this
     * object.
     */
    create: function(widgetSet, widgetName) {
        var widget = null;
        this.widgetSet = widgetSet;
        var oldName = this.name;
        if (typeof widgetName == 'undefined') {
            widgetName = this.name;
        }
        /* create the widget if the name is not null and the name
         * is either an empty string (in the case of buttons in menus)
         * or something exists in the dom with the right id
         */
        if (widgetName != null && (widgetName == '' || $(widgetName) != null)) {
            this.name = widgetName;
            widget = new Fusion.Widget[this.type](this);
            widgetSet.addWidgetInstance(widget);
            if (this.name.length>0 && $(this.name)) {
                widget.id = this.name;
                $(this.name).widget = widget;
                if (widget.uiClass) {
                  widget.setUiObject(new widget.uiClass({
                      label: this.label,
                      image: this.imageUrl,
                      imageClass: this.imageClass,
                      toggle: this.isExclusive
                  }));
                    if (widget.uiObj.addTo) {
                        widget.uiObj.addTo($(this.name));
                    }
                    if (widget.uiObj.setEnabled) {
                        widget.uiObj.setEnabled(widget.isEnabled());
                        widget.registerForEvent(Fusion.Event.WIDGET_STATE_CHANGED, function() {
                            widget.uiObj.setEnabled(widget.isEnabled());
                        });
                    }
                }
            }
            this.name = oldName;
        }
        return widget;
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.Item
 *
 * Holds an internal representation of Menu Item objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.Item = OpenLayers.Class({
    uniqueId: [0],
    type: null,
    initialize: function(jsonNode) {
        this.type = jsonNode.Function[0];
        switch(this.type) {
            case 'Widget':
                this.widgetName = jsonNode.Widget[0];
                break;
            case 'Flyout':
                this.flyout = new Fusion.Lib.ApplicationDefinition.Flyout(jsonNode);
                break;
            case 'Multi':
                this.multi = new Fusion.Lib.ApplicationDefinition.Multi(jsonNode);
                break;
            case 'Separator':
                break;
        }
    },

    create: function(widgetSet, container, idx) {
        switch(this.type) {
            case 'Widget':
                var widgetTag = widgetSet.getWidgetByName(this.widgetName);
                if (widgetTag) {
                    var name = 'FusionItem'+this.uniqueId[0];
                    this.uniqueId[0]++;
                    if (container instanceof Jx.Toolbar) {
                        var widget = widgetTag.create(widgetSet, '');
                        if (!widget.uiClass) {
                            widget.uiClass = Jx.Toolbar.Item;
                            widget.setUiObject(new widget.uiClass());
                        } else if (widget.uiClass == Jx.Button || widget.uiClass == Jx.Button.Color || widget.uiClass == Jx.Menu || widget.uiClass == Jx.Button.Flyout) {
                            widget.setUiObject(new widget.uiClass({
                                label: widgetTag.label,
                                image: widgetTag.imageUrl,
                                imageClass: widgetTag.imageClass,
                                toggle: widget.isExclusive
                            }));
                        }
                        container.add(widget.uiObj);
                        if (widget.uiObj.setEnabled) {
                            widget.uiObj.setEnabled(widget.isEnabled());
                            widget.registerForEvent(Fusion.Event.WIDGET_STATE_CHANGED, function() {
                                widget.uiObj.setEnabled(widget.isEnabled());
                            });
                        }
                    } else if (container instanceof Jx.Splitter) {
                        var widget = widgetTag.create(widgetSet, idx);
                    } else if (container instanceof Jx.Menu ||
                               container instanceof Jx.Menu.Context ||
                               container instanceof Jx.Menu.SubMenu) {
                        var widget = widgetTag.create(widgetSet, '');
                        if (!widget.uiClass || widget.uiClass == Jx.Button) {
                            widget.uiClass = Jx.Menu.Item;
                        } else if (widget.uiClass == Jx.Menu) {
                            widget.uiClass = Jx.Menu.SubMenu;
                        }
                        widget.setUiObject(new widget.uiClass({
                            label: widgetTag.label,
                            image: widgetTag.imageUrl,
                            imageClass: widgetTag.imageClass,
                            toggle: widget.isExclusive
                        }));
                        container.add(widget.uiObj);
                        if (widget.uiObj.setEnabled) {
                            widget.uiObj.setEnabled(widget.isEnabled());
                            widget.registerForEvent(Fusion.Event.WIDGET_STATE_CHANGED, function() {
                                widget.uiObj.setEnabled(widget.isEnabled());
                            });
                        }
                    } else if (container instanceof Jx.Button.Multi) {
                        var widget = widgetTag.create(widgetSet, '');
                        widget.uiClass = Jx.Button;
                        widget.setUiObject(new widget.uiClass({
                            label: widgetTag.label,
                            image: widgetTag.imageUrl,
                            imageClass: widgetTag.imageClass,
                            toggle: widget.isExclusive
                        }));
                        container.add(widget.uiObj);
                        if (widget.uiObj.setEnabled) {
                            widget.uiObj.setEnabled(widget.isEnabled());
                            widget.registerForEvent(Fusion.Event.WIDGET_STATE_CHANGED, function() {
                                widget.uiObj.setEnabled(widget.isEnabled());
                                var b = !container.buttons.some(function(w) {
                                  return !w.isEnabled();
                                });
                                container.setEnabled(b);
                            });
                        }
                    }
                } else {
                  Fusion.reportError(new Fusion.Error(Fusion.Error.WARNING,
                    "can't find widget: " + this.widgetName));
                 }
                break;
            case 'Flyout':
                /* create a menu */
                var menu;
                var opt = {
                    label: this.flyout.label,
                    tooltip: this.flyout.tooltip,
                    image: this.flyout.imageUrl,
                    imageClass: this.flyout.imageClass
                };
                if (container instanceof Jx.Toolbar) {
                    menu = new Jx.Menu(opt);
                } else if (container instanceof Jx.Menu ||
                           container instanceof Jx.Menu.Context ||
                           container instanceof Jx.Menu.SubMenu) {
                    menu = new Jx.Menu.SubMenu(opt);
                }
                container.add(menu);
                this.flyout.create(widgetSet, menu);

                break;
            case 'Multi':
                var multi;
                var opt = {
                    label: this.multi.label,
                    tooltip: this.multi.tooltip,
                    image: this.multi.imageUrl,
                    imageClass: this.multi.imageClass
                }
                multi = new Jx.Button.Multi(opt);
                container.add(multi);
                this.multi.create(widgetSet, multi);
                break;

            case 'Separator':
                if (container instanceof Jx.Toolbar) {
                    container.add(new Jx.Toolbar.Separator());
                } else if (container instanceof( Jx.Menu) ||
                           container instanceof(Jx.Menu.SubMenu) ||
                           container instanceof(Jx.Menu.Context)) {
                    container.add(new Jx.Menu.Separator());
                }
                break;
        }
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.Flyout
 *
 * Holds an internal representation of Flyout objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.Flyout = OpenLayers.Class({
    label: null,
    tooltip: null,
    description: null,
    imageUrl: null,
    items: null,

    initialize: function(jsonNode) {
        this.label = jsonNode.Label ? jsonNode.Label[0] : '';
        this.tooltip = jsonNode.Tooltip ? jsonNode.Tooltip[0] : '';
        this.description = jsonNode.Description ? jsonNode.Description[0] : '';
        this.imageUrl = jsonNode.ImageUrl ? jsonNode.ImageUrl[0] : '';
        this.items = [];
        if (jsonNode.Item instanceof Array) {
            for (var i=0; i<jsonNode.Item.length; i++) {
                this.items.push(new Fusion.Lib.ApplicationDefinition.Item(jsonNode.Item[i]));
            }
        }
    },

    create: function(widgetSet, menu) {
        for (var i=0; i<this.items.length; i++) {
            this.items[i].create(widgetSet, menu);
        }
    }

});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.Multi
 *
 * Holds an internal representation of Multi objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.Multi = OpenLayers.Class({
    label: null,
    tooltip: null,
    description: null,
    imageUrl: null,
    items: null,

    initialize: function(jsonNode) {
        this.label = jsonNode.Label ? jsonNode.Label[0] : '';
        this.tooltip = jsonNode.Tooltip ? jsonNode.Tooltip[0] : '';
        this.description = jsonNode.Description ? jsonNode.Description[0] : '';
        this.imageUrl = jsonNode.ImageUrl ? jsonNode.ImageUrl[0] : '';
        this.items = [];
        if (jsonNode.Item instanceof Array) {
            for (var i=0; i<jsonNode.Item.length; i++) {
                this.items.push(new Fusion.Lib.ApplicationDefinition.Item(jsonNode.Item[i]));
            }
        }
    },

    create: function(widgetSet, multi) {
        for (var i=0; i<this.items.length; i++) {
            this.items[i].create(widgetSet, multi);
        }
    }

});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.SearchDefinition
 *
 * Holds an internal representation of SearchDefinition objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.SearchDefinition = OpenLayers.Class({
    id: null,
    name: null,
    category: null,
    parameters: null,
    join: null,
    rule: null,

    initialize: function(json) {
        this.id = json['@id'];
        this.name = json['@name'];
        if (json.Join instanceof Array) {
            this.join = new Fusion.Lib.ApplicationDefinition.SearchJoin(json.Join[0]);
        }
        this.parameters = [];
        if (json.Parameter instanceof Array) {
            for (var i=0; i<json.Parameter.length; i++) {
                this.parameters.push(json.Parameter[i]['@name']);
            }
        }
        this.reports = [];
        if (json.Report instanceof Array) {
            for (var i=0; i<json.Report.length; i++) {
              var label = json.Report[i].Label[0]
              var link = json.Report[i].Link[0]
              this.reports.push({'label':label, 'link':link});
            }
        }
        var rule;
        if (json.SearchAnd instanceof Array) {
            this.rule = new Fusion.Lib.ApplicationDefinition.SearchRule('AND');
            rule = json.SearchAnd[0];
        } else if (json.SearchOr instanceof Array) {
            this.rule = new Fusion.Lib.ApplicationDefinition.SearchRule('OR');
            rule = json.SearchOr[0];
        }
        if (rule && rule.SearchCondition instanceof Array) {
            for (var i=0; i<rule.SearchCondition.length; i++) {
                this.rule.add(new Fusion.Lib.ApplicationDefinition.SearchCondition(rule.SearchCondition[i]));
            }
        }
    },

    getJoinUrl: function(params) {
        if (this.join) {
            return '&joinlayer='+this.join.layer+'&joinpk='+this.join.primaryKey+'&joinfk='+this.join.foreignKey;
        } else {
            return '';
        }
    },

    getFilterUrl: function(params) {
        return '&filter='+encodeURIComponent(this.rule.toString(params));
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.SearchJoin
 *
 * Holds an internal representation of SearchJoin objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.SearchJoin = OpenLayers.Class({
    layer: null,
    primaryKey: null,
    foreignKey: null,
    initialize: function(json) {
        this.layer = json.Layer ? json.Layer[0] : '';
        this.primaryKey = json.PrimaryKey ? json.PrimaryKey[0] : '';
        this.foreignKey = json.ForeignKey ? json.ForeignKey[0] : '';
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.SearchRule
 *
 * Holds an internal representation of SearchRule objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.SearchRule = OpenLayers.Class({
    type: null,
    conditions: null,
    initialize: function(type) {
        this.type = type;
        this.conditions = [];
    },

    add: function(condition) {
        this.conditions.push(condition);
    },

    remove: function(condition) {
        for (var i=0; i<this.conditions.length; i++) {
            if (this.conditions[i] == condition) {
                this.conditions.splice(i, 1);
                break;
            }
        }
    },

    toString: function(params) {
        var conditions = [];
        for (var i=0; i<this.conditions.length; i++) {
            this.conditions[i].setParams(params);
            var c = this.conditions[i].toString();
            if (c != '') {
                conditions.push(c);
            }
        }
        return '(' + conditions.join(') ' + this.type + ' (') + ')';
    }
});

/****************************************************************************
 * Class: Fusion.Lib.ApplicationDefinition.SearchCondition
 *
 * Holds an internal representation of SearchCondition objects as defined in the AppDef
 *
 */

Fusion.Lib.ApplicationDefinition.SearchCondition = OpenLayers.Class({
    column: null,
    operator: null,
    parameter: null,
    quote: null,
    value: null,
    operators: {eq:'=', like:'like', lt:'<', lte:'<=', gt:'>', gte:'>=', neq:'<>'},
    includeIfEmpty: false,

    initialize: function(json) {
        this.column = json.Column[0];
        this.operator = this.operators[json.Operator[0].toLowerCase()];
        this.parameter = json.Parameter[0];
        this.quote = json['@quote'] ? json['@quote'] : '';
        this.wildcard = json['@wildcard'] ? json['@wildcard'] : 'both';
        this.caseSensitive = true;
        if (json['@caseSensitive'] && json['@caseSensitive'] == 'false') {
            this.caseSensitive = false;
        }
    },

    setParams: function(p) {
        if (p[this.parameter]) {
            this.value = p[this.parameter];
        } else {
            this.value = '';
        }
    },

    toString: function() {
        var value = this.value ? this.value : '';
        if (value == '' && !this.includeIfEmpty) {
            return '';
        }
        var upper = '';
        if (!this.caseSensitive) {
            value = value.toUpperCase();
            upper = 'Upper';
        }
        var prewildcard = '';
        var prewildcard = '';
        var postwildcard = '';
        if (this.operator == 'like') {
            if (this.wildcard == 'before' || this.wildcard == 'both') {
                prewildcard = '*';
            }
            if (this.wildcard == 'after' || this.wildcard == 'both') {
                postwildcard = '*';
            }
        }
        var wildcard = this.operator == 'like' ? '*' : '';
        return upper + '('+this.column + ') ' + this.operator + ' ' + this.quote + prewildcard + value + postwildcard + this.quote;
    }
});
/**
 * Fusion.Lib.MGBroker
 *
 * $Id: MGBroker.js 2827 2014-02-08 17:41:35Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
 
/****************************************************************************
 * Class: Fusion.Lib.MGBroker
 *
 * MGBroker is used to broker requests to the MapGuide Open Source
 * mapagent interface.  It is a very simple class that is configured
 * with a URL and credentials via the <Fusion.Lib.MGBroker.setSiteURL> method and can
 * send requests to the server via the <Fusion.Lib.MGBroker.dispatchRequest> method.
 */

Fusion.Lib.MGBroker = OpenLayers.Class({
    /**
     * the URL to a MapGuide Open Source installation.  Set this using
     * <Fusion.Lib.MGBroker.setSiteURL>
     * @type String
     */
    mapGuideURL : '',
    /**
     * the agent URL for the MapGuide Open Source installation.  Set from
     * <Fusion.Lib.MGBroker.setSiteURL>
     * @type String
     */
    mapAgentURL : '',
    
    /**
     * default method for communicating with the server, if set
     * this overrides the individual request methods
     */
    method: null,
    
    /**
     * @class
     * MGBroker constructor
     *
     * @constructor
     * create a new MGBroker instance
     */
    initialize : function() { 
    },
    /**
     * Method: dispatchRequest
     * 
     * send a request to the MapGuide Open Source server using
     * XMLHttpRequest and return the result to the specified
     * function.
     * 
     * Parameters:
     * r - {Object} an <Fusion.Lib.MGRequest>-subclass instance that
     *        defines the operation to request.
     * f - {Function} a function object to invoke when the
     *        XMLHttpRequest call completes
     */
    dispatchRequest : function(r, f) {
        var s = r.encode() + '&ts='+(new Date()).getTime();
        if (this.method) {
            r.options.method = this.method;
        }
        if (!r.options.onException) {
            r.options.onException = OpenLayers.Function.bind(Fusion.ajaxException, Fusion);
        }
        var a = new OpenLayers.Ajax.Request( this.mapAgentURL, 
            OpenLayers.Util.extend({parameters:r.parameters, onComplete:f }, r.options));
        a.originalRequest = r;
    },
    /**
     * Method: setSiteURL
     * 
     * set up a connection to a MapGuide Open Source site.  This function
     * expects that url is in the form http(s)://<address>/path-to-mapguide.
     * Path-to-mapguide is should be the base URL to a MapGuide Open
     * Source install.  It is expected that the mapagent is
     * in the expected place (mapagent/mapagent.fcgi) under that URL.  If
     * (for some strange reason) its not, then you can include the full
     * path to mapagent.fcgi in the URL and this function won't try to
     * guess its location.
     * The user name and password are passed on using basic HTML
     * authentication (http://<user>:<pass>@<server>/path-to-mapguide).
     * 
     * Parameters:
     * url - {String} a properly formatted universal reverse locator
     *        to a MapGuide Open Source installation.
     * user - {String} a valid user name
     * pass - {String} the password for the given user.
     */
    setSiteURL : function(url, user, pass) {
        //url = url.replace('://', '://'+user+':'+pass+'@');
        this.user = user;
        this.pass = pass;
        if (url.indexOf('mapagent.fcgi') == -1) {
            if (url.charAt(url.length - 1) != '/') {
                url = url + '/';
            }
            this.mapGuideURL = url;            
            url = url + 'mapagent/mapagent.fcgi';
        }
        this.mapAgentURL = url;
    },
    /**
     * Method: clearSiteURL
     * 
     * remove all authentication information from the broker
     */
    clearSiteURL: function() {
        this.user = '';
        this.pass = '';
        this.mapGuideURL = '';
        this.mapAgentURL = '';
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest
 *
 * MGRequest is the base class for all broker-compatible requests.  A request
 * is a wrapper around an operation that is supported by the mapagent.
 */
Fusion.Lib.MGRequest = OpenLayers.Class({
    /**
     * core options shared by all requests
     */
    options : null,
    
    /**
     * core parameters shared by all requests
     */
    parameters : null,
    
    /**
     * @constructor
     * initialize a new instance of MGRequest
     */
    initializeRequest : function() {
        this.options = { method:'post' };
        this.parameters = { version : '1.0.0', locale : Fusion.locale, clientagent : 'Fusion Viewer' };
    },
    
    /**
     * Method: setParams
     * 
     * set the parameters associated with this request.  Parameters are
     * dependent on the specific MGRequest subclass except for two
     * mandatory parameters, version and locale, that are provided by
     * this base class.
     *
     * Parameters:
     * o - {Object} an object that contains named key : value pairs representing parameters to a request
     */
    setParams : function( o ){ OpenLayers.Util.extend( this.parameters, (o || {}) ); },

    /**
     * Method: setOptions
     * 
     * set the options associated with this request
     * 
     * Parameters:
     * o - {Object} an object that contains named key : value pairs representing for a request
     */
    setOptions : function( o ){ OpenLayers.Util.extend( this.options, (o || {}) ); },
    
    /**
     * Method: encode
     * 
     * returns a string containing all the request parameters in URL form suitable
     * for appending to a URL.
     * 
     * Return:
     * {String} the parameters in URL form.
     */
    encode : function() {
        var s = sep = '';
        for (var p in this.parameters) {
            if (this.parameters[p]) {
                s = s + sep + p + '=' + encodeURI(this.parameters[p]);
            }
            sep = '&';
        }
        return s;
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGEnumerateResources
 *
 * encapsulate a request to the server to enumerate resources in the library.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGEnumerateResources = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGEnumerateResources
     * 
     * initialize a new instance of MGEnumerateResources
     *
     * Parameters:
     * resourceID - {String} optional parameter indicating the resource
     *              to enumerate.  If not set or null, it defaults to "Library://" which
     *              is the root of the library.
     *
     * type - {String} optional parameter indicating the type of resources
     *        to enumerate.  If not set, it will default to an empty string which
     *        indicates all types will be returned.
     *
     * depth - {Integer} optional parameter that controls the depth of the
     *         resource tree to enumerate.  If not set, it will default to -1 which
     *         means the tree will be fully enumerated.
     *
     */
    initialize : function( resourceID, type, depth ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'ENUMERATERESOURCES',
            resourceid : (resourceID || "Library://"),
            type : (type || ""),
            depth : (typeof depth == 'undefined' ? -1 : depth) } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGGetResourceContent
 *
 * encapsulate a request to the server to get resource contents from the library.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGGetResourceContent = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGGetResourceContent
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetResourceContent
     *
     * Parameters:
     * resourceID - {String} optional parameter indicating the resource
     *              to enumerate.  If not set or null, it defaults to "Library://" which
     *              is the root of the library.
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETRESOURCECONTENT',
            resourceid : (resourceID || "Library://")
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGGetResourceHeader
 *
 * encapsulate a request to the server to get resource header from the library.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGGetResourceHeader = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGGetResourceHeader
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetResourceHeader
     *
     * Parameters:
     * resourceID - {String} optional parameter indicating the resource
     *              to enumerate.  If not set or null, it defaults to "Library://" which
     *              is the root of the library.
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETRESOURCEHEADER',
            resourceid : (resourceID || "Library://")
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGCreateSession
 *
 * encapsulate a request to the server to create a new session on the server.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGCreateSession = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGCreateSession
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGCreateSession
     */
    initialize : function( ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'CREATESESSION'
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGCreateRuntimeMap
 *
 * encapsulate a request to the server to create a new runtime map (and session if required)
 * on the server
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGCreateRuntimeMap = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGCreateRuntimeMap
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGCreateRuntimeMap
     */
    initialize: function(mapDefinition, features, iconsPerScaleRange) {
        this.initializeRequest();
        this.setParams({
            operation: "CREATERUNTIMEMAP",
            mapdefinition: mapDefinition,
            requestedfeatures: features,
            iconsperscalerange: iconsPerScaleRange,
            format: "application/json",
            version: "2.6.0"
        });
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGDescribeRuntimeMap
 *
 * encapsulate a request to the server to create a new runtime map (and session if required)
 * on the server
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGDescribeRuntimeMap = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGCreateRuntimeMap
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGCreateRuntimeMap
     */
    initialize: function(mapName, features, iconsPerScaleRange) {
        this.initializeRequest();
        this.setParams({
            operation: "DESCRIBERUNTIMEMAP",
            mapname: mapName,
            requestedfeatures: features,
            iconsperscalerange: iconsPerScaleRange,
            format: "application/json",
            version: "2.6.0"
        });
    }
});


/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGCopyResource
 *
 * encapsulate a request to the server to copy a resource.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGCopyResource = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGCopyResource
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGCopyResource
     *
     * Parameters:
     * sourceID - {String} the Resource ID of the source
     * destinationID - {String} the Resource ID of the destination
     * overwrite - {Boolean} overwrite the destination if it exists
     */
    initialize : function( sourceID, destinationID, overwrite ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'COPYRESOURCE',
            source : sourceID,
            destination: destinationID,
            overwrite : overwrite
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGDeleteResource
 *
 * encapsulate a request to the server to delete a resource.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGDeleteResource = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGDeleteResource
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGDeleteResource
     *
     * Parameters:
     * resourceID - {String} the id of the resource to delete
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'DELETERESOURCE',
            resourceid : resourceID
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGMoveResource
 *
 * encapsulate a request to the server to move a resource in the repository.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGMoveResource = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGMoveResource
     * initialize a new instance of Fusion.Lib.MGRequest.MGMoveResource
     *
     * Parameters:
     * sourceID - {String} the Resource ID of the source
     * destinationID - {String} the Resource ID of the destination
     * overwrite - {Boolean} overwrite the destination if it exists
     */
    initialize : function( sourceID, destinationID, overwrite ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'MOVERESOURCE',
            source : sourceID,
            destination : destinationID,
            overwrite : overwrite
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGMoveResource
 *
 * encapsulate a request to the server to set the content XML of a resource.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGMoveResource = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGMoveResource
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGMoveResource
     */
    initialize : function( resourceID, content, header ) {
        this.initializeRequest();
        this.setParams( {
            method: 'post', /* SetContent requires post method */
            operation : 'SETRESOURCE',
            resourceid : resourceID,
            content : content,
            header : header
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGDescribeSchema
 *
 * encapsulate a request to the server to describe the schema of a FeatureSource.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGDescribeSchema = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGDescribeSchema
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGDescribeSchema
     *
     * Parameters:
     * resourceID - {String} the id of the resource to describe the schema for
     * schema - {String} what does this do?
     */
    initialize : function( resourceID, schema ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'DESCRIBEFEATURESCHEMA',
            resourceid : resourceID,
            schema : schema
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGGetSpatialContexts
 *
 * encapsulate a request to the server to retrieve the spatial context of a resource.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGGetSpatialContexts = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGGetSpatialContexts
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetSpatialContexts
     *
     * Parameters:
     * resourceID - {String} the id of the resource to retrieve the spatial context for
     * activeonly - {Boolean} what does this do?
     */
    initialize : function(resourceID, activeonly) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETSPATIALCONTEXTS',
            resourceid : resourceID,
            activeonly : activeonly?'1':'0'
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGEnumerateResourceReferences
 *
 * encapsulate a request to the server to enumerate the references to a resource id.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGEnumerateResourceReferences = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGEnumerateResourceReferences
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGEnumerateResourceReferences
     *
     * Parameters:
     * resourceID - {String} the id of the resource to retrieve the spatial context for
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'ENUMERATERESOURCEREFERENCES',
            resourceid: resourceID
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGEnumerateResourceData
 *
 * encapsulate a request to the server to enumerate the data associated with
 * a FeatureSource
 * N.B. This does not enumerate resource data for 'unmanaged' FeatureSources
 *      (those referencing files or directories outside the respository)
 *      <Fusion.Lib.MGRequest.MGDescribeSchema> should be used for those sources.
 *
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGEnumerateResourceData = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGEnumerateResourceData
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGEnumerateResourceData
     *
     * Parameters:
     * resourceID - {String} the id of the FeatureSource to retrieve data for
     */
    initialize : function( resourceID ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'ENUMERATERESOURCEDATA',
            resourceid: resourceID
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGGetVisibleMapExtent
 *
 * Encapsulate a request to the server to get the visible map extent
 *      
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGGetVisibleMapExtent = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGGetVisibleMapExtent
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetVisibleMapExtent
     *
     * Parameters:
     * sessionId - {String} the id of the session to restore
     * mapName - {String} the name of the map
     * viewCenterX - {String} the horizontal center of the view
     * viewCenterY - {String} the vertical center of the view
     * viewScale - {String} the scale of the map
     * dataExtent - {String} the extent of the data 
     * displayDpi - {String} the DPI of the display
     * displayWidth - {String} the width of the map
     * displayHeight - {String} the height of the map
     * showLayers - {String} a list of layer names to show
     * hideLayers - {String} a list of layer names to hide
     * showGroups - {String} a list of group names to show
     * hideGroups - {String} a list of groupnames to hide
     * refreshLayers - {String} a list of layers that need to be refreshed
     */
    initialize : function( sessionId, mapName, viewCenterX, viewCenterY,
                           viewScale, dataExtent, displayDpi, displayWidth, 
                           displayHeight, showLayers, hideLayers, 
                           showGroups, hideGroups, refreshLayers ) {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETVISIBLEMAPEXTENT',
            session: sessionId,
            mapname: mapName,
            setviewcenterx: viewCenterX,
            setviewcentery: viewCenterY,
            setviewscale: viewScale,
            setdataextent: dataExtent,
            setdisplaydpi: displayDpi,
            setdisplaywidth: displayWidth,
            setdisplayheight: displayHeight,
            showlayers: showLayers,
            hidelayers: hideLayers,
            showgroups: showGroups,
            hidegroups: hideGroups,
            refreshlayers: refreshLayers
        } );
    }
});


/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGQueryMapFeatures
 *
 * encapsulate a request to the server to query map features on 
 * selectable layers
 * 
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGQueryMapFeatures = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGQueryMapFeatures
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGQueryMapFeatures
     *
     * Parameters:
     * sessionId - {String} the id of the session to restore
     * mapName - {String} the id of the session to restore
     * geometry (sting wkt} gemetry to use for selection.  Example : POLYGON(x1 y1, x2,y2)
     * maxFeatures - {integer} number of maximum results (-1 to indicate no maximum)
     * selectionPersist - {boolean} save the selection (valid values are 0 and 1) 
     * selectionVariant - {String} indicates the spatial operation. Valid values are 'INTERSECTS', ...
     * featureFilter - {String} filter crieteria to be applied for selection.
     * layerNames - {String} comma separated list of layer names to include in the query
     * layerAttributeFilter - {integer} bitmask determining layer selection behaviour (1=visible layers,
     *          2=selectable layers, 4=layers with tooltips)
     */
    initialize : function( sessionId, mapName, geometry, maxFeatures, persist, selectionVariant, featureFilter, layerNames, layerAttributeFilter ) 
    {
        this.initializeRequest();
        this.setParams( {
            operation : 'QUERYMAPFEATURES',
            format: "application/json",
            session: sessionId,
            mapname: mapName,
            geometry: geometry,
            maxFeatures: maxFeatures,
            persist: persist,
            selectionVariant: selectionVariant,
            featureFilter: featureFilter,
            layerNames: layerNames,
            layerAttributeFilter: layerAttributeFilter
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGQueryMapFeatures2
 *
 * encapsulate a request to the server to query map features on 
 * selectable layers
 * 
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGQueryMapFeatures2 = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGQueryMapFeatures2
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGQueryMapFeatures2
     *
     * Parameters:
     * sessionId - {String} the id of the session to restore
     * mapName - {String} the id of the session to restore
     * geometry (sting wkt} gemetry to use for selection.  Example : POLYGON(x1 y1, x2,y2)
     * maxFeatures - {integer} number of maximum results (-1 to indicate no maximum)
     * selectionPersist - {boolean} save the selection (valid values are 0 and 1) 
     * selectionVariant - {String} indicates the spatial operation. Valid values are 'INTERSECTS', ...
     * featureFilter - {String} filter crieteria to be applied for selection.
     * layerNames - {String} comma separated list of layer names to include in the query
     * layerAttributeFilter - {integer} bitmask determining layer selection behaviour (1=visible layers,
     *          2=selectable layers, 4=layers with tooltips)
     * requestData - {integer} a bitmask of the desired information to return (Attributes = 1 InlineSelection? = 2 Tooltip = 4 Hyperlink = 8)
     * selectionColor - {String} the html color for the inline selection image (if requested)
     * selectionFormat - {String} the format of the inline selection image (if requested)
     */
    initialize : function( sessionId, mapName, geometry, maxFeatures, persist, selectionVariant, featureFilter, layerNames, layerAttributeFilter, requestData, selectionColor, selectionFormat) 
    {
        this.initializeRequest();
        this.setParams( {
            operation : 'QUERYMAPFEATURES',
            format: "application/json",
            version: "2.6.0",
            session: sessionId,
            mapname: mapName,
            geometry: geometry,
            maxFeatures: maxFeatures,
            persist: persist,
            selectionVariant: selectionVariant,
            featureFilter: featureFilter,
            layerNames: layerNames,
            layerAttributeFilter: layerAttributeFilter,
            requestData: requestData,
            selectionColor: selectionColor,
            selectionFormat: selectionFormat
        } );
    }
});

/****************************************************************************
 * Class: Fusion.Lib.MGRequest.MGGetFeatureSetEnvelope
 *
 * encapsulate a request to the server to query map features on 
 * selectable layers
 * 
 * Inherits from:
 *  - <Fusion.Lib.MGRequest>
 */
Fusion.Lib.MGRequest.MGGetFeatureSetEnvelope = OpenLayers.Class(Fusion.Lib.MGRequest, {
    /**
     * Constructor: Fusion.Lib.MGRequest.MGGetFeatureSetEnvelope
     * 
     * initialize a new instance of Fusion.Lib.MGRequest.MGGetFeatureSetEnvelope
     *
     * Parameters:
     * sessionId - {String} the id of the session to restore
     * mapName - {String} the id of the session to restore
     * features - (String XML} a feature set selection XML
     */
    initialize : function( sessionId, mapName, features ) 
    {
        this.initializeRequest();
        this.setParams( {
            operation : 'GETFEATURESETENVELOPE',
            session: sessionId,
            mapname: mapName,
            featureSet: features
        } );
    }
});
/**
 * Fusion.Widget
 *
 * $Id: Widget.js 2827 2014-02-08 17:41:35Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /* ***************************************************************************
 * Class: Fusion.Widget
 *
 * This is the base class for all widgets.  It provides some basic
 * functionality that all widgets should need.
 *
 * ***************************************************************************/
 
Fusion.Event.WIDGET_STATE_CHANGED = Fusion.Event.lastEventId++;
 
Fusion.Widget = OpenLayers.Class(Fusion.Lib.EventMgr, {
    isExclusive: false,
    name: null,
    type: null,
    oMap: null,
    widgetLayerId: null,   //the AppDef <Map> object associated with this widget 
    enabled: false,
    mapLoadedWatcher: null,
    paramRegister: null,
    groups: [],
    group: null,
    domObj: null,
    uiClass: null,
    widgetUniqueId: null,
    
    /**
     * initialize the widget
     * @param name {string} the name of the widget
     */
    initialize: function(widgetTag) {
        this.type = widgetTag.type;
        this.name = widgetTag.name;
        this.widgetTag = widgetTag;
        this.registerEventID(Fusion.Event.WIDGET_STATE_CHANGED);
        if (!Fusion.Widget.uiInstances[this.type]) {
            Fusion.Widget.uiInstances[this.type] = [];
        }
        
        var group = widgetTag.extension.Group ? widgetTag.extension.Group[0] : '';
        if (group != '') {
            if (!this.groups[group]) {
                this.groups[group] = [];
            }
            this.groups[group].push(this);
            this.group = group;
        }
        this.setMap(widgetTag.getMapWidget());
        
        if (widgetTag.name) {
            this.domObj = $(widgetTag.name);
        }

        this.paramRegister = [];
        
        //check to see if this widget is associated with a specific Map
        var json = widgetTag.extension;
        this.widgetLayerId = json.WidgetLayerId ? json.WidgetLayerId[0] : null;
        this.widgetUniqueId = OpenLayers.Util.createUniqueID("FusionWidget_");
        
        this.initializeWidget(widgetTag);
    },

    initializeWidget: function() {},

    /**
     * Function: activate
     * 
     * Activates this widget
     * 
     */
    activate: function() { },

    /**
     * Function: deactivate
     * 
     * Deactivates this widget
     */
    deactivate: function() { },

    setUiObject: function(uiObj) {
        Fusion.Widget.uiInstances[this.type].push(this);
        if (this.widgetTag.tooltip) {
          if (uiObj.setTooltip) {
              uiObj.setTooltip(this.widgetTag.tooltip);
          } else if (uiObj.button && uiObj.button.setTooltip){
              uiObj.button.setTooltip(this.widgetTag.tooltip);
          }
        }
        if (this.isExclusive) {
        	if (Fusion.Widget.uiInstances[this.type].length==1) {
            this.getMap().buttonSet.add(uiObj);
          } else {
						var instances = Fusion.Widget.uiInstances[this.type];
						for (var i=0; i<instances.length; i++) {
							if (!this.shouldActivateWith(instances[i])) {
								this.getMap().buttonSet.add(uiObj);
								break;
							}
						}
					}
        }
        if (uiObj.options.active) {
            this.activate();
        }
        if (uiObj.addEvents) {
            if (Fusion.Widget.uiInstances[this.type][0].uiObj &&
                Fusion.Widget.uiInstances[this.type][0].uiObj.options.active &&
                Fusion.Widget.uiInstances[this.type][0].shouldActivateWith(this) &&
                uiObj.setActive) {
                uiObj.setActive(true);
            }
            
            uiObj.addEvents({
                'click': (function() {
                    this.activate();
                }).bind(this),
                'up': (function() {
                    var instances = Fusion.Widget.uiInstances[this.type];
                    for (var i=0; i<instances.length; i++) {
                        var instance = instances[i];
                        if (instance.shouldActivateWith(this) &&
                            instance.uiObj && instance.uiObj.setActive) {
                            instance.uiObj.setActive(false);
                        }
                    }
                    this.deactivate();
                }).bind(this),
                'down': (function() {
                    var instances = Fusion.Widget.uiInstances[this.type];
                    for (var i=0; i<instances.length; i++) {
                        var instance = instances[i];
                        if (instance.shouldActivateWith(this) &&
                            instance.uiObj && instance.uiObj.setActive) {
                            instance.uiObj.setActive(true);
                        }
                    }
                    this.activate();
                }).bind(this)
            });
        }
        this.uiObj = uiObj; 
    },
    
    /**
     * Method: shouldActivateWith
     *
     */
    shouldActivateWith: function(widget) {
        return true;
    },

    /**
     * Method: setMap
     *
     * set the map object that this widget is associated with
     * 
     * Parameters:
     * 
     *   oMap - {<Fusion.Widget.Map>} the map
     */
    setMap: function(oMap) {
        if (this.mapLoadedWatcher) {
            this.oMap.deregisterForEvent(Fusion.Event.MAP_LOADED, this.mapLoadedWatcher);
            this.mapLoadedWatcher = null;
        }
        
        this.oMap = oMap;
        if (oMap) {
            this.mapLoadedWatcher = OpenLayers.Function.bind(this._mapLoaded, this);
            oMap.registerForEvent(Fusion.Event.MAP_LOADED, this.mapLoadedWatcher);
        }
        
        if (oMap && oMap.isLoaded()) {
            this.enable();
        } else {
            this.disable();
        }
    },
    /**
     * Method: getMap
     *
     * accessor to get the Map object that this widget is associated with
     * 
     * Return: 
     * {<Fusion.Widget.Map>} the map
     */
    getMap: function() {
        return this.oMap;
    },
    
    /**
     * Method: getMapLayer
     *
     * accessor to get the implementation-specific Layer object that this widget is associated with
     * 
     * Return: 
     * {<Fusion.Layers>} the implementation-specific layer
     */
    getMapLayer: function() {
      if (this.widgetLayerId) {
        var map = this.getMap();
        for (var i=0; i<map.aMaps.length; ++i) {
          if (map.aMaps[i].id && (map.aMaps[i].id == this.widgetLayerId) ) {
            return map.aMaps[i];
          }
        }
        Fusion.reportFatalError("Requested widgetLayerId not found:"+this.widgetLayerId);
        return null;
      } else {
        return this.getMap().aMaps[0];
      }
    },
    
    /**
     * Method: addControl
     * 
     * utility method to add an {<OpenLayers.Control>} control to the internal {<OpenLayers.Map>} object
     * 
     * Parameters:
     * control - {<OpenLayers.Control>} the control to add
     */
    addControl: function(control) {
        this.getMap().oMapOL.addControl(control);
    },
    
    /**
     */
    _mapLoaded: function() {
        if (this.oMap && this.oMap.isLoaded()) {
            //console.log('enable');
            this.enable();
        } else {
            //console.log('disable');
            this.disable();
        }
    },
    
    /** 
     * Method: setMutEx
     *
     * sets whether this widget is mutually exclusive on its map
     * 
     * Parameters:
     * bIsMutEx - {boolean} is the widget mutually exclusive?
     */
    setMutEx: function(bIsMutEx) {
        this.isExclusive = bIsMutEx;
    },
    
    /**
     * Method: isMutEx
     * 
     * accessor to determine if the widget should be activated mutually
     * exclusively from other widgets on the map.
     * 
     * Return: 
     * {boolean} true if the widget is mutually exclusive
     */
    isMutEx: function() {
        return this.isExclusive;
    },
    
    /**
     * Method: getName
     *
     * accessor to return the name of the widget.  Mostly for debugging
     * 
     * Return: 
     * {string} the name of the widget
     */
    getName: function() {
        return this.name;
    },
    
    /**
     * Method: getLocation
     *
     * returns the location of this widget relative to the installation
     * of fusion.  Can be used to construct URLs to resources that
     * the widget needs.
     *
     * Return: 
     * {String} the location of this widget
     */
    getLocation: function() {
        return this.widgetTag.location;    
    },
    
    /**
     * Method: isEnabled
     * 
     * Gets whether this widget is enabled
     */
    isEnabled: function() { 
        return this.enabled; 
    },
    
    /**
     * Method: enabled
     * 
     * Enables this widget. Triggers the {<Fusion.Event.WIDGET_STATE_CHANGED>} event
     */
    enable: function() { 
        this.enabled = true; 
        this.triggerEvent(Fusion.Event.WIDGET_STATE_CHANGED, this);
    },

    /**
     * Method: disable
     * 
     * Disables this widget. Triggers the {<Fusion.Event.WIDGET_STATE_CHANGED>} event
     */
    disable: function() { 
        this.enabled = false; 
        this.triggerEvent(Fusion.Event.WIDGET_STATE_CHANGED, this);
    },

    setParameter : function(param, value){},

    registerParameter : function(param) {
      this.paramRegister.push(param);
    }
});

Fusion.Widget.uiInstances = {};
/**
 * Fusion.Tool.Search
 *
 * $Id: Search.js 1377 2008-04-16 19:27:32Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Tool.Search
 *
 * The Fusion search mechanism defines a common capability for searches that
 * link individual searches to a common search set maintained for all
 * search-based widgets.  The common search set can be statically and/or
 * dynamically created.  Static definition is done in the WebLayout.  Dynamic
 * creation is done by individual widgets and creating instances of
 * Fusion.Tool.SearchDefinition(s) - or rather a MGSearchDefinition.
 *
 * Widgets that want to take advantage of this behaviour can sub-class this
 * widget.  Just make sure to initialize properly!
 * 
 * **********************************************************************/

Fusion.Tool.Search = OpenLayers.Class({
    lastSearch: null,
    lastResult: null,
    resultOffset: 0,
    initialize : function() {
        //console.log('Fusion.Tool.Search.initialize');
    },
    getProperties: function() {
        var properties = null;
        if (this.lastResult && this.lastResult.properties) {
            properties = this.lastResult.properties;
        }
        return properties;
    },
    getNumberOfProperties: function() {
        var n = 0;
        if (this.lastResult && this.lastResult.properties) {
            n = this.lastResult.properties.length;
        }
        return n;
    },
    getProperty: function(n) {
        var property = '';
        if (this.lastResult && this.lastResult.properties) {
            property = this.lastResult.properties[n];
        }
        return property;
    },
    getNumberOfResults: function() {
        result = 0;
        if (this.lastResult && this.lastResult.values) {
            result = this.lastResult.values.length;
        }
        return result;
    },
    getFirstResult: function() {
        this.resultOffset = 0;
        return this.getResult(this.resultOffset);
    },
    getNextResult: function() {
        this.resultOffset++;
        return this.getResult(this.resultOffset);
    },
    getResult: function(idx) {
        result = null;
        if (this.lastResult && this.lastResult.values) {
            result = this.lastResult.values[idx];
        }
        return result;
    },
    zoomToResult: function(filter) {
        //console.log('zoomTo ' + filter);
        var filter = '&filter='+filter;
        
        var s = this.getMap().arch + '/' + Fusion.getScriptLanguage() + "/Query." + Fusion.getScriptLanguage() ;
        var params = {};
        params.parameters = 'session='+this.getMap().getSessionID()+'&mapname='+ this.getMap().getMapName()+
                         '&layers='+this.layerName+filter; 
        params.onComplete = OpenLayers.Function.bind(this.selectComplete, this);
        Fusion.ajaxRequest(s, params);
    },
    selectComplete: function(r) {
        var node = new DomNode(r.responseXML);
        var success = node.getNodeText('Selection');
        if (success == 'true') {
            this.getMap().newSelection();
            this.getMap().getSelection(OpenLayers.Function.bind(this.zoomToSelection, this));
        }    
    },
    /**
     * set the extents of the map based on the pixel coordinates
     * passed
     * 
     * @param selection the active selection, or null if there is none
     */
    zoomToSelection : function(selection) {
        var ll = selection.getLowerLeftCoord();
        var ur = selection.getUpperRightCoord();
        //buffer extents (zoom out by factor of two)
        var dX = ur.x - ll.x;
        var dY = ur.y - ll.y;
        ll.x = ll.x - dX;
        ur.x = ur.x + dX;
        ll.y = ll.y - dY;
        ur.y = ur.y + dY;
        this.getMap().setExtents(new OpenLayers.Bounds(ll.x,ll.y,ur.x,ur.y));
    }
});

/**
 * Fusion.Widget.Map
 *
 * $Id: Map.js 2903 2015-04-26 16:10:02Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * Constant: Fusion.Event.MAP_EXTENTS_CHANGED
 */
Fusion.Event.MAP_EXTENTS_CHANGED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_BUSY_CHANGED
 */
Fusion.Event.MAP_BUSY_CHANGED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_GENERIC_EVENT
 */
Fusion.Event.MAP_GENERIC_EVENT = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_RESIZED
 */
Fusion.Event.MAP_RESIZED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_SELECTION_ON
 */
Fusion.Event.MAP_SELECTION_ON = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_SELECTION_OFF
 */
Fusion.Event.MAP_SELECTION_OFF = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_ACTIVE_LAYER_CHANGED
 */
Fusion.Event.MAP_ACTIVE_LAYER_CHANGED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_LOADED
 */
Fusion.Event.MAP_LOADED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_LOADING
 */
Fusion.Event.MAP_LOADING = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_RELOADED
 */
Fusion.Event.MAP_RELOADED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_SESSION_CREATED
 */
Fusion.Event.MAP_SESSION_CREATED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_MAPTIP_REQ_FINISHED
 */
Fusion.Event.MAP_MAPTIP_REQ_FINISHED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.WMS_LAYER_ADDED
 */
Fusion.Event.WMS_LAYER_ADDED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_SCALE_RANGE_LOADED
 */
Fusion.Event.MAP_SCALE_RANGE_LOADED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_MAP_GROUP_LOADED
 */
Fusion.Event.MAP_MAP_GROUP_LOADED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_DIGITIZER_ACTIVATED
 */
Fusion.Event.MAP_DIGITIZER_ACTIVATED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_DIGITIZER_DEACTIVATED
 */
Fusion.Event.MAP_DIGITIZER_DEACTIVATED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_BASE_LAYER_CHANGED
 */
Fusion.Event.MAP_BASE_LAYER_CHANGED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Constant.LAYER_POINT_TYPE
 */
Fusion.Constant.LAYER_POINT_TYPE = 0;
/**
 * Constant: Fusion.Constant.LAYER_POINT_TYPE
 */
Fusion.Constant.LAYER_POINT_TYPE = 1;
/**
 * Constant: Fusion.Constant.LAYER_POINT_TYPE
 */
Fusion.Constant.LAYER_POINT_TYPE = 2;
/**
 * Constant: Fusion.Constant.LAYER_POINT_TYPE
 */
Fusion.Constant.LAYER_POINT_TYPE = 3;
/**
 * Constant: Fusion.Constant.LAYER_POINT_TYPE
 */
Fusion.Constant.LAYER_POINT_TYPE = 4;
/**
 * Constant: Fusion.Constant.LAYER_DWF_TYPE
 */
Fusion.Constant.LAYER_DWF_TYPE = 5;

/****************************************************************************
 * Class: Fusion.Widget.Map
 *
 * generic class for map widgets. Provides common utility classes.
 * This class provides a wrapper around the {<OpenLayers.Map>} object.
 * 
 * Inherits from:
 *  - <Fusion.Lib.EventMgr>
 * **********************************************************************/
Fusion.Widget.Map = OpenLayers.Class(Fusion.Lib.EventMgr, {
    
    ID_DIGITIZING_LAYER: "FusionMapDigitizingLayer",
    DRAW_CONTROL_POINT: "FusionMapDrawPoint",
    DRAW_CONTROL_LINE: "FusionMapDrawLine",
    DRAW_CONTROL_LINESTR: "FusionMapDrawLineString",
    DRAW_CONTROL_RECT: "FusionMapDrawRect",
    DRAW_CONTROL_POLY: "FusionMapDrawPoly",
    DRAW_CONTROL_CIRCLE: "FusionMapDrawCircle",
    
    /** The DOM object that holds the map */
    _oDomObj: null,
    
    /** The id of DOM object */
    _sDomObj: '',
    
    /** The internal name used for the map, usually assigned by the server */
    _sMapname: '',
    
    /** conversion factor to convert from map units to meters */
    _fMetersperunit: -1,
    
    /** scale denominator value */
    _fScale: -1,
    
    /** Dots per inch setting for the server */
    _nDpi: 96,
    
    /** The current bounds of the map, this is updated on every map draw */
    _oCurrentExtents: null,
    
    /** The maximum extent of the map */
    maxExtent: new OpenLayers.Bounds(),
    
    /** counter of worker threads for the ActivityIndicator widget  */
    _nWorkers: 0,
    
    /** The context menu object for right click */
    oContextMenu: null,
    
    /** flag to disable the map context menu, set this in the Map extension  */
    bSupressContextMenu: false,
    
    /** an array to hold all the individual map layer objects */
    aMaps: null,
    
    /** flag to indicate if all the map layers are loaded */
    mapsLoaded: false,
    
    /** the root of the layer structure for the Legend */
    layerRoot: null,
    
    /** flag to indicate if the map is tiled or not, set in the Map extension */
    singleTile: true,
    
    /** flag to indicate if fractional zoom is supported, ie. allow any scale
    *   value rather than a fixed set of scales
    */
    fractionalZoom: true,
    
    /** The DOM object that holds the map */
    maxScale: null, //set this to a large number in AppDef to zoom out beyond maxExtent, e.g. 1 billion

    /**
     * Property: isDigitizing
     *
     * Gets whether a digitizer is currently active
     */
    isDigitizing: false,

    /**
     * Property: message
     * 
     * The {<Fusion.MapMessage>} notification bar
     */
    message: null,

    /** The OL style map for digitizing **/
    digitizingStyleMap: null,

    /**
     * construct a new view Fusion.Widget.Map class.
     */
    initialize: function(widgetTag, mapGroup, widgetSet) {
        this.widgetTag = widgetTag;
        var name = widgetTag.name;
        this.aMaps = [];
        this.buttonSet = new Jx.ButtonSet();
        this.widgetSet = widgetSet;
        this._sDomObj = name;
        this._oDomObj = $(this._sDomObj);
        this.layerRoot = new Fusion.Layers.Group({
            legendLabel: 'mapRoot',
            uniqueId: 'mapRoot',
            groupName: 'mapRoot',
            visible: true
        });

        var jxl = this._oDomObj.retrieve('jxLayout');
        if (jxl) {
            jxl.addEvent('sizeChange', OpenLayers.Function.bind(this.sizeChanged, this));
        }

        if (widgetTag.extension.FractionalZoom) {
          this.fractionalZoom = widgetTag.extension.FractionalZoom[0]=='false'?false:true;
        }

        //if there is a scales array specified, use that set and turn off fraczoom
        var scalesArray = null;
        if (widgetTag.extension.Scales) {
          scalesArray = widgetTag.extension.Scales[0].split(',');
          this.fractionalZoom = false;
        }

        //Set a MaxScale in MapWIdget extension to allow zoom out to that scale
        //otherwise, MaxScale is calculated automatically
        if (widgetTag.extension.MaxScale) {
          this.maxScale = parseInt(widgetTag.extension.MaxExtent[0]);
        }

        //set the maxExtent
        var maxExtent = null;
        if (widgetTag.extension.MaxExtent) {
          maxExtent = OpenLayers.Bounds.fromString(widgetTag.extension.MaxExtent[0]);
        }

        //create the OpenLayers Map object
        OpenLayers.DOTS_PER_INCH = this._nDpi;
        if (!this.oMapOL) {
            var options = {
                controls: [ new OpenLayers.Control.PinchZoom() ],
                fallThrough: true,
                scales: scalesArray,
                fractionalZoom: this.fractionalZoom
            };
            if (widgetTag.extension.ConstrainMapExtent) {
              this.bRestrictExtent = widgetTag.extension.ConstrainMapExtent[0]=='true'?true:false;
            }
            if (maxExtent) {
              options.maxExtent = maxExtent;
              this.maxExtent = maxExtent;
            }
            this.oMapOL = new OpenLayers.Map(this._sDomObj, options );
        }

        //Fusion specific modifications to the OL map window style
        this.oMapOL.viewPortDiv.style.position = 'absolute';  //not the top level container so set it to absolute
        this.oMapOL.viewPortDiv.style.zIndex = 0;   //must explicitly set the z-index for FF3

        this.handlers = [];

        //add in the handler for mouse wheel actions
        var useMouseWheel = true;
        if (widgetTag.extension.DisableMouseWheel &&
            widgetTag.extension.DisableMouseWheel[0] == 'true') {
            useMouseWheel = false;
        }
        if (useMouseWheel) {
          this.wheelHandler = new OpenLayers.Handler.MouseWheel(this,
                                            {"up"  : this.wheelUp,
                                             "down": this.wheelDown} );
          this.wheelHandler.map = this.oMapOL;
          this.wheelHandler.activate();
          this.handlers.push(this.wheelHandler);
        }

        $(name).widget = this;

        this.registerEventID(Fusion.Event.MAP_EXTENTS_CHANGED);
        this.registerEventID(Fusion.Event.MAP_BUSY_CHANGED);
        this.registerEventID(Fusion.Event.MAP_GENERIC_EVENT);
        this.registerEventID(Fusion.Event.MAP_RESIZED);
        this.registerEventID(Fusion.Event.MAP_ACTIVE_LAYER_CHANGED);
        this.registerEventID(Fusion.Event.MAP_LOADED);
        this.registerEventID(Fusion.Event.MAP_LOADING);
        this.registerEventID(Fusion.Event.MAP_RELOADED);
        this.registerEventID(Fusion.Event.MAP_SELECTION_ON);
        this.registerEventID(Fusion.Event.MAP_SELECTION_OFF);
        this.registerEventID(Fusion.Event.MAP_MAPTIP_REQ_FINISHED);
        this.registerEventID(Fusion.Event.WMS_LAYER_ADDED);
        this.registerEventID(Fusion.Event.MAP_SCALE_RANGE_LOADED);
        this.registerEventID(Fusion.Event.MAP_MAP_GROUP_LOADED);
        this.registerEventID(Fusion.Event.MAP_DIGITIZER_ACTIVATED);
        this.registerEventID(Fusion.Event.MAP_DIGITIZER_DEACTIVATED);
        this.registerEventID(Fusion.Event.MAP_BASE_LAYER_CHANGED);

        this.registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.mapLoaded,this));
        this.registerForEvent(Fusion.Event.MAP_RELOADED, OpenLayers.Function.bind(this.mapLoaded,this));

        //register for OL map extent change events
        this.oMapOL.events.register('moveend', this, this.mapExtentsChanged);

        this._oDomObj.onselectstart = function() {return false;};
        this._oDomObj.oncontextmenu = function() {return false;};
        OpenLayers.Event.observe(this._oDomObj, 'contextmenu',
                            OpenLayers.Function.bind(this.onContextMenu, this));

        this.aSelectionCallbacks = [];
        this.bFetchingSelection = false;

        //Create style map
        this.digitizingStyleMap = new OpenLayers.StyleMap(new OpenLayers.Style({
            pointRadius: 4,
            graphicName: "square",
            fillColor: "white",
            fillOpacity: 0.4,
            strokeWidth: 2,
            strokeOpacity: 1,
            strokeColor: "#666666",
            strokeColor: "#666666"
        }));
        this.keyHandler = OpenLayers.Function.bind(this.onKeyPress, this);

        //create the 'Map' layer widgets defined in the MapGroup
        this.loadMapGroup(mapGroup);
    },
    
    /**
     * Function: setExternalBaseLayer
     *
     * Sets the given external layer as the new base layer
     *
     * Parameter: {Fusion.Layers} baseLayer - a Fusion.Layers instance to set as the new base map
     *
     * Return: none
     */
    setExternalBaseLayer: function(baseLayer) {
        this.oMapOL.setBaseLayer(baseLayer.oLayerOL, false);
        this.triggerEvent(Fusion.Event.MAP_BASE_LAYER_CHANGED, baseLayer);
    },

    /**
     * Function: mapLoaded
     *
     * Callback for when all maps have been loaded
     *
     * Parameter: none.
     *
     * Return: none
     */
    mapLoaded: function() {
        this.setViewOptions(this.getUnits());
        this.loadScaleRanges(OpenLayers.Function.bind(this.scaleRangesLoaded, this));
    },

    /**
     * Function: setMenu
     *
     * Attaches a context menu as defined in the AppDef to the map
     *
     * Parameter: none.
     *
     * Return: none
     */
    setMenu: function() {
        if (this.widgetTag.extension.MenuContainer) {
            var contextMenu = new Jx.Menu.Context();
            var container = this.widgetSet.getContainerByName(this.widgetTag.extension.MenuContainer[0]);
            if (container) {
                container.createWidgets(this.widgetSet, contextMenu);
                this.setContextMenu(contextMenu);
            }
        }
    },

    /**
     * Function: loadMapGroup
     *
     * Controls the creation of Layers based on the MapGroup object passed in
     *
     * Parameter: {Object} mapGroup - a representation of the MapGroup object
     * {Boolean} loadOverlays - if true, this will leave the first layer (usually
     * a commercial baselayer) on the map and just replace all the overlays
     *
     * Return: none
     */
    loadMapGroup: function(mapGroup, loadOverlays) {
        //clear any existing selection
        this.mapsLoaded = false;
        this.clearSelection();
        //this._addWorker();

        this.mapGroup = mapGroup;
        var startIndex = loadOverlays?1:0;
        for (var i = this.aMaps.length-1; i >= startIndex; i--) {
          if (this.aMaps[i].oLayerOL) {
            this.aMaps[i].oLayerOL.destroy();
            this.aMaps.splice(i,1);
          }
        }
 
        this._fMetersperunit = -1;
        this.oMapOL.center = null;
        this.maxExtent = new OpenLayers.Bounds();
        var storedBase;
        if (loadOverlays) {
          storedBase = this.layerRoot.layers[0];
        }
        this.layerRoot.clear();
        if (loadOverlays) {
          this.layerRoot.addLayer(storedBase);
        }

        for (var i=0; i<mapGroup.maps.length; ++i) {
          var mapTag = mapGroup.maps[i];

          if (Fusion.Layers[mapTag.type]) {
              this.aMaps.push(new Fusion.Layers[mapTag.type](this, mapTag, true));
              var oGroup = this.aMaps[this.aMaps.length-1].layerRoot;
              oGroup.isFusionLayer = true;
              this.layerRoot.addGroup(oGroup);
          } else {
              this.aMaps.push(new Fusion.Layers.Generic(this, mapTag, true));
              this.layerRoot.addLayer(this.aMaps[this.aMaps.length-1].layerRoot);
          }
          this.aMaps[this.aMaps.length-1].registerForEvent(Fusion.Event.LAYER_LOADED, OpenLayers.Function.bind(this.layerLoaded,this,mapTag));
        }
        this.triggerEvent(Fusion.Event.MAP_MAP_GROUP_LOADED);
    },
    
    _fixBaseLayerGroupVisibility: function() {
        //Fix Defect: the Base Layer Group should be invisiable when the "initially visiable in map" is set to false
        var i = 0;
        var j = 0;
        for (i = 0;i < this.layerRoot.groups.length; i++) {
            if (this.layerRoot.groups[i].uniqueId == "layerRoot") {
                for (var k = 0; k < this.layerRoot.groups[i].groups.length; k++) {
                    if (!this.layerRoot.groups[i].groups[k].initiallyVisible) {
                        for (j = 0; j < this.aMaps.length; j++) {
                            if (this.aMaps[j].arch != 'MapGuide') {
                                continue;
                            }
                            if (this.aMaps[j].oLayerOL.params.basemaplayergroupname === this.layerRoot.groups[i].groups[k].name) {
                                this.aMaps[j].oLayerOL.setVisibility(false);
                            } 
                        }
                    }   
                }
            } else {
                if (!this.layerRoot.groups[i].initiallyVisible){
                    for (j = 0; j < this.aMaps.length; j++) {
                        if (this.aMaps[j].arch != 'MapGuide') {
                            continue;
                        }
                        if (this.aMaps[j].oLayerOL.params.basemaplayergroupname === this.layerRoot.groups[i].name) {
                            this.aMaps[j].oLayerOL.setVisibility(false);
                        } 
                    }
                }   
            }
        }
    },

    /**
     * Function: layerLoaded
     *
     * Called after each layer is loaded in the OL viewport.  Nothing happens 
     * here until the last layer is laoded, then it sets the OL baselayer and
     * zooms to the initial extent.
     *
     * Parameter: none
     *
     * Return: none
     */
    layerLoaded: function(mapTag) {
        for (var i=0; i<this.aMaps.length; ++i) {
            if (!this.aMaps[i].isMapLoaded()) {
                return;
            }
        }
        this.mapsLoaded = true;
        if (this.aMaps.length == 1) {
            this.oMapOL.setBaseLayer(this.aMaps[0].oLayerOL);
            this._fixBaseLayerGroupVisibility();
        } else {
            //Due to initialization being done asynchronously, re-order the maps to ensure the OpenLayers draw order matches the order of this.aMaps
            //Fortunately this.aMaps[i] already carries the related OpenLayers layer object.

            var mgLayerCount = 0;

            //First remove the ones in question
            for (var i = 0; i < this.aMaps.length; i++) {
                if (this.aMaps[i].arch == 'MapGuide') {
                    mgLayerCount++;
                }
                
                //if (this.aMaps[i].oLayerOL != this.oMapOL.layers[i])
                //    console.log("Found OL layer not in correct position");
                    
                this.oMapOL.removeLayer(this.aMaps[i].oLayerOL);
            }

            //Now re-add them in the correct order
            for (var i = 0; i < this.aMaps.length; i++) {
                this.oMapOL.addLayer(this.aMaps[i].oLayerOL);
            }

            //HACK: Force alwaysInRange = true if more than one Map Definition was found in the MapGroup
            //
            //I suspect the underlying problem is the variations in coordinate system name and/or WKT of the
            //Map Definitions in question that screw up the map resolution calculations that determine
            //whether the OpenLayers.Layer object actually renders or not. Setting alwaysInRange = true will
            //skip this (incorrect) calculation
            if (mgLayerCount > 1) {
                for (var i = 0; i < this.aMaps.length; i++) {
                    if (this.aMaps[i].arch == 'MapGuide') {
                        this.aMaps[i].oLayerOL.isBaseLayer = false; //Required for multiple MG tiled maps a in a MapGroup to be in sync
                        this.aMaps[i].oLayerOL.alwaysInRange = true;
                        this.aMaps[i].oLayerOL.setVisibility(true);
                    }
                }
            }
            
            //Need to do this after setting alwaysInRange = true as we require the original visibility settings to be respected
            this._fixBaseLayerGroupVisibility();
        }

        // Take advantage of new client-zoom capabilities introduced in OpenLayers 2.12
        //
        // What we do here is for every grid-based commerical layer, we tack on extra resolutions
        // of the MapGuide OL Layer that is below the minimum supported resolution of the commercial
        // layers. The existing resolutions array is shifted to the layer's serverResolutions array.
        //
        // When we go below the minimum supported resolution of these commercial layers, 
        // client-zoom kicks in and stretches the commerical layers appropriately.
        //
        // A small side-effect is that if fractionalZoom = true, and the layer setup is such that
        // this won't be set to false during initialization (eg. fractionalZoom = true + 
        // A MapGuide dynamic map + OSM layers), then the OSM layers will almost always be slightly 
        // stretched, as any zoom scale is allowed and OL will be stretching the OSM layers to match.
        //
        // This technique does not work with Google layers (as they aren't grid-based). The mere presence
        // of Google layers (or any non-Grid layers) will disable client-zoom if a non-Grid layer is the active
        // one, and your map will be snapping to whatever discrete scale list that is imposed by the commercial layer
        //
        // With these changes the behaviour is like so (OSM is interchangeable with any other grid-based layer, MapGuide
        // is assumed to be fully dynamic and have no tiled layers):
        //
        // - MapGuide + OSM (fractionalZoom = true): Fully dynamic map and OSM tiles are stretched accordingly. OSM 
        //                                           tiles will most likely never be un-stretched due to the fully dynamic
        //                                           nature of the map.
        // - MapGuide + OSM (fractionalZoom = false): Scales "snap" to OSM scales and are stretched accordingly 
        //                                            when going below the minimum supported OSM scale
        // - MapGuide + OSM + Explicit Scale List: Scales "snap" to OSM scales (any MG scales within the OSM scale 
        //                                         list are disregarded). OSM tiles are stretched accordingly when 
        //                                         going below the minimum supported OSM scale
        //                                         
        // MapGuide Map Definitions with tiled layers cannot take advantage of client-zoom (at least to my knowledge and for
        // this initial implementation). 
        //
        // So the same rules apply for MG tiled map integration: It must use the same scale list as Google + Bing + OSM.
        var newResolutions = null;
        var mgResolutions = null;
        var bCanUseClientZoom = true;
        for (var i = 0; i < this.aMaps.length; i++) {
            if (this.aMaps[i].arch == 'MapGuide') {
                mgResolutions = this.aMaps[i].oLayerOL.resolutions;
                if (this.aMaps[i].bSingleTile) {
                    bCanUseClientZoom = true;
                } else {
                    //TODO: How smart can we be to make this be true?
                    bCanUseClientZoom = false;
                }
                break;
            }
        }

        //console.log("Can use client-zoom: " + bCanUseClientZoom);
        if (bCanUseClientZoom) {
            for (var i = 0; i < this.aMaps.length; i++) {
                if (this.aMaps[i].arch == 'Generic') {
                    var oLayer = this.aMaps[i].oLayerOL;
                    oLayer.serverResolutions = oLayer.resolutions;
                    // We need to set new resolutions to null because the resolutions of each Map may be not same e.g. Google and Bing map.
                    newResolutions = null;
                    if (newResolutions == null) {
                        newResolutions = [];
                        for (var j = 0; j < oLayer.serverResolutions.length; j++) {
                            newResolutions.push(oLayer.serverResolutions[j]);
                        }
                        for (var j = 0; j < mgResolutions.length; j++) {
                            if (mgResolutions[j] < oLayer.minResolution) {
                                newResolutions.push(mgResolutions[j]);
                            }
                        }
                    }
                    oLayer.resolutions = newResolutions;
                    var newScales = [];
                    for (var j = 0; j < newResolutions.length; j++) {
                        newScales.push(OpenLayers.Util.getScaleFromResolution(newResolutions[j], oLayer.units));
                    }
                    oLayer.scales = newScales;
                    oLayer.minResolution = newResolutions[newResolutions.length - 1];
                    oLayer.maxScale = newScales[newScales.length - 1];
                    oLayer.numZoomLevels = newScales.length;
                }
                //TODO: Do the same for tiled MapGuide oLayerOL objects, allowing for such layers to be scaled client-side as well
            }
        }

        var initialExtent = this.setInitialExtents();
        this.setExtents(initialExtent);
        
        //Start observing keypresses
        OpenLayers.Event.observe(document,"keypress",this.keyHandler);

        this.triggerEvent(Fusion.Event.MAP_LOADED, mapTag);
    },

    /**
     * Function: wheelChange
     *
     * handles mouse wheel events by accummulating the events and setting a timer
     * to do the actual zoom in/out
     * 
     * Parameters:
     * evt - {Event} the mouse wheel event object
     * deltaZ - {Integer} the number of ticks wheeled
     */
    wheelChange: function(evt, deltaZ) {
        if (this.wheelTimer) {
            clearTimeout(this.wheelTimer);
        } else {
            this.cumulativeDelta = 0;
        }
        this.cumulativeDelta += (deltaZ < 0)?-1:1;
        
        this.wheelTimer = setTimeout(OpenLayers.Function.bind(function(){this.doWheelChange(evt, deltaZ);}, this), 200);
    },

    /**
     * Function: doWheelChange
     *
     * Carries out the actual map zoom based on the wheel movement
     *
     * Parameters:
     * evt - {Event} the mouse wheel event object
     * deltaZ - {Integer} the number of ticks wheeled
     *
     * Return: none
     */
    doWheelChange: function(evt, deltaZ) {
        this.wheelTimer = null;
        if (this.cumulativeDelta == 0) {
            return;
        }
        var size    = this.oMapOL.getSize();
        var deltaX  = size.w/2 - evt.xy.x;
        var deltaY  = evt.xy.y - size.h/2;

        var deltaRes = this.cumulativeDelta > 0 ? 1/(this.cumulativeDelta+1) : Math.abs(this.cumulativeDelta)+1;
        var newRes  = this.oMapOL.baseLayer.getResolution() * deltaRes;
        var zoomPoint = this.oMapOL.getLonLatFromPixel(evt.xy);
        var newCenter = new OpenLayers.LonLat(
                            zoomPoint.lon + deltaX * newRes,
                            zoomPoint.lat + deltaY * newRes );
        var newBounds = new OpenLayers.Bounds(
                            newCenter.lon - size.w*newRes/2,
                            newCenter.lat - size.h*newRes/2,
                            newCenter.lon + size.w*newRes/2,
                            newCenter.lat + size.h*newRes/2);
        this.setExtents(newBounds);
    },

    /**
     * Method: wheelUp
     * User spun scroll wheel up
     *
     * Parameters:
     * evt - {Event}
     */
    wheelUp: function(evt) {
        this.wheelChange(evt, 1);
    },

    /**
     * Method: wheelDown
     * User spun scroll wheel down
     *
     * Parameters:
     * evt - {Event}
     */
    wheelDown: function(evt) {
        this.wheelChange(evt, -1);
    },

    /**
     * Function: getDomObj
     *
     * returns the dom element for this widget
     *
     * Parameters: none
     *
     * Return: none
     */
    getDomObj: function() {
        return this._oDomObj;
    },


    /**
     * Function: getDomId
     *
     * returns the ID of dom element for this widget
     *
     * Parameters: none
     *
     * Return: {String} the id of the DOM element
     */
    getDomId: function() {
        return this._sDomObj;
    },

    /**
     * Function: setMapOptions
     *
     * sets options on the OpenLayers map object, a wrapper for the OL function
     *
     * Parameters: {Object} options - has of option key/value pairs
     *
     * Return: none
     */
    setMapOptions: function(options) {
        this.oMapOL.setOptions(options);
    },

    /**
     * Function: addMap
     *
     * adds a map/Layer to the map and registers some event handlers on the map
     *
     * Parameters: {Object} map - the map object to tbe added (really a layer object)
     *
     * Return: none
     */
    addMap: function(map) {

        if (map.mapTag.layerOptions.maxExtent) {
          this.maxExtent.extend(map.mapTag.layerOptions.maxExtent);
          this.oMapOL.setOptions({maxExtent: this.maxExtent});
        }

        //if bRestrictExtent is null, use the default OL behaviour with somewhat restricted map navigation
        //if bRestrictExtent is set to true, map navigation is limited to the map extent
        //if bRestrictExtent is set to false, map navigation is not restricted at all
        if (this.bRestrictExtent != null) {
          if (this.bRestrictExtent) {
            this.oMapOL.restrictedExtent = map.mapTag.layerOptions.maxExtent;
          } else {
            this.oMapOL.restrictedExtent = false;
          }
        }
        this.oMapOL.addLayer(map.oLayerOL);
        if (map.oLayerOL.isBaseLayer) {
          this.projection = map.oLayerOL.projection;
          this.units = map.oLayerOL.units;
          this.oMapOL.setOptions({
                  units: this.units,
                  projection: this.projection
          });
        }
        
        map.registerForEvent(Fusion.Event.MAP_SELECTION_OFF,
                OpenLayers.Function.bind(this.selectionHandler, this));
        map.registerForEvent(Fusion.Event.MAP_SELECTION_ON,
                OpenLayers.Function.bind(this.selectionHandler, this));
    },

    /**
     * Function: getAllMaps
     *
     * returns the array of map objects
     *
     * Parameters: none
     *
     * Return: {Array} the array of map Objects
     */
    getAllMaps: function() {
        return this.aMaps;
    },

    /**
     * Function: reloadMap
     *
     * Called to reload all the map objects.
     * this uses setTimeout so this method can be called from an IFRAME
     *
     * Parameters: none
     *
     * Return: none
     */
    reloadMap: function() {
      for (var i=0; i<this.aMaps.length; ++i) {
        var map = this.aMaps[i];
        window.setTimeout(OpenLayers.Function.bind(map.reloadMap, map),1);
      }
    },

    /**
     * Function: loadScaleRanges
     *
     * Loads the ScaleRange objects separate from the LoadMap sequence since
     * this also generates sprites and is a relatively expensive operation.
     *
     * Parameters: {Function} userFunc - a user defined function to be called as a
     *                                  callback
     *
     * Return: none
     */
    loadScaleRanges: function() {
      for (var i=0; i<this.aMaps.length; ++i) {
        var map = this.aMaps[i];
        map.loadScaleRanges();
      }
    },

    /**
     * Function: query
     *
     * dispatch query requests to the maps
     *
     * Parameters: {object} options - query options to be passed to each map
     */
    query: function(options) {
        this.lastQueryOptions = options;
        for (var i=0; i<this.aMaps.length; i++ ) {
            if (this.aMaps[i].query(options)) {
            }
        }
    },

    /**
     * Function: selectionHandler
     *
     * handle selection events from maps and republish for
     * widgets as appropriate
     *
     * Parameters: none
     *
     * Return: none
     */
    selectionHandler: function() {
        if (this.hasSelection()) {
            this.triggerEvent(Fusion.Event.MAP_SELECTION_ON);
        } else {
            this.triggerEvent(Fusion.Event.MAP_SELECTION_OFF);
        }
    },

    /**
     * Function: hasSelection
     *
     * cehck to see if any of the maps have an active selection
     *
     * Returns: {Boolean} - true if any map has a selection
     */
     hasSelection: function() {
         for (var i=0; i<this.aMaps.length; i++ ) {
             if (this.aMaps[i].hasSelection()) {
                 return true;
             }
         }
         return false;
     },

     /**
      * Function: clearSelection
      *
      * clear the selection on all maps
      */
     clearSelection: function() {
         this.oSelection = null;
         this.lastQueryOptions = null;
         for (var i=0; i<this.aMaps.length; i++ ) {
             this.aMaps[i].clearSelection();
         }
     },

     /**
      * Function: getSelection
      *
      * initiates a call to get the current selection asynchronously in case we
      * need to retrieve the details from the server
      */
     getSelection: function(callback, layers, startcount) {
       //console.log('map.js : getselection ' + layers);

       var layers = (arguments[1]) ? arguments[1] : '';
       var startcount = (arguments[2]) ? arguments[2] : '';
         this.aSelectionCallbacks.push(callback);
         if (this.bFetchingSelection) {
             return;
         }
         this.bFetchingSelection = true;
         this.oSelection = {};
         this.nSelectionMaps = 0;
         for (var i=0; i<this.aMaps.length; i++ ) {
           if (!this.aMaps[i].supports.query) 
         // if(this.aMaps[i].layerType == "Google" || this.aMaps[i].layerType == "Yahoo" ||this.aMaps[i].layerType == "VirtualEarth" )
               continue;
             this.nSelectionMaps++;
             this.aMaps[i].getSelection(
                    OpenLayers.Function.bind(this.accumulateSelection, this, this.aMaps[i]),
                    layers, startcount);
         }
     },

     /**
      * Function: setSelection
      *
      * sets a Selection XML back to the server for each of the maps
      *
      * Parameters: 
      * {String} selText - the selection string 
      * {Boolean} zoomTo - if set, will zoom to the selection on completion
      */
      setSelection: function(selText, zoomTo) {
         for (var i=0; i<this.aMaps.length; i++ ) {
             this.aMaps[i].setSelection(selText, zoomTo);
         }
      },

     /**
      * Function: accumulateSelection
      *
      * accumulate the selection results from each map and when all have
      * reported in, pass the results to the callback function
      *
      * Parameters:
      * {Object} map - the map object that has the selection
      * {Object} oSelection - the Fusion selection object
      */
     accumulateSelection: function(map, oSelection) {
         this.oSelection[map._sMapname] = oSelection;

         if (!--this.nSelectionMaps) {
             this.bFetchingSelection = false;
             for (var i=0; i<this.aSelectionCallbacks.length; i++) {
                 this.aSelectionCallbacks[i](this.oSelection);
             }
             this.aSelectionCallbacks = [];
         }
     },

     /**
      * Function: setActiveLayer
      *
      * sets the active layer for selection/manipulation
      *
      * Parameters:
      * {Object} oLayer - the layer to be set as active
      */
      setActiveLayer: function( oLayer ) {
          this.oActiveLayer = oLayer;
          if (oLayer) {
              this.oActiveMap = oLayer.map;
          }
          this.triggerEvent(Fusion.Event.MAP_ACTIVE_LAYER_CHANGED, oLayer);
      },

     /**
      * Function: getActiveLayer
      *
      * returns the active layer for selection/manipulation
      *
      * Returns:
      * {Object} the active layer
      */
    getActiveLayer: function() {
        return this.oActiveLayer;
    },

    /**
     * Function: _addWorker
     * 
     * indicate that a new asynchronous process has started and make sure the
     * visual indicator is visible for the user.  This is intended to be used
     * internally by gMap but could be used by external tools if appropriate.
     */
    _addWorker: function() {
        this._nWorkers += 1;
        this.triggerEvent(Fusion.Event.MAP_BUSY_CHANGED, this);
        this._oDomObj.style.cursor = 'wait';
    },

    /**
     * Function: _removeWorker
     * 
     * indicate that an asynchronous process has completed and hide the
     * visual indicator if no remaining processes are active.  This is
     * intended to be used internally by gMap but could be used by
     * external tools if appropriate.  Only call this function if
     * addWorker was previously called
     */
    _removeWorker: function() {
        if (this._nWorkers > 0) {
            this._nWorkers -= 1;
        }
        this.setCursor(this.cursor);
        this.triggerEvent(Fusion.Event.MAP_BUSY_CHANGED, this);
    },

    /**
     * Function: mapExtentsChanged
     *
     * OpenLayers event handler for whenever the map changes either zoom level
     * of the center has changed.  This in turn triggers the Fusion event. 
     *
     * Parameters: none
     *
     * Return: none
     */
    mapExtentsChanged: function() {
        this._oCurrentExtents = this.oMapOL.getExtent();
        this.triggerEvent(Fusion.Event.MAP_EXTENTS_CHANGED);
    },

    /**
     * Function: isBusy
     *
     * check to see if there are any outstanding requests to the server
     *
     * Parameters: none
     *
     * Return: {Boolean} true if there are active requests
     */
    isBusy: function() {
        return this._nWorkers > 0;
    },

    /**
     * Function: sizeChanged
     *
     * callback for when the browser window size changes, simply calls resize 
     * on the map viewport layout object
     *
     * Parameters: none
     *
     * Return: none
     */
    sizeChanged: function() {
        this.resize();
    },

    /**
     * Function: resize
     *
     * handles the resizing of the maps and triggers a Fusion event
     *
     * Parameters: none
     *
     * Return: none
     */
    resize: function() {
      if (!this.mapsLoaded) {
        return;
      }
      //console.log('Fusion.Widget.Map.resize');
        this.oMapOL.updateSize();
        var d = $(this.getDomObj()).getContentBoxSize();
        if (this._oCurrentExtents) {
          this.setExtents(this._oCurrentExtents);
        }
        this.triggerEvent(Fusion.Event.MAP_RESIZED, this);
    },

    /**
     * Function: redraw
     *
     * redraws the map using current extents and zoom level.  A dummy parameter
     * is added to the map request to ensure the map request is not cached.
     *
     * Parameters: none
     *
     * Return: none
     */
    redraw: function() {
      for (var i=0; i<this.aMaps.length; i++ ) {
        if(this.aMaps[i].oLayerOL.params){
            this.aMaps[i].oLayerOL.params.ts = (new Date()).getTime();
            //mergeNewParams calls redraw on the layer, which will get called below anyway
            //this.aMaps[i].oLayerOL.mergeNewParams({ts : (new Date()).getTime()});
        }
      }
      this.oMapOL.setCenter(this.oMapOL.getCenter(), this.oMapOL.getZoom(), false, true);
    },

    /**
     * Function: setBackgroundColor
     *
     * sets the background color for the map
     *
     * Parameters: {String} color - the CSS color value to set as background
     *
     * Return: none
     */
    setBackgroundColor: function(color) {
        this._oDomObj.style.backgroundColor = color;
    },

    /**
     * Function: setExtents
     *
     * handle selection events from maps and republish for
     * widgets as appropriate
     *
     * Parameters: {Object} oExtents - an OpenLayers.Bounds object or an array 
     *                            of 4 values that will be converted to Bounds
     *
     * Return: none
     */
    setExtents: function(oExtents) {
        if (!oExtents) {
            Fusion.reportError(new Fusion.Error(Fusion.Error.WARNING,
                                OpenLayers.i18n('nullExtents')));
        }
        if (oExtents instanceof Array && oExtents.length == 4) {
            oExtents = new OpenLayers.Bounds(oExtents[0], oExtents[1], oExtents[2], oExtents[3]);
        }

        //update the timestamp param to prevent caching
        for (var i=0; i<this.aMaps.length; i++ ) {
          if (this.aMaps[i].oLayerOL.params && this.aMaps[i].noCache) {
            this.aMaps[i].oLayerOL.params.ts = (new Date()).getTime();
          }
      }
        
        // when the parameter "oExtents" is a point which should be a rectangle, it will zoom to 1:1
        // this is often because the selected feature is a point feature, like tree
        // in this situation we can pan the point to the center, no need to zoom to 1:1
        if(oExtents.bottom == oExtents.top && oExtents.left == oExtents.right)
        {
            this.oMapOL.panTo(new OpenLayers.LonLat(oExtents.left, oExtents.top));
        }
        else
        {
            this.oMapOL.zoomToExtent(oExtents,true);
        }
        
        this._oCurrentExtents = this.oMapOL.getExtent();
    },

    /**
     * Function: setInitialExtents
     * 
     * determine the initialExtents of the map from (in order of precedence):
     * 1. a URL query parameter called 'extent'
     * 2. an <InitialView> specified in the MapGroup in AppDef
     * 3. the maxExtent as specified by the LoadMap call (default)
     *
     * Returns: {Object} an OpenLayers.Bounds object which is the initial extent
     */
    setInitialExtents: function() {
      var initialExtents;
      var bbox = Fusion.getQueryParam("extent");   //set as min x,y, max x,y
      if (bbox) {
        initialExtents = new OpenLayers.Bounds.fromArray(bbox.split(","));
      } else if (this.mapGroup.initialView) {
          var iv = this.mapGroup.getInitialView();
          if (iv.x != undefined) {
              initialExtents = this.getExtentFromPoint(iv.x, iv.y, iv.scale);
          } else if (iv.minX != undefined) {
              initialExtents = new OpenLayers.Bounds(iv.minX, iv.minY, iv.maxX, iv.maxY);
          }
          var mapProj = this.oMapOL.baseLayer.projection;
          if (iv.projection && !iv.projection.equals(mapProj)) {
            initialExtents.transform(iv.projection, mapProj);
          }
          if (!initialExtents.intersectsBounds(this.maxExtent)) {
            initialExtents = this.maxExtent;
          }
      } else {
        initialExtents = this.getMapGroupExtent();
        if (!initialExtents) {
            initialExtents = this.getMapGroupExtent(true);
        }
      }
      if (!this.initialExtents) this.initialExtents = initialExtents;
      return initialExtents;
    },

    /**
     * Function: getMapGroupExtent
     *
     * gets the intitial extents from all maps with or without including the
     * extent of any Generic/commercial layers
     * 
     * Return: {Object} an OpenLayers.Bounds object which is the initial extent
     */
    getMapGroupExtent: function(includeGenericLayers) {
      var initialExtent;
      for (var i=0; i<this.aMaps.length; ++i) {
        if (!includeGenericLayers && this.aMaps[i].arch == 'Generic') {
            continue;
        }
        initialExtent = this.aMaps[i].getMaxExtent();
      }
      return initialExtent;
    },

    /**
     * Function: fullExtents
     * 
     * sets the extent of the map to the max as returned by loadMap
     */
    fullExtents: function() {
      var extents = this.maxExtent;
      this.setExtents(extents);
    },

    /**
     * Function: isMapLoaded
     * 
     * returns if the map has finished loading (based on setting the 
     * _oCurrentExtent value)
     *
     * Returns {Boolean} true if the map is loaded and extent has been set
     */
    isMapLoaded: function() {
        return (this._oCurrentExtents) ? true : false;
    },

    /**
     * Function: zoom
     * 
     * sets the map zoom and extent.
     *
     * Parameters:
     *   fX {Float} - new x coordinate value in map units
     *   fY {Float} - new y coordinate value in map units
     *   nFactor {Float} - zoom factor; positive values zoom in, negative out
     *                  - if set to 0 or 1, the map is just recentered
     *                  - if the map has fractional zoom enabled, the map resolution
     *                  will be modified by this factor
     *                  - with fixed scales, zoom up or down log2(nFactor) level
     *
     * Returns: none
     */
    zoom: function(fX, fY, nFactor) {
        //do this differntly with OL code??
        if (nFactor == 1 || nFactor == 0) {
            /*recenter*/
            this.oMapOL.panTo(new OpenLayers.LonLat(fX, fY));
        } else {
            var extent = this.oMapOL.getExtent();
            if (this.fractionalZoom) {
                var fDeltaX = extent.right - extent.left;
                var fDeltaY = extent.top - extent.bottom;
                var fMinX,fMaxX,fMinY,fMaxY;
                if (nFactor > 0) {
                    /*zoomin*/
                    fMinX = fX - (fDeltaX/2 / nFactor);
                    fMaxX = fX + (fDeltaX/2 / nFactor);
                    fMinY = fY - (fDeltaY/2 / nFactor);
                    fMaxY = fY + (fDeltaY/2 / nFactor);
                } else if (nFactor < 0) {
                    /*zoomout*/
                    fMinX = fX - ((fDeltaX/2) * Math.abs(nFactor));
                    fMaxX = fX + ((fDeltaX/2) * Math.abs(nFactor));
                    fMinY = fY - ((fDeltaY/2) * Math.abs(nFactor));
                    fMaxY = fY + ((fDeltaY/2) * Math.abs(nFactor));
                }
                this.setExtents(new OpenLayers.Bounds(fMinX, fMinY, fMaxX, fMaxY));
            } else {
                var currentZoomLevel = this.oMapOL.getZoom();
                this.oMapOL.setCenter(new OpenLayers.LonLat(fX, fY));
                this.oMapOL.zoomTo(currentZoomLevel + Math.floor(Math.log(nFactor) / Math.log(2)));
            }
        }
    },

    /**
     * Function: zoomToScale
     * 
     * Zooms to the specified scale
     *
     * Parameters:
     *   fScale - {Float} The scale to zoom to
     *
     * Returns: none
     */
    zoomToScale: function(fScale) {
        var center = this.getCurrentCenter();
        var extent = this.getExtentFromPoint(center.x, center.y, fScale);
        this.setExtents(extent);
    },

    /**
     * Performs a rectangular query
     *
     * Parameters:
     *   fMinX {Float} - new minimum x coordinate value in map units
     *   fMinY {Float} - new minimum y coordinate value in map units
     *   fMaxX {Float} - new maximum x coordinate value in map units
     *   fMaxY {Float} - new maximum y coordinate value in map units
     *
     * Returns: none
     */
    queryRect: function(fMinX, fMinY, fMaxX, fMaxY) { },

    /**
     * Performs a point query
     *
     * Parameters:
     *   fX {Float} - new x coordinate value in map units
     *   fY {Float} - new y coordinate value in map units
     *
     * Returns: none
     */
    queryPoint: function(fX, fY) { },

    /**
     * Method: pixToGeo
     *
     * convert pixel coordinates into geographic coordinates.
     *
     * Parameters:
     * pX - {Integer} the x coordinate in pixel units
     * pY - {Integer} the y coordinate in pixel units
     *
     * Return:
     * an object with geographic coordinates in x and y properties of the object.
     */
    pixToGeo: function( pX, pY ) {
        var lonLat = this.oMapOL.getLonLatFromPixel( new OpenLayers.Pixel(pX,pY) );
        if (lonLat != null) {
          return {x:lonLat.lon, y:lonLat.lat};
        }
        return null;
    },

    /**
     * Method: geoToPix
     *
     * convert geographic coordinates into pixel coordinates.
     *
     * Parameters:
     * gX - {Integer} the x coordinate in geographic units
     * gY - {Integer} the y coordinate in geographic units
     *
     * Return:
     * an object with pixel coordinates in x and y properties of the object.
     */
    geoToPix: function( gX, gY ) {
        if (!(this._oCurrentExtents)) {
            return null;
        }
        var px = this.oMapOL.getPixelFromLonLat(new OpenLayers.LonLat(gX,gY));  //use getViewPortPxFromLonLat instead?
        return {x:Math.floor(px.x), y:Math.floor(px.y)};
    },

    /**
     * Method: pixToGeoMeasure
     *
     * convert pixel into geographic : used to measure.
     *
     * Parameters:
     * nPixels - {Integer} measures in pixel
     *
     * @return geographic measure
     */
    pixToGeoMeasure: function(nPixels) {
        var resolution = this.oMapOL.getResolution();
        return (nPixels*resolution);
    },

    /**
     * Method: setProjection
     *
     * initializes the OpenLayers projection object on the Map object
     *
     * Parameters:
     * projCode - {String} projection code
     * units - the units
     */
    setProjection: function(projCode, units) {
        this.projection = projCode;
        this.oMapOL.projection = projCode;
        this.oMapOL.units = units;
    },

    /**
     * Method: setMetersPerUnit
     *
     * initializes the meters per unit values when a new map is loaded.  Some systems make different
     * assumptions for the conversion of degrees to meters so this makes sure both Fusion and
     * OpenLayers are using the same value.
     *
     * Parameters:
     * metersPerUnit - {Float} the value returned by LoadMap.php for meters per unit
     */
    setMetersPerUnit: function(metersPerUnit) {
        if (this._fMetersperunit < 0) {
            Fusion.initUnits(metersPerUnit);
            this._fMetersperunit = metersPerUnit;
        } else {
            if (metersPerUnit != this._fMetersperunit) {
                Fusion.reportError(new Fusion.Error(Fusion.Error.WARNING,
                                    'meters per unit value already set'));
            }
        }
    },

    /**
     * Method: getMetersPerUnit
     *
     * returns the meters per unit value
     *
     * Return:
     * metersPerUnit the value as set when the map initialized
     */
    getMetersPerUnit: function() {
        return this._fMetersperunit;
    },

    /**
     * Method: setViewOptions
     * 
     * initializes all widgets with the map units after the map has loaded
     *
     */
    setViewOptions: function(data) {
        this.setWidgetParam('Units', data);
    },

    /**
     * Method: setWidgetParam
     *
     * initializes all widgets with a parameter and value at runtime
     *
     */
    setWidgetParam: function(param, data) {
      for (var i=0; i<Fusion.applicationDefinition.widgetSets.length; ++i) {
        var widgetSet = Fusion.applicationDefinition.widgetSets[i];
        for (var j=0; j<widgetSet.widgetInstances.length; ++j) {
          var widget = widgetSet.widgetInstances[j];
          for (var k=0; k<widget.paramRegister.length; ++k) {
            if (widget.paramRegister[k] == param) {
              widget.setParameter(param, data);
            }
          }
        }
      }
    },

    /**
     * Method: geoToPixMeasure
     *
     * convert geographic into pixels.
     *
     * Parameters:
     * fGeo - {Float} distance in geographic units
     *
     * Return:
     * pixels
     */
    geoToPixMeasure: function(fGeo) {
        return parseInt(fGeo/this.oMapOL.getResolution());
    },

    /**
     * Function: getCurrentCenter
     *
     * returns the current center of the map view
     *
     * Return: 
     * {Object} an object with the following attributes
     * x - the x coordinate of the center
     * y - the y coordinate of the center
     */
    getCurrentCenter: function() {
        var c = this.getCurrentExtents().getCenterLonLat();
        return {x:c.lon, y:c.lat};
    },

    /**
     * Method: getCurrentExtents
     * 
     * returns the current extents
     */
    getCurrentExtents: function() {
        return this.oMapOL.getExtent();
    },

    /**
     * Function: getExtentFromPoint
     *
     * returns the Extent of the map given a center point and a scale (optional)
     *
     * Return: 
     * {<OpenLayers.Bounds>} the bounds for the map centered on a point
     */
    getExtentFromPoint: function(fX,fY,fScale) {
        if (!fScale) {
            fScale = this.getScale();
        }

        var res = OpenLayers.Util.getResolutionFromScale(fScale, this.oMapOL.baseLayer.units);
        var size = this.getSize();
        var w_deg = size.w * res;
        var h_deg = size.h * res;
        return new OpenLayers.Bounds(fX - w_deg / 2,
                                           fY - h_deg / 2,
                                           fX + w_deg / 2,
                                           fY + h_deg / 2);
    },

    /**
     * Function: getScale
     * 
     * Gets the current scale of the map
     * 
     * Returns:
     * The current map scale
     */
    getScale: function() {
        return this.oMapOL.getScale();
    },

    /**
     * Function: getResolution
     * 
     * Gets the current resolution of the map
     * 
     * Returns:
     * The current resolution of the map
     */
    getResolution: function() {
        return this.oMapOL.getResolution();
    },

    getUnits: function() {
        return this.oMapOL.baseLayer.units;
    },

    /**
     * Function: getSize
     * 
     * Gets the size of the map
     * 
     * Returns:
     * 
     *   return description
     */
    getSize: function() {
        return this.oMapOL.getSize();
    },

    getEventPosition: function(e) {
        return this.oMapOL.events.getMousePosition(e);
    },

    setCursor: function(cursor) {
        this.cursor = cursor;
        if (this.isBusy()) {
            return;
        }
        if (cursor && cursor.length && typeof cursor == 'object') {
            for (var i = 0; i < cursor.length; i++) {
                this._oDomObj.style.cursor = cursor[i];
                if (this._oDomObj.style.cursor == cursor[i]) {
                    break;
                }
            }
        } else if (typeof cursor == 'string') {
            this._oDomObj.style.cursor = cursor;
        } else {
            this._oDomObj.style.cursor = 'auto';
        }
    },
    /**
     *
     * Observe specified event on the event div of the map
     *
     * @param sEventName string event name (eg : mousemove')
     * @param fnCB function Call back function name
     *
     */
     observeEvent: function(sEventName, fnCB)
     {
         OpenLayers.Event.observe(this._oDomObj, sEventName, fnCB, false);
     },

     /**
     *
     * Stop observing specified event on the event div of the map
     *
     * @param sEventName string event name (eg : mousemove')
     * @param fnCB function Call back function name
     *
     */
     stopObserveEvent: function(sEventName, fnCB)
     {
         OpenLayers.Event.stopObserving(this._oDomObj, sEventName, fnCB, false);
     },

     /**
     *
     * call the Activate method on the widget
     * if widgets is set to be mutually exclusive,
     * all other widgets are deactivated
     *
     * @param nId integer widget id
     */
     activateWidget: function(oWidget)
     {
         /*console.log('Fusion.Widget.Map.activateWidget ' + oWidget.getName());*/
         // if (oWidget.isMutEx()) {
         //     if (this.oActiveWidget) {
         //         this.deactivateWidget(this.oActiveWidget);
         //     }
         //     oWidget.activate();
         //     this.oActiveWidget = oWidget;
         // } else {
         //     oWidget.activate();
         // }
     },

     /**
     *
     * call the Activate method on the widget
     * if widgets is set to be mutually exclusive,
     * all other widgets are deactivated
     *
     * @param oWidget the widget to deactivate
     */
     deactivateWidget: function(oWidget)
     {
         /*console.log('Fusion.Widget.Map.deactivateWidget ' + oWidget.getName());*/
         //oWidget.deactivate();
         //this.oActiveWidget = null;
     },

     /**
      */
     isLoaded: function() {
         return (this.oMapOL.getExtent() != null);
     },

     getLinkParams: function() {
        var queryParams = {
          extent: this.getCurrentExtents().toBBOX()
        };
        if (this.lastQueryOptions) {
          if (this.lastQueryOptions.filter) {
            queryParams.filter = this.lastQueryOptions.filter;
          }
          if (this.lastQueryOptions.geometry) {
            queryParams.spatialfilter = this.lastQueryOptions.geometry;
          }
          if (this.lastQueryOptions.queryHiddenLayers) {
            queryParams.queryHiddenLayers = this.lastQueryOptions.queryHiddenLayers;
          }
          if (this.lastQueryOptions.maxFeatures) {
            queryParams.maxfeatures = this.lastQueryOptions.maxFeatures;
          }
          if (this.lastQueryOptions.layers) {
            queryParams.selectlayer = this.lastQueryOptions.layers;
          }
          if (this.lastQueryOptions.selectionType) {
            queryParams.variant = this.lastQueryOptions.selectionType;
          }
        }
        for (var i=0; i< this.aMaps.length; ++i) {
          queryParams = OpenLayers.Util.extend(queryParams, this.aMaps[i].getLinkParams());
        }
        return OpenLayers.Util.getParameterString(queryParams);
     },
     
    supressContextMenu: function( bSupress ) {
        this.bSupressContextMenu = bSupress;
    },

    setContextMenu: function(menu) {                         
        //console.log('setcontextmenu');
        this.oContextMenu = menu;
    },

    onContextMenu: function(e) {
        //console.log('oncontextmenu');
        if (this.oContextMenu && !this.bSupressContextMenu && this.isLoaded()) {
            this.oContextMenu.show(new Event(e));
            this.contextMenuPosition = this.getEventPosition(e);
            OpenLayers.Event.stop(e);
        }
    },

    executeFromContextMenu: function(widget) {
        //console.log('executefromcontextmenu');
        widget.activate(this.contextMenuPosition.x, this.contextMenuPosition.y);
    },
    _getDigitizingLayer: function() {
        var layer = this.oMapOL.getLayer(this.ID_DIGITIZING_LAYER);
        if (layer == null) {
            layer = new OpenLayers.Layer.Vector("Digitizing Layer", { styleMap: this.digitizingStyleMap });
            layer.id = this.ID_DIGITIZING_LAYER;
            this.oMapOL.addLayers([layer]);
        }
        return layer;
    },
    /**
     * Method: cancelDigitization
     *
     * De-activates any active digitizers
     */
    cancelDigitization: function() {
        //Don't trigger until the end
        this._deactivateDigitizer(this.DRAW_CONTROL_POINT, false, true);
        this._deactivateDigitizer(this.DRAW_CONTROL_LINE, false, true);
        this._deactivateDigitizer(this.DRAW_CONTROL_LINESTR, false, true);
        this._deactivateDigitizer(this.DRAW_CONTROL_RECT, false, true);
        this._deactivateDigitizer(this.DRAW_CONTROL_POLY, false, true);
        this._deactivateDigitizer(this.DRAW_CONTROL_CIRCLE, false, true);
        this._triggerDigitizerDeactivated();
    },
    _onGeometryDigitized: function(evt, controlId, origCallback) {
        this._deactivateDigitizer(controlId, true, true);
        if (controlId == this.DRAW_CONTROL_CIRCLE) {
            var bcenter = evt.feature.geometry.bounds.getCenterLonLat();
            var radius = evt.feature.geometry.bounds.getWidth() / 2;
            origCallback({ x: bcenter.lon, y: bcenter.lat, r: radius });
        } else {
            origCallback(evt.feature.geometry);
        }
    },
    _setDigitizationPrompt: function(prompt) {
        this.message.info(prompt + ' <a id="abortDigitizationLink" href="javascript:void(0)">' + OpenLayers.i18n("stop") + '</a>');
        var link = this.message.container.ownerDocument.getElementById("abortDigitizationLink");
        //Wire the anchor click
        link.onclick = OpenLayers.Function.bind(function() {
            this.cancelDigitization();
        }, this);
    },
    _deactivateDigitizer: function(controlId, bTrigger, bUnsub) {
        var ctrl = this.oMapOL.getControl(controlId);
        if (ctrl) {
            ctrl.deactivate();
            ctrl.layer.removeAllFeatures();
            if (bUnsub)
                ctrl.events.remove("featureadded");
        }
        if (bTrigger)
            this._triggerDigitizerDeactivated();
    },
    _activateDigitizer: function(ctrl, controlId, origCallback) {
        //Register for one-shot callback notification
        if (ctrl.events) {
            var oneShotHandler = OpenLayers.Function.bind(function(cid, evt) {
                ctrl.events.unregister("featureadded", null, oneShotHandler);
                //console.log("De-registered one-shot callback for: " + cid)
                this._onGeometryDigitized(evt, cid, origCallback);
            }, this, controlId);
            ctrl.events.register("featureadded", null, oneShotHandler);
            //console.log("Register one-shot callback for: " + controlId);
        }
        ctrl.activate();
        this._triggerDigitizerActivated();
    },
    _triggerDigitizerActivated: function() {
        if (!this.isDigitizing) {
            this.isDigitizing = true;
            this.triggerEvent(Fusion.Event.MAP_DIGITIZER_ACTIVATED);
            //console.log("digitizer activated");
        }
    },
    _triggerDigitizerDeactivated: function() {
        if (this.isDigitizing) {
            this.isDigitizing = false;
            try {
                this.message.clear();
            } catch (e) {}
            this.triggerEvent(Fusion.Event.MAP_DIGITIZER_DEACTIVATED);
            //console.log("digitizer de-activated");
        }
    },
    onKeyPress: function(e) {
        if (this.isDigitizing) {
            var charCode = (e.charCode) ? e.charCode : ((e.keyCode) ? e.keyCode : e.which);
            if (charCode == OpenLayers.Event.KEY_ESC) {
                this.cancelDigitization();
            }
        }
    },
    /**
     * Method: digitizePoint
     *
     * Digitizes a point and passes the value to a function for processing. Digitization can be cancelled via the cancelDigitization()
     * method or via pressing the ESC key
     *
     * Parameters:
     * callback - {Function} The callback function that will receive the digitized geometry
     *
     * Return:
     * {OpenLayers.Geometry.Point}
     */
    digitizePoint: function(options, callback) {
        if (this.isDigitizing) {
            this.cancelDigitization();
        }
        var ctrl = this.oMapOL.getControl(this.DRAW_CONTROL_POINT);
        if (ctrl == null) {
            ctrl = new OpenLayers.Control.DrawFeature(
                this._getDigitizingLayer(),
                OpenLayers.Handler.Point, {
                    id: this.DRAW_CONTROL_POINT,
                    handlerOptions: {
                        layerOptions: {
                            styleMap: this.digitizingStyleMap
                        }
                    }
                });
            this.oMapOL.addControl(ctrl);
        }
        if (options && options.prompt) {
            this._setDigitizationPrompt(options.prompt);
        }
        this._activateDigitizer(ctrl, this.DRAW_CONTROL_POINT, callback);
    },
    /**
     * Method: digitizeLine
     *
     * Digitizes a line and passes the value to a function for processing. Digitization can be cancelled via the cancelDigitization()
     * method or via pressing the ESC key
     *
     * Parameters:
     * options - {Object} digitization options
     * callback - {Function} The callback function that will receive the digitized geometry
     *
     * Return:
     * {OpenLayers.Geometry.LineString}
     */
    digitizeLine: function(options, callback) {
        if (this.isDigitizing) {
            this.cancelDigitization();
        }
        var ctrl = this.oMapOL.getControl(this.DRAW_CONTROL_LINE);
        if (ctrl == null) {
            ctrl = new OpenLayers.Control.DrawFeature(
                this._getDigitizingLayer(),
                OpenLayers.Handler.Path, {
                    id: this.DRAW_CONTROL_LINE,
                    handlerOptions: {
                        maxVertices: 2,
                        freehandToggle: null, 
                        freehand: false, 
                        persist: true,
                        style: "default", // this forces default render intent
                        layerOptions: {
                            styleMap: this.digitizingStyleMap
                        }
                    }
                });
            this.oMapOL.addControl(ctrl);
        }
        if (options && options.prompt) {
            this._setDigitizationPrompt(options.prompt);
        }
        this._activateDigitizer(ctrl, this.DRAW_CONTROL_LINE, callback);
    },
    /**
     * Method: digitizeLineString
     *
     * Digitizes a line string and passes the value to a function for processing. Digitization can be cancelled via the cancelDigitization()
     * method or via pressing the ESC key
     *
     * Parameters:
     * options - {Object} digitization options
     * callback - {Function} The callback function that will receive the digitized geometry
     *
     * Return:
     * {OpenLayers.Geometry.LineString}
     */
    digitizeLineString: function(options, callback) {
        if (this.isDigitizing) {
            this.cancelDigitization();
        }
        var ctrl = this.oMapOL.getControl(this.DRAW_CONTROL_LINESTR);
        if (ctrl == null) {
            ctrl = new OpenLayers.Control.DrawFeature(
                this._getDigitizingLayer(),
                OpenLayers.Handler.Path, {
                    id: this.DRAW_CONTROL_LINESTR,
                    handlerOptions: {
                        freehand: false, 
                        persist: true, 
                        style: "default", // this forces default render intent
                        layerOptions: {
                            styleMap: this.digitizingStyleMap
                        }
                    }
                });
            this.oMapOL.addControl(ctrl);
        }
        if (options && options.prompt) {
            this._setDigitizationPrompt(options.prompt);
        }
        this._activateDigitizer(ctrl, this.DRAW_CONTROL_LINESTR, callback);
    },
    /**
     * Method: digitizeRectangle
     *
     * Digitizes a rectangle and passes the value to a function for processing. Digitization can be cancelled via the cancelDigitization()
     * method or via pressing the ESC key
     *
     * Parameters:
     * options - {Object} digitization options
     * callback - {Function} The callback function that will receive the digitized geometry
     *
     * Return:
     * {OpenLayers.Geometry.Polygon}
     */
    digitizeRectangle: function(options, callback) {
        if (this.isDigitizing) {
            this.cancelDigitization();
        }
        var ctrl = this.oMapOL.getControl(this.DRAW_CONTROL_RECT);
        if (ctrl == null) {
            ctrl = new OpenLayers.Control.DrawFeature(
                this._getDigitizingLayer(),
                OpenLayers.Handler.RegularPolygon, {
                    id: this.DRAW_CONTROL_RECT,
                    handlerOptions: {
                        persist: true, 
                        sides: 4, 
                        irregular: true,
                        style: "default", // this forces default render intent
                        layerOptions: {
                            styleMap: this.digitizingStyleMap
                        }
                    }
                });
            this.oMapOL.addControl(ctrl);
        }
        if (options && options.prompt) {
            this._setDigitizationPrompt(options.prompt);
        }
        this._activateDigitizer(ctrl, this.DRAW_CONTROL_RECT, callback);
    },
    /**
     * Method: digitizePolygon
     *
     * Digitizes a polygon and passes the value to a function for processing. Digitization can be cancelled via the cancelDigitization()
     * method or via pressing the ESC key
     *
     * Parameters:
     * options - {Object} digitization options
     * callback - {Function} The callback function that will receive the digitized geometry
     *
     * Return:
     * {OpenLayers.Geometry.Polygon}
     */
    digitizePolygon: function(options, callback) {
        if (this.isDigitizing) {
            this.cancelDigitization();
        }
        var ctrl = this.oMapOL.getControl(this.DRAW_CONTROL_POLY);
        if (ctrl == null) {
            ctrl = new OpenLayers.Control.DrawFeature(
                this._getDigitizingLayer(),
                OpenLayers.Handler.Polygon, {
                    id: this.DRAW_CONTROL_POLY,
                    handlerOptions: {
                        freehand: false, 
                        persist: true, 
                        style: "default", // this forces default render intent
                        layerOptions: {
                            styleMap: this.digitizingStyleMap
                        }
                    }
                });
            this.oMapOL.addControl(ctrl);
        }
        if (options && options.prompt) {
            this._setDigitizationPrompt(options.prompt);
        }
        this._activateDigitizer(ctrl, this.DRAW_CONTROL_POLY, callback);
    },
    /**
     * Method: digitizeCircle
     *
     * Digitizes a circle and passes the value to a function for processing. Digitization can be cancelled via the cancelDigitization()
     * method or via pressing the ESC key
     *
     * Parameters:
     * options - {Object} digitization options
     * callback - {Function} The callback function that will receive the digitized circle
     *
     * Return:
     * {Object} - The circle structured { x, y, r }
     */
    digitizeCircle: function(options, callback) {
        if (this.isDigitizing) {
            this.cancelDigitization();
        }
        var ctrl = this.oMapOL.getControl(this.DRAW_CONTROL_CIRCLE);
        if (ctrl == null) {
            ctrl = new OpenLayers.Control.DrawFeature(
                this._getDigitizingLayer(),
                OpenLayers.Handler.RegularPolygon, {
                    id: this.DRAW_CONTROL_CIRCLE,
                    handlerOptions: {
                        sides: 40,
                        layerOptions: {
                            styleMap: this.digitizingStyleMap
                        }
                    }
                });
            this.oMapOL.addControl(ctrl);
        }
        if (options && options.prompt) {
            this._setDigitizationPrompt(options.prompt);
        }
        this._activateDigitizer(ctrl, this.DRAW_CONTROL_CIRCLE, callback);
    }
});

/**
 * Class: Fusion.SelectionObject
 * 
 * Holds information about selected map features
 */
Fusion.SelectionObject = OpenLayers.Class({
    aLayers : null,

    initialize: function(o)
    {
        this.aLayers = [];
        this.nTotalElements =0;
        this.nLayers = 0;

        if ( o.layers &&  o.layers.length > 0)
        {
            this.fMinX =  o.extents.minx;
            this.fMinY =  o.extents.miny;
            this.fMaxX =  o.extents.maxx;
            this.fMaxY =  o.extents.maxy;

            this.nLayers =  o.layers.length;
            for (var i=0; i<o.layers.length; i++)
            {
                this.aLayers[i] = new Fusion.SelectionObject.Layer(o, o.layers[i]);
                this.nTotalElements += this.aLayers[i].getNumElements();
            }
        }
    },
    /**
     * Function: getNumElements
     * 
     * Gets the number of selected map features
     * 
     * Returns:
     * 
     *   the number of selected map features
     */
    getNumElements : function()
    {
        return this.nTotalElements;
    },

    /**
     * Function: getLowerLeftCoord
     * 
     * Gets the lower left coordinate of this selection's bounding box
     * 
     * Returns:
     * 
     *   the lower left coordinate
     */
    getLowerLeftCoord : function()
    {
        return {x:this.fMinX, y:this.fMinY};
    },

    /**
     * Function: getUpperRightCoord
     * 
     * Gets the upper right coordinate of this selection's bounding box
     * 
     * Returns:
     * 
     *   the upper right coordinate
     */
    getUpperRightCoord : function()
    {
        return {x:this.fMaxX, y:this.fMaxY};
    },

    /**
     * Function: getNumLayers
     * 
     * Gets the number of map layers included in this selection
     * 
     * Returns:
     *  The number of map layers
     */
    getNumLayers : function()
    {
        return this.nLayers;
    },

    /**
     * Function: getLayerByName
     * 
     * Gets the selected map layer by its name
     * 
     * Parameters:
     * 
     *   name - The name of the selected map layer
     * 
     * Returns:
     * 
     *   {<Fusion.SelectionObject.Layer>} The selected map layer
     */
    getLayerByName : function(name)
    {
        var oLayer = null;
        for (var i=0; i<this.nLayers; i++)
        {
            if (this.aLayers[i].getName() == name)
            {
                oLayer = this.aLayers[i];
                break;
            }
        }
        return oLayer;
    },

    /**
     * Function: getLayer
     * 
     * Gets the selected map layer by the specified index
     * 
     * Parameters:
     * 
     *   iIndice - The index of the selected map layer
     * 
     * Returns:
     * 
     *   {<Fusion.SelectionObject.Layer>} The selected map layer
     */
    getLayer : function(iIndice)
    {
        if (iIndice >=0 && iIndice < this.nLayers)
        {
            return this.aLayers[iIndice];
        }
        else
        {
            return null;
        }

    }
});

/**
 * Class: Fusion.SelectionObject.Layer
 * 
 * Defines a map layer in a {<Fusion.SelectionObject>}
 */
Fusion.SelectionObject.Layer = OpenLayers.Class({
    name: null,
    nElements: null,
    aElements: null,
    nProperties: null,
    aPropertiesName: null,
    aPropertiesTypes: null,

    type: null,
    area: null,
    distance: null,
    bbox: null,
    center: null,

    initialize: function(o, layerName)
    {
        this.name =  layerName;
        this.nElements =  o[layerName].numelements;

        this.aElements = [];

        this.nProperties = o[layerName].propertyvalues ? o[layerName].propertyvalues.length : 0;

        this.aPropertiesName = [];
        this.aPropertiesName  = o[layerName].propertyvalues;

        this.aPropertiesTypes = [];
        this.aPropertiesTypes = o[layerName].propertytypes;

        //var oValueCollection = oNode.findNextNode('ValueCollection');

        this.area = 0;
        this.distance = 0;

        for (var i=0; i<o[layerName].values.length; i++)
        {
            this.aElements[i] =[];
            for (var j=0; j<o[layerName].values[i].length; j++)
            {
                this.aElements[i][j] = o[layerName].values[i][j];
            }
        }

        //loop over all features in the metadata array
        for (var i=0; i<o[layerName].metadata.length; i++) {
          var featureMetadata = o[layerName].metadata[i];
          var dim = featureMetadata[0];   //nothing to do with this one
          var bbox = featureMetadata[1];
          var center = featureMetadata[2];
          var area = featureMetadata[3];
          var length = featureMetadata[4];
          this.area += parseFloat(area);
          this.distance += parseFloat(length);
        }

        /*
        var iElement=0;
        while(oValueCollection)
        {
            this.aElements[iElement] = [];
            for (var i=0; i<oValueCollection.childNodes.length; i++)
            {
                oTmp = oValueCollection.childNodes[i].findFirstNode('v');
                this.aElements[iElement][i] = oTmp.textContent;

            }
            var type = oValueCollection.attributes['type'];
            var area = oValueCollection.attributes['area'];
            var distance = oValueCollection.attributes['distance'];
            var bbox = oValueCollection.attributes['bbox'];
            var center = oValueCollection.attributes['center'];

            this.aElements[iElement]['attributes'] = {};
            this.aElements[iElement]['attributes'].type = type;
            this.aElements[iElement]['attributes'].bbox = bbox;
            this.aElements[iElement]['attributes'].center = bbox;
            //console.log('type is ' + type);
            if (type > 1) {
                this.area += parseFloat(area);
                this.aElements[iElement]['attributes'].area = area;
            }
            if (type > 0) {
                this.aElements[iElement]['attributes'].distance = distance;
                this.distance += parseFloat(distance);
            }
            oValueCollection = oNode.findNextNode('ValueCollection');
            iElement++;
        }
        */
        //console.log( 'final area is ' + this.area);
        //console.log( 'final distance is ' + this.distance);

    },

    getName : function()
    {
        return this.name;
    },

    getNumElements : function()
    {
        return this.nElements;
    },

    getNumProperties : function()
    {
        return this.nProperties;
    },

    getPropertyNames : function()
    {
        return this.aPropertiesName;
    },

    getPropertyTypes : function()
    {
        return this.aPropertiesTypes;
    },

    getElementValue : function(iElement, iProperty)
    {
        if (iElement >=0 && iElement < this.nElements &&
            iProperty >=0 && iProperty < this.nProperties)
        {
            return this.aElements[iElement][iProperty];
        }
        else
        {
            return null;
        }
    }
});
/*****************************************************************************
 * Class: Fusion.MapMessage
 *
 * It is to show a floating message bar over the main map viewer
 */
Fusion.MapMessage = OpenLayers.Class({
    parentNode : null,
    domObj : null,
    leadingIcon : null,
    textCell : null,
    message : "",
    
    infoIconName : "images/icons/info.png",
    warningIconName : "images/icons/warning.png",
    errorIconName : "images/icons/error.png",
    
    containerCssText : "position:absolute; z-index:10000; padding:10px; border:solid 2px #ECECEC; background:#FFFFBB",
    iconCssText : "margin-right:10px",
    textCellCssText : "width:100%; vertical-align:top; font: 8pt Tahoma",

    opacity: 0.95,
    
    initialize : function(parentNode)
    {
        this.parentNode = $(parentNode);
    },
    
    createBar : function()
    {
        if (!this.container)
        {
            // Create the div container
            var container   = document.createElement("div");
            container.style.visibility = "hidden";
            this.container  = $(container);
            this.parentNode.appendChild(container);
        }

        this.container.style.cssText = this.containerCssText;
        var offset = {left:10, top:10};
        this.container.style.left = offset.left + "px";
        this.container.style.top  = offset.top  + "px";
        
        this.container.innerHTML = "";
        
        // Create the inner table
        var table = document.createElement("table");
        this.container.appendChild(table);
        table.style.width = "100%";
        table.cellSpacing = "0";
        table.cellPadding = "0";
        table.border      = "0";
        // Create the table row
        var row   = table.insertRow(0);
        // The icon cell
        var cell  = row.insertCell(0);
        // Add the info icon by default
        var icon  = document.createElement("img");
        icon.src  = this.infoIconName;
        cell.appendChild(icon);
        icon.style.cssText = this.iconCssText;
        this.leadingIcon   = icon;
        // Create the text cell
        cell      = row.insertCell(1);
        cell.style.cssText = this.textCellCssText;
        this.textCell = $(cell);
        this.textCell.innerHTML = this.message;
        
        this.refreshLayout();
        // Hide message bar by default
        this.container.setOpacity(0);
        this.container.style.visibility = "visible";
    },
    
    removeBar : function()
    {
        if (typeof (this.container) != "undefined" && this.container != null)
        {
            this.container.fade(0);
            window.setTimeout((function()
            {
                if (typeof (this.container) != "undefined" && this.container != null)
                {
                    this.container.parentNode.removeChild(this.container);
                    this.container = null;
                }
                
            }).bind(this), 500);
        }
    },
    
    /**
     * Function: info
     * 
     * Displays an informational message on the Map Message notification bar
     * 
     * Parameters:
     * 
     *   message - The message to display
     */
    info : function(message)
    {
        this.message = message;
        this.show();
        this.leadingIcon.src = this.infoIconName;
    },
    
    /**
     * Function: warn
     * 
     * Displays a warning message on the Map Message notification bar
     * 
     * Parameters:
     * 
     *   message - The message to display
     */
    warn : function(message)
    {
        this.message = message;
        this.show();
        this.leadingIcon.src = this.warningIconName;
    },
    
    /**
     * Function: error
     * 
     * Displays a error message on the Map Message notification bar
     * 
     * Parameters:
     * 
     *   message - The message to display
     */
    error : function(message)
    {
        this.message = message;
        this.show();
        this.leadingIcon.src = this.errorIconName;
    },
    
    /**
     * Function: clear
     * 
     * Hides the notification bar and clears all content
     */
    clear : function()
    {
        this.message = "";
        this.textCell.innerHTML = "";
        this.hide();
    },
    
    /**
     * Function: show
     * 
     * Displays the notification bar
     */
    show : function()
    {
        this.createBar();
        this.textCell.innerHTML = this.message;
        this.container.fade(this.opacity);
    },
    
    /**
     * Function: hide
     * 
     * Hides the notification bar
     */
    hide : function()
    {
        this.removeBar();
    },
    
    hideDesignatedMessage: function(message)
    {
        if(message == this.message)
        {
            this.removeBar();
        }
    },
    
    refreshLayout: function()
    {
        if (typeof (this.container) != "undefined" && this.container != null)
        {
            // 44 = 2 * padding (10) + 2 * offset(10) + 2 * border (2)
            var newWidth = this.parentNode.offsetWidth - 44;
            if (newWidth >= 0)
                this.container.style.width  = this.parentNode.offsetWidth - 44 + "px";
        }
    },
    
    CLASS_NAME: "Fusion.MapMessage"
});/**
 * Fusion.Layers
 *
 * $Id: MapGuide.js 1590 2008-10-10 14:01:27Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * Constant: Fusion.Event.MAP_LAYER_TOGGLED
 */
Fusion.Event.MAP_LAYER_TOGGLED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.MAP_LAYER_ORDER_CHANGED
 */
Fusion.Event.MAP_LAYER_ORDER_CHANGED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_LOADED
 */
Fusion.Event.LAYER_LOADED = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_LOADING
 */
Fusion.Event.LAYER_LOADING = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_START_EDIT
 */
Fusion.Event.LAYER_START_EDIT = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_STOP_EDIT
 */
Fusion.Event.LAYER_STOP_EDIT = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_DATA_FILTER
 */
Fusion.Event.LAYER_DATA_FILTER = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_DATA_RELOAD
 */
Fusion.Event.LAYER_DATA_RELOAD = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_DATA_ADD
 */
Fusion.Event.LAYER_DATA_ADD = Fusion.Event.lastEventId++;
/**
 * Constant: Fusion.Event.LAYER_DATA_DELETE
 */
Fusion.Event.LAYER_DATA_DELETE = Fusion.Event.lastEventId++;

/**
 * Class: Fusion.Layers
 * 
 * Implements Layers for Fusion
 * 
 * Inherits from:
 * 
 *  - <Fusion.Lib.EventMgr>
 */
Fusion.Layers = OpenLayers.Class(Fusion.Lib.EventMgr, {
    bSingleTile: null,
    bIsBaseLayer: false,     //TODO: set this in AppDef?
    bDisplayInLegend: true,   //TODO: set this in AppDef?
    bExpandInLegend: true,   //TODO: set this in AppDef?
    bMapLoaded: false,
    bIsMapWidgetLayer: true,  //Set this to false for overview map layers
    bLayersReversed: false,     //MGOS returns layers top-most layer first
    sMapResourceId: null,    //pointer to the resource that defines the map (URL, MapFile, MGOS, etc)
    sImageType: 'png',      //TODO: set this in AppDef?
    clientAgent: 'Fusion Viewer',
    noCache: false,
    _sMapTitle: null,
    _sMapname: null,
    supports: {
      query: false,
      edit: false
    },

    initialize: function(map, mapTag, isMapWidgetLayer) {
        // console.log('Fusion.Layers.initialize');

        this.registerEventID(Fusion.Event.MAP_SELECTION_ON);
        this.registerEventID(Fusion.Event.MAP_SELECTION_OFF);
        this.registerEventID(Fusion.Event.MAP_LOADED);
        this.registerEventID(Fusion.Event.LAYER_LOADED);
        this.registerEventID(Fusion.Event.LAYER_LOADING);
        this.registerEventID(Fusion.Event.MAP_LAYER_ORDER_CHANGED);
        this.registerEventID(Fusion.Event.LAYER_PROPERTY_CHANGED);
        this.registerEventID(Fusion.Event.LAYER_START_EDIT);
        this.registerEventID(Fusion.Event.LAYER_STOP_EDIT);
        this.registerEventID(Fusion.Event.LAYER_DATA_FILTER);
        this.registerEventID(Fusion.Event.LAYER_DATA_RELOAD);
        this.registerEventID(Fusion.Event.LAYER_DATA_ADD);
        this.registerEventID(Fusion.Event.LAYER_DATA_DELETE);
        
        this.mapWidget = map;
        this.oSelection = null;
        if (isMapWidgetLayer != null) {
            this.bIsMapWidgetLayer = isMapWidgetLayer;
        }

        this.mapTag = mapTag;
        if (!this.mapTag.layerOptions) {
          this.mapTag.layerOptions = {};
        }
        this.ratio = this.mapTag.layerOptions.MapRatio ? this.extension.MapRatio[0] : 1.0;
        this.bSingleTile = mapTag.singleTile; //this is set in thhe AppDef.Map class
        this.bIsBaseLayer = mapTag.isBaseLayer;
        this.sMapResourceId = mapTag.resourceId ? mapTag.resourceId : '';
        this.mapInfo = mapTag.mapInfo;
        this.layerType = mapTag.type;
        this.id = mapTag.id;
    },

    /**
     * Function: loadScaleRanges
     *
     * This function should be called after the map has loaded. It
     * loads the scsle ranges for each layer. I tis for now only
     * used by the legend widget.
     */

    loadScaleRanges: function(userFunc) {
      if (userFunc) userFunc();
    },

    /**
     * Function: getScale
     * 
     * Gets the current scale of the map widget
     * 
     * Returns:
     * 
     *   The current scale of the map widget
     */
    getScale: function() {
        return this.mapWidget.getScale();
    },

    /**
     * Function: getMapName
     * 
     * Gets the map name of the map widget
     * 
     * Returns:
     * 
     *   The map name
     */
    getMapName: function() {
        return this._sMapname;
    },

    /**
     * Function: getMapTitle
     * 
     * Gets the map title of the map widget
     * 
     * Returns:
     * 
     *   the map title
     */
    getMapTitle: function() {
        return this._sMapTitle;
    },
    
    /**
     * Function: getMetadata
     * 
     * Gets the value for the specified metadata property
     * 
     * Parameters:
     * 
     *   key - {String} the metadata property name
     * 
     * Returns:
     * 
     *   The metadata property value
     */
    getMetadata: function(key) {
        if (this.metadata && typeof this.metadata[key] != 'undefined') {
            return this.metadata[key];
        } else {
            return '';
        }
    },

    /**
     * Function: isMapLoaded
     *
     * Returns true if the Map has been laoded succesfully form the server
     */
    isMapLoaded: function() {
        return this.bMapLoaded;
    },

    getMaxExtent: function() {
      var maxExtent = null;
      if (this.oLayerOL) {
        maxExtent = this.oLayerOL.maxExtent;
      }
      return maxExtent;
    },

    hasSelection: function() {
      return this.bSelectionOn;
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectedFeatureCount: function() {
      var total = 0;
      return total;
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectedLayers: function() {
      var layers = [];
      return layers;
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectableLayers: function() {
      var layers = [];
      return layers;
    },

    setSelection: function (selText, zoomTo) {
    },


   /**
    * Method: getSelection
    * 
    * asynchronously load the current selection.  When the current
    * selection changes, the selection is not loaded because it
    * could be a lengthy process.  The user-supplied function will
    * be called when the selection is available.
    *
    * Parameters:
    *  userFunc - {Function} a function to call when the
    *        selection has loaded
    *  layers - {string} Optional parameter.  A comma separated
    *        list of layer names (Roads,Parcels). If it is not
    *        given, all the layers that have a selection will be used
    *  startcount - {string} Optional parameter.  A comma separated
    *        list of a statinh index and the number of features to be retured for
    *        each layer given in the layers parameter. Index starts at 0
    *        (eg: 0:4,2:6 : return 4 elements for the first layers starting at index 0 and
    *         six elements for layer 2 starting at index 6). If it is not
    *        given, all the elemsnts will be returned.
    */
    getSelection: function(userFunc, layers, startcount) {
    },

    /**
     * Function: clearSelection
     * 
     * Utility function to clear current selection
     */
    clearSelection: function() {
    },

    /**
     * Function: query
     * 
     * Do a query on the map
     * 
     * Parameters:
     * 
     *   options - Query options
     */
    query: function(options) {
    },

    processLayerEvents: function(layer, isEnabling) {
        if (this.mapInfo && this.mapInfo.mapEvents.layerEvents[layer.layerName]) {
            var layerEvent = this.mapInfo.mapEvents.layerEvents[layer.layerName];
            var events = isEnabling ? layerEvent.onEnable : layerEvent.onDisable;
            for (var i=0; i<events.length; i++) {
                var o = events[i];
                if (o.type == 'layer') {
                    var l = this.layerRoot.findLayer(o.name);
                    if (l) {
                        if (o.enable) {
                            l.show(true);
                        } else {
                            l.hide(true);
                        }
                    }

                } else if (o.type == 'group') {
                    var g = this.layerRoot.findGroupByAttribute('groupName', o.name);
                    if (g) {
                        if (o.enable) {
                            g.show(true);
                        } else {
                            g.hide(true);
                        }
                    }
                }
            }
        }
    },

    processGroupEvents: function(group, isEnabling) {
        if (this.mapInfo && this.mapInfo.mapEvents.groupEvents[group.groupName]) {
            var groupEvent = this.mapInfo.mapEvents.groupEvents[group.groupName];
            var events = isEnabling ? groupEvent.onEnable : groupEvent.onDisable;
            for (var i=0; i<events.length; i++) {
                var o = events[i];
                if (o.type == 'layer') {
                    var l = this.layerRoot.findLayer(o.name);
                    if (l) {
                        if (o.enable) {
                            l.show(true);
                        } else {
                            l.hide(true);
                        }
                    }

                } else if (o.type == 'group') {
                    var g = this.layerRoot.findGroupByAttribute('groupName', o.name);
                    if (g) {
                        if (o.enable) {
                            g.show(true);
                        } else {
                            g.hide(true);
                        }
                    }
                }
            }
        }
    },

    refreshLayer: function( layer ) {
        this.drawMap();
    },

    setParameter: function(param, value) {
        if (param == 'SelectionType') {
            this.selectionType = value;
        }
    },

    loadStart: function() {
      if (this.bIsMapWidgetLayer) {
        this.mapWidget._addWorker();
      }
    },

    loadEnd: function() {
      if (this.bIsMapWidgetLayer) {
        this.mapWidget._removeWorker();
      }
    },

    getGroupInfoUrl: function(groupName) {
        if (this.mapInfo) {
            var groups = this.mapInfo.links.groups;
            for (var i=0; i<groups.length; i++) {
                if (groups[i].name == groupName) {
                    return groups[i].url;
                }
            }
        }
        return null;
    },
    getLayerInfoUrl: function(layerName) {
        if (this.mapInfo) {
            var layers = this.mapInfo.links.layers;
            for (var i=0; i<layers.length; i++) {
                if (layers[i].name == layerName) {
                    return layers[i].url;
                }
            }
        }
        return null;
    },
    
    getMapTip: function(mapTipWidget) {},
    
    startEditing: function() { },

    stopEditing: function() { }
});

/**
 * Constant: Fusion.Event.GROUP_PROPERTY_CHANGED
 */
Fusion.Event.GROUP_PROPERTY_CHANGED = Fusion.Event.lastEventId++;

/**
 * Class: Fusion.Layers.Group
 * 
 * Implements the map layer groups
 * 
 * Inherits from:
 * 
 *  - <Fusion.Lib.EventMgr>
 */
Fusion.Layers.Group = OpenLayers.Class(Fusion.Lib.EventMgr, {
    name: null,
    groups: null,
    layers: null,
    oMap: null,
    initialize: function(o, oMap) {
        this.uniqueId = o.uniqueId;
        this.name = o.groupName;
        this.groups = [];
        this.layers = [];
        this.oMap = oMap;
        this.groupName = o.groupName;
        this.legendLabel = o.legendLabel;
        this.parentUniqueId = o.parentUniqueId;
        this.groupType = o.groupType;
        this.displayInLegend = o.displayInLegend;
        this.expandInLegend = o.expandInLegend;
        this.visible = o.visible;
        this.initiallyVisible = o.visible;
        this.actuallyVisible = o.actuallyVisible;
        this.isBaseMapGroup = o.isBaseMapGroup;
        this.registerEventID(Fusion.Event.GROUP_PROPERTY_CHANGED);
    },

    show: function(noDraw) {
        if (this.visible) {
            return;
        }
        this.oMap.showGroup(this, noDraw ? true : false);
        this.set('visible', true);
        if (this.legend && this.legend.treeItem && this.legend.treeItem.checkBox) {
            this.legend.treeItem.checkBox.checked = true;
        }
    },

    hide: function(noDraw) {
        if (!this.visible) {
            return;
        }
        this.oMap.hideGroup(this, noDraw ? true : false);
        this.set('visible', false);
        if (this.legend && this.legend.treeItem && this.legend.treeItem.checkBox) {
            this.legend.treeItem.checkBox.checked = false;
        }
    },

    isVisible: function() {
        return this.visible;
    },

    clear: function() {
        for (var i=0; i<this.groups.length; i++) {
            this.groups[i].clear();
        }
        for (var i=0; i<this.layers.length; i++) {
            this.layers[i].clear();
        }
        this.groups = [];
        this.layers = [];
    },

    set: function(property, value) {
        this[property] = value;
        this.triggerEvent(Fusion.Event.GROUP_PROPERTY_CHANGED, this);
    },

    addGroup: function(group,reverse) {
        group.parentGroup = this;
        if (reverse) {
          this.groups.unshift(group);
        } else {
          this.groups.push(group);
        }
    },

    addLayer: function(layer,reverse) {
        layer.parentGroup = this;
        if (reverse) {
          this.layers.unshift(layer);
        } else {
          this.layers.push(layer);
        }
    },

    findGroup: function(name) {
        return this.findGroupByAttribute('uniqueId', name);
    },

    findGroupByAttribute: function(attribute, value) {
        if (this[attribute] == value) {
            return this;
        }
        for (var i=0; i<this.groups.length; i++) {
            var group = this.groups[i].findGroupByAttribute(attribute, value);
            if (group) {
                return group;
            }
        }
        return null;
    },

    findLayer: function(name) {
        return this.findLayerByAttribute('name', name);
    },

    findLayerByAttribute: function(attribute, value) {
        for (var i=0; i<this.layers.length; i++) {
            if (this.layers[i][attribute] == value) {
                return this.layers[i];
            }
        }
        for (var i=0; i<this.groups.length; i++) {
            var layer = this.groups[i].findLayerByAttribute(attribute,value);
            if (layer) {
                return layer;
            }
        }
        return null;
    },

    deleteLayer: function(uniqueId) {
        for (var i=0; i<this.layers.length; i++) {
            if (this.layers[i]['uniqueId'] == uniqueId) {
                this.layers.splice(i,1);
            }
        }
        for (var i=0; i<this.groups.length; i++) {
            this.groups[i].deleteLayer(uniqueId);
        }
    }

});

/**
 * Constant: Fusion.Event.LAYER_PROPERTY_CHANGED
 */
Fusion.Event.LAYER_PROPERTY_CHANGED = Fusion.Event.lastEventId++;

/**
 * Variable: Fusion.Layers.Layer
 * 
 * Implements individual map legend layers
 */
Fusion.Layers.Layer = OpenLayers.Class(Fusion.Lib.EventMgr, {

    name: null,
    scaleRanges: null,
    oMap: null,

    initialize: function(o, oMap) {
        this.oMap = oMap;
        this.layerName = o.layerName;
        this.uniqueId = o.uniqueId;
        this.resourceId = o.resourceId;
        this.selectedFeatureCount = 0;
        this.layerTypes = [].concat(o.layerTypes);
        this.legendLabel = o.legendLabel;
        this.displayInLegend = o.displayInLegend;
        this.expandInLegend = o.expandInLegend;
        this.actuallyVisible = o.actuallyVisible;
        this.statusDefault = o.statusdefault;
        this.feature = OpenLayers.Util.extend({},o.feature);
        this.attribute = OpenLayers.Util.extend({},o.attribute);
        this.editable = this.feature.create || 
                        this.feature.update || 
                        this.feature.del || 
                        this.attribute.create || 
                        this.attribute.update || 
                        this.attribute.del;
        this.isEditing = false;
        this.visible = o.visible;
        this.initiallyVisible = o.visible;
        this.selectable = o.selectable;
        this.metadata = o.metadata?o.metadata:{};
        this.isBaseMapLayer = o.isBaseMapLayer;

        //determine the layer type so that the correct icon can be displayed in the legend
        this.layerType = null;
        if (this.supportsType(Fusion.Constant.LAYER_RASTER_TYPE)) {   //raster layers
          this.layerType = Fusion.Constant.LAYER_RASTER_TYPE;
        } else if (this.supportsType(Fusion.Constant.LAYER_DWF_TYPE)) {  //DWF layers
          this.layerType = Fusion.Constant.LAYER_DWF_TYPE;
        }

        this.parentGroup = o.parentGroup;
        this.minScale = o.minScale;
        this.maxScale = o.maxScale;
        if (this.maxScale == 'infinity') {
          this.maxScale = 1000000000000;
        }
        this.scaleRanges = [];

        if (o.scaleRanges)
        {
          for (var i=0; i<o.scaleRanges.length; i++) {
            var scaleRange = new Fusion.Layers.ScaleRange(o.scaleRanges[i],
                                                                 this.layerType);
            this.scaleRanges.push(scaleRange);
          }
        }

        //this.registerEventID(Fusion.Event.LAYER_PROPERTY_CHANGED);
    },

    supportsType: function(type) {
        for (var i=0; i<this.layerTypes.length; i++) {
            if (this.layerTypes[i] == type) {
                return true;
            }
        }
        return false;
    },

    getScaleRange: function(fScale) {
        for (var i=0; i<this.scaleRanges.length; i++) {
            if (this.scaleRanges[i].contains(fScale)) {
                return this.scaleRanges[i];
            }
        }
        return null;
    },

    show: function(noDraw) {
        if (this.visible) {
            return;
        }
        this.set('visible', true);
        this.oMap.showLayer(this, noDraw ? true : false);
        if (this.legend && this.legend.treeItem && this.legend.treeItem.checkBox) { 
            this.legend.treeItem.checkBox.checked = true;
        }
    },

    hide: function(noDraw) {
        if (!this.visible) {
            return;
        }
        this.set('visible',false);
        this.oMap.hideLayer(this, noDraw ? true : false);
        if (this.legend && this.legend.treeItem && this.legend.treeItem.checkBox) { 
            this.legend.treeItem.checkBox.checked = false;
        }
    },

    isVisible: function() {
        return this.visible;
    },

    clear: function() {},

    set: function(property, value) {
        this[property] = value;
        this.oMap.triggerEvent(Fusion.Event.LAYER_PROPERTY_CHANGED, this);
    },
    
    startEditing: function() {
      if (this.oMap.supports.edit && this.editable && !this.isEditing) {
        this.isEditing = true;
        this.oMap.triggerEvent(Fusion.Event.LAYER_START_EDIT, this);
      }
    },
    
    stopEditing: function() {
      if (this.oMap.supports.edit && this.editable && this.isEditing) {
        this.isEditing = false;
        this.oMap.triggerEvent(Fusion.Event.LAYER_STOP_EDIT, this);
      }
    },
    
    hasWFSConnection: function() {
      return this.metadata['wfs_onlineresource'] != undefined && 
             this.metadata.wfs_onlineresource != '';
    },
    
    getWFSConnection: function(callback) {
      var that = this;
      if (this.hasWFSConnection()) {
        if (!this.wfsConnection) {
          var version = this.metadata['wfs_version'] || '1.1.0';
          var typeName = this.metadata['wfs_typename'] || this.name;
          
          OpenLayers.Request.GET({
            url: this.metadata.wfs_onlineresource,
            params: {
              service: 'WFS',
              version: version,
              request: 'GetCapabilities'
            },
            callback: function(gc_response) {
              var gc_parser = new OpenLayers.Format.WFSCapabilities();
              var capabilities = gc_parser.read(gc_response.responseText);
              OpenLayers.Request.GET({
                url: that.metadata.wfs_onlineresource,
                params: {
                  service: 'WFS',
                  version: version,
                  request: 'DescribeFeatureType',
                  typename: typeName
                },
                callback: function(dft_response) {
                  if (dft_response.status == 200) {
                    var dft_parser = new OpenLayers.Format.WFSDescribeFeatureType();
                    var xml = dft_parser.read(dft_response.responseText);
                    if (xml.featureTypes.length) {
                      var props = xml.featureTypes[0].properties;
                      that.metadata.wfs_properties = props;
                      if (!that.metadata['wfs_geometry']) {
                        for (var i=0; i<props.length; i++) {
                          var prop = props[i];
                          if (prop.localType == 'GeometryPropertyType') {
                            that.metadata.wfs_geometry = prop.name;
                            that.metadata.wfs_geometrytype = prop.type;
                            break;
                          }
                        }
                      }
                      that.wfsConnection = new OpenLayers.Protocol.WFS({
                        url: that.metadata.wfs_onlineresource,
                        version: version,
                        featureType: typeName,
                        geometryName: that.metadata.wfs_geometry,
                        srsName: that.oMap.oLayerOL.projection ? that.oMap.oLayerOL.projection.getCode() : '4326'
                      });
                    }
                  }
                  if (callback) {
                    callback.apply(null, [that.wfsConnection]);
                  }
                }
              });
            }
          });
          
          // this.wfsConnection = new OpenLayers.Protocol.WFS({
          //   version: version,
          //   featureType: typeName
          // });
        } else if (callback) {
          callback.apply(null, [this.wfsConnection]);
        }
      }
    }
});

/**
 * Class: Fusion.Layers.ScaleRange
 * 
 * Implements a scale range object
 */
Fusion.Layers.ScaleRange = OpenLayers.Class({
    styles: null,
    initialize: function(o, layerType, iconOpt) {
        this.minScale = o.minScale;
        this.maxScale = o.maxScale;
        this.isCompressed = o.isCompressed;
        if (this.maxScale == 'infinity' || this.maxScale == 'auto') {
          this.maxScale = Infinity;
        }
        this.styles = [];
        if (!o.styles) {
          var styleItem = new Fusion.Layers.StyleItem({legendLabel:iconOpt.label}, layerType, iconOpt);
          this.styles.push(styleItem);
          return;
        }
        var staticIcon = o.styles.length>1 ? false : layerType;
        for (var i=0; i<o.styles.length; i++) {
            var styleItem = new Fusion.Layers.StyleItem(o.styles[i], staticIcon, iconOpt);
            if (o.styles[i].imageData)
            {
                styleItem.iconOpt = {
                    url: o.styles[i].imageData,
                    width: iconOpt.width,
                    height: iconOpt.height
                };
            }
            this.styles.push(styleItem);
        }
    },
    contains: function(fScale) {
        var testScale = Math.round(fScale);
        return testScale >= this.minScale && testScale <= this.maxScale;
    }
});

/**
 * Class: Fusion.Layers.StyleItem
 * 
 * Implements the legend style items to get a legend icon from the server
 */
Fusion.Layers.StyleItem = OpenLayers.Class({
    clientAgent: 'Fusion Viewer',
    initialize: function(o, staticIcon, iconOpt) {
        this.iconOpt = iconOpt;
        this.iconX = o.icon_x || 0;
        this.iconY = o.icon_y || 0;
        this.legendLabel = o.legendLabel;
        this.filter = o.filter;
        this.geometryType = o.geometryType;
        if (this.geometryType == '') {
            this.geometryType = -1;
        }
        this.categoryIndex = o.categoryIndex;
        if (this.categoryindex == '') {
            this.categoryindex = -1;
        }
        this.index = o.index; //TODO: merge this with categoryIndex?
        this.staticIcon = staticIcon;
    }
});
Fusion.Strings.en = {
'scriptFailed': 'failed to load script: ${script}',
'configParseError': 'Error parsing fusion configuration file, initialization aborted',
'configLoadError': 'Error loading fusion configuration file, initialization aborted.' +
                  'Make sure that you have copied config_dist.json to config.json ' +
                  'and have configured the settings for your system',
'pingFailure': 'Failed to keep session alive: ${message}',
'sessionExpired': 'Your current MapGuide session has expired',
'reload': 'Reload this application',
'createSessionFailure': 'Failed to create session: ${message}',
'ajaxError': 'Exception occurred in AJAX callback.\nMessage: ${exception}\nLocation: ${filename} (${line})\nInner Stack: ${innerStack}\nResponse: ${response}',
'detailedErrorTemplateHtml': '<h3>${name}</h3><br/>Error Details:<pre>${message}</pre><br/>Stack Trace:<br/><pre>${stacktrace}</pre><br/>Source: <pre>${source}</pre>',
'importFailed': 'failed to import stylesheet: ${url}',
'serverNotAvailable': '<h2>Server not available. Try to reload the application. If this problem persists, please contact the administrator</h2>',
'invalidXMLDocument': 'xml2json: invalid XML document: ${msg} : ${url}',
'registerEventError': 'Error registering eventID, invalid (empty) eventID.',
'appDefLoadFailed': 'failed to load: ${script}',
'appDefParseError': 'failed to parse ApplicationDefinition',
'widgetSetParseError': 'failed to parse the WidgetSet',
'fusionError': 'Fusion Error: ${type}\n${message}',
'nullExtents': 'Map.setExtents called with null extents',
'mapLoadError': 'Failed to load requested map:\n${error}',
'setLayersError': "setLayers failure: ${error}",
'printTitle': 'Printable Page ',
'noSelection': 'No features selected',
'selectionInfo': '${features} feature(s) selected on ${layers} layer(s)',
'attribute': 'Attribute',
'value': 'Value',
'stop': 'Stop',
'taskHome': 'return to the task pane home',
'prevTask': 'go to previous task executed',
'nextTask': 'go to next task executed',
'taskList': 'Task List',
'taskPane': 'Task Pane',
'imperial': 'Imperial',
'metric': 'Metric',
'deg': 'Degrees',
'refresh': 'Refresh',
'expandAll': 'Expand All',
'expand': 'Expand',
'expandTheme': 'Expand Theme',
'otherThemeItems': '... (${count} other styles)',
'expandCompressedThemeConfirmation': 'Expand this theme?',
'collapseAll': 'Collapse All',
'collapse': 'Collapse',
'defaultMapTitle': 'Map',
'legendTitle': 'Legend',
'selectionPanelTitle': 'Selection',
'ovmapTitle': 'Overview Map',
'ovmapTitleShort': 'Overview',
'taskPaneTitle': 'Tasks',
'segment': 'Segment ${seg}',
'calculating': 'calculating ...',
'panWest': 'Pan West',
'panEast': 'Pan East',
'panSouth': 'Pan South',
'panNorth': 'Pan North',
'zoomOut': 'Zoom Out',
'zoomIn': 'Zoom In',
'printCancel': 'Cancel',
'printGenerate': 'Generate',
'zoomRect': 'Zoom Rectangle',
'maptipLinkText': 'Click for more information',
'redlineLayerName': 'Digitizing Layer ',
'printShowTitle':'Show Title?',
'printOptions'	:'Options',
'printTitleText':'Title',
'printShowLegend':'Show Legend?',
'printShowNorthArrow':'Show North Arrow?',

'quickPlotResolutionWarning':'The current settings exceed the map resolution. Zooming out or increasing the scaling will help create a more legible plot',
'googleSteetViewCSTransformError':'The CS of Current Map Cannot be transformed to EPSG:4326, which will result in Google Street View fail!',
'googleSteetViewNotAvailableError':'The Service of Google Street View may not available in this specific location!',
'googleBaseMapNotOnError':'To use Google Street Viewer, the Google Base Map must be turned on!',
'noCommercialMapLayers':'NONE',
'googleStreet':'Google Street',
'googleSatellite':'Google Satellite',
'googleTerrain':'Google Terrain',
'googleHybrid':'Google Hybrid',
'yahooStreet':'Yahoo Street',
'yahooSatellite':'Yahoo Satellite',
'yahooHybrid':'Yahoo Hybrid',
'bingStreet':'Bing Street',
'bingSatellite':'Bing Satellite',
'bingHybrid':'Bing Hybrid',
'openStreetMap':'Open Street Map',
'openStreetMapTransportMap':'Open Street Map (TransportMap)',
'openStreetMapCycleMap':'Open Street Map (CycleMap)',
'end': '',
'currentPositionOutsideInitialView': 'Your position is outside of the initial view of the map',
'currentPositionOutsideMaxExtent': 'Your position is outside of the maximum extent of the map',
'measureInProgress':'Measurement is currently in progress',
'selectPolygonPrompt':'Click to add a vertex to the selection polygon. Double click to complete the polygon and select all features with it',
'selectRadiusPrompt':'Click and drag to the desired radius to select'
};
/* Translators (2009 onwards):
 *  - Meno25
 *  - Mutarjem horr
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["ar"]
 * Dictionary for العربية.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["ar"] = OpenLayers.Util.applyDefaults({

    'Permalink': "وصلة دائمة",

    'Base Layer': "الطبقة الاساسية",

    'Scale = 1 : ${scaleDenom}': "النسبة = 1 : ${scaleDenom}",

    'W': "غ",

    'E': "شر",

    'N': "شم",

    'S': "ج"

});
/* Translators (2009 onwards):
 *  - EugeneZelenko
 *  - Jim-by
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["be-tarask"]
 * Dictionary for Беларуская (тарашкевіца).  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["be-tarask"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Неапрацаваны вынік запыту ${statusText}",

    'Permalink': "Сталая спасылка",

    'Overlays': "Слаі",

    'Base Layer': "Базавы слой",

    'noFID': "Немагчыма абнавіць магчымасьць, для якога не існуе FID.",

    'browserNotSupported': "Ваш браўзэр не падтрымлівае вэктарную графіку. У цяперашні момант падтрымліваюцца: ${renderers}",

    'minZoomLevelError': "Уласьцівасьць minZoomLevel прызначана толькі для выкарыстаньня са слаямі вытворнымі ад FixedZoomLevels. Тое, што  гэты wfs-слой правяраецца на minZoomLevel — рэха прошлага. Але мы ня можам выдаліць гэтую магчымасьць, таму што ад яе залежаць некаторыя заснаваныя на OL дастасаваньні. Тым ня менш, праверка minZoomLevel будзе выдаленая ў вэрсіі 3.0. Калі ласка, выкарыстоўваеце замест яе ўстаноўкі мінімальнага/максымальнага памераў, як апісана тут: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS-транзакцыя: ПОСЬПЕХ ${response}",

    'commitFailed': "WFS-транзакцыя: ПАМЫЛКА ${response}",

    'googleWarning': "Не атрымалася загрузіць слой Google. \x3cbr\x3e\x3cbr\x3eКаб пазбавіцца гэтага паведамленьня, выберыце новы базавы слой у сьпісе ў верхнім правым куце.\x3cbr\x3e\x3cbr\x3e Хутчэй за ўсё, прычына ў тым, што скрыпт бібліятэкі Google Maps ня быў уключаныя альбо не ўтрымлівае слушны API-ключ для Вашага сайта.\x3cbr\x3e\x3cbr\x3eРаспрацоўшчыкам: Для таго, каб даведацца як зрабіць так, каб усё працавала, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eнацісьніце тут\x3c/a\x3e",

    'getLayerWarning': "Немагчыма загрузіць слой ${layerType}.\x3cbr\x3e\x3cbr\x3eКаб пазбавіцца гэтага паведамленьня, выберыце новы базавы слой у сьпісе ў верхнім правым куце.\x3cbr\x3e\x3cbr\x3eХутчэй за ўсё, прычына ў тым, што скрыпт бібліятэкі ${layerLib} ня быў слушна ўключаны.\x3cbr\x3e\x3cbr\x3eРаспрацоўшчыкам: Для таго, каб даведацца як зрабіць так, каб усё працавала, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eнацісьніце тут\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Маштаб = 1 : ${scaleDenom}",

    'W': "З",

    'E': "У",

    'N': "Пн",

    'S': "Пд",

    'reprojectDeprecated': "Вы выкарыстоўваеце ўстаноўку \'reproject\' для слоя ${layerName}. Гэтая ўстаноўка зьяўляецца састарэлай: яна выкарыстоўвалася для падтрымкі паказу зьвестак на камэрцыйных базавых мапах, але гэта функцыя цяпер рэалізаваная ў убудаванай падтрымцы сфэрычнай праекцыі Мэркатара. Дадатковая інфармацыя ёсьць на http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Гэты мэтад састарэлы і будзе выдалены ў вэрсіі 3.0. Калі ласка, замест яго выкарыстоўвайце ${newMethod}."

});
/* Translators (2009 onwards):
 *  - DCLXVI
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["bg"]
 * Dictionary for Български.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["bg"] = OpenLayers.Util.applyDefaults({

    'Permalink': "Постоянна препратка",

    'Base Layer': "Основен слой",

    'Scale = 1 : ${scaleDenom}': "Мащаб = 1 : ${scaleDenom}",

    'methodDeprecated': "Този метод е остарял и ще бъде премахват в 3.0. Вместо него използвайте ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Fulup
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["br"]
 * Dictionary for Brezhoneg.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["br"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Distro evel reked anveret ${statusText}",

    'Permalink': "Peurliamm",

    'Overlays': "Gwiskadoù",

    'Base Layer': "Gwiskad diazez",

    'noFID': "N\'haller ket hizivaat un elfenn ma n\'eus ket a niverenn-anaout (FID) eviti.",

    'browserNotSupported': "N\'eo ket skoret an daskor vektorel gant ho merdeer. Setu aze an daskorerioù skoret evit ar poent :\n${renderers}",

    'minZoomLevelError': "Ne zleer implijout ar perzh minZoomLevel nemet evit gwiskadoù FixedZoomLevels-descendent. Ar fed ma wiria ar gwiskad WHS-se hag-eñ ez eus eus minZoomLevel zo un aspadenn gozh. Koulskoude n\'omp ket evit e ziverkañ kuit da derriñ arloadoù diazezet war OL a c\'hallfe bezañ stag outañ. Setu perak eo dispredet -- Lamet kuit e vo ar gwiriañ minZoomLevel a-is er stumm 3.0. Ober gant an arventennoù bihanañ/brasañ evel deskrivet amañ e plas : http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Treuzgread WFS : MAT EO ${response}",

    'commitFailed': "Treuzgread WFS Transaction: C\'HWITET ${response}",

    'googleWarning': "N\'eus ket bet gallet kargañ ar gwiskad Google ent reizh.\x3cbr\x3e\x3cbr\x3eEvit en em zizober eus ar c\'hemenn-mañ, dibabit ur BaseLayer nevez en diuzer gwiskadoù er c\'horn dehoù el laez.\x3cbr\x3e\x3cbr\x3eSur a-walc\'h eo peogwir n\'eo ket bet ensoc\'het levraoueg Google Maps pe neuze ne glot ket an alc\'hwez API gant ho lec\'hienn.\x3cbr\x3e\x3cbr\x3eDiorroerien : Evit reizhañ an dra-se, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclick here\x3c/a\x3e",

    'getLayerWarning': "N\'haller ket kargañ ar gwiskad ${layerType} ent reizh.\x3cbr\x3e\x3cbr\x3eEvit en em zizober eus ar c\'hemenn-mañ, dibabit ur BaseLayer nevez en diuzer gwiskadoù er c\'horn dehoù el laez.\x3cbr\x3e\x3cbr\x3eSur a-walc\'h eo peogwir n\'eo ket bet ensoc\'het mat al levraoueg ${layerLib}.\x3cbr\x3e\x3cbr\x3eDiorroerien : Evit gouzout penaos reizhañ an dra-se, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclick here\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Skeul = 1 : ${scaleDenom}",

    'W': "K",

    'E': "R",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Emaoc\'h oc\'h implijout an dibarzh \'reproject\' war ar gwiskad ${layerName}. Dispredet eo an dibarzh-mañ : bet eo hag e talveze da ziskwel roadennoù war-c\'horre kartennoù diazez kenwerzhel, un dra hag a c\'haller ober bremañ gant an arc\'hwel dre skor banndres boullek Mercator. Muioc\'h a ditouroù a c\'haller da gaout war http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Dispredet eo an daore-se ha tennet e vo kuit eus ar stumm 3.0. Grit gant ${newMethod} e plas."

});
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["ca"]
 * Dictionary for Catalan, UTF8 encoding. Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang.ca = {

    'unhandledRequest': "Resposta a petició no gestionada ${statusText}",

    'Permalink': "Enllaç permanent",

    'Overlays': "Capes addicionals",

    'Base Layer': "Capa Base",

    'noFID': "No es pot actualitzar un element per al que no existeix FID.",

    'browserNotSupported':
        "El seu navegador no suporta renderització vectorial. Els renderitzadors suportats actualment són:\n${renderers}",

    // console message
    'minZoomLevelError':
        "La propietat minZoomLevel s'ha d'utilitzar només " +
        "amb les capes que tenen FixedZoomLevels. El fet que " +
        "una capa wfs comprovi minZoomLevel és una relíquia del " +
        "passat. No podem, però, eliminar-la sense trencar " +
        "les aplicacions d'OpenLayers que en puguin dependre. " +
        "Així doncs estem fent-la obsoleta -- la comprovació " +
        "minZoomLevel s'eliminarà a la versió 3.0. Feu servir " +
        "els paràmetres min/max resolution en substitució, tal com es descriu aquí: " +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transacció WFS: CORRECTA ${response}",

    'commitFailed': "Transacció WFS: HA FALLAT ${response}",

    'googleWarning':
        "La capa Google no s'ha pogut carregar correctament.<br><br>" +
        "Per evitar aquest missatge, seleccioneu una nova Capa Base " +
        "al gestor de capes de la cantonada superior dreta.<br><br>" +
        "Probablement això és degut a que l'script de la biblioteca de " +
    "Google Maps no ha estat inclòs a la vostra pàgina, o no " +
    "conté la clau de l'API correcta per a la vostra adreça.<br><br>" +
        "Desenvolupadors: Per obtenir consells sobre com fer anar això, " +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>féu clic aquí</a>",

    'getLayerWarning':
        "Per evitar aquest missatge, seleccioneu una nova Capa Base " +
        "al gestor de capes de la cantonada superior dreta.<br><br>" +
        "Probablement això és degut a que l'script de la biblioteca " +
        "${layerLib} " +
        "no ha estat inclòs a la vostra pàgina.<br><br>" +
        "Desenvolupadors: Per obtenir consells sobre com fer anar això, " +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>féu clic aquí</a>",

    'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",

    //labels for the graticule control
    'W': 'O',
    'E': 'E',
    'N': 'N',
    'S': 'S',
    'Graticule': 'Retícula',    
        
    // console message
    'reprojectDeprecated':
        "Esteu fent servir l'opció 'reproject' a la capa " +
        "${layerName}. Aquesta opció és obsoleta: el seu ús fou concebut " +
        "per suportar la visualització de dades sobre mapes base comercials, " + 
        "però ara aquesta funcionalitat s'hauria d'assolir mitjançant el suport " +
        "de la projecció Spherical Mercator. Més informació disponible a " +
        "http://trac.openlayers.org/wiki/SphericalMercator.",

    // console message
    'methodDeprecated':
        "Aquest mètode és obsolet i s'eliminarà a la versió 3.0. " +
        "Si us plau feu servir em mètode alternatiu ${newMethod}.",

    // **** end ****
    'end': ''

};
/* Translators (2009 onwards):
 *  - Mormegil
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["cs-CZ"]
 * Dictionary for Česky.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["cs-CZ"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Nezpracovaná návratová hodnota ${statusText}",

    'Permalink': "Trvalý odkaz",

    'Overlays': "Překryvné vrstvy",

    'Base Layer': "Podkladové vrstvy",

    'noFID': "Nelze aktualizovat prvek, pro který neexistuje FID.",

    'browserNotSupported': "Váš prohlížeč nepodporuje vykreslování vektorů. Momentálně podporované nástroje jsou::\n${renderers}",

    'minZoomLevelError': "Vlastnost minZoomLevel by se měla používat pouze s potomky FixedZoomLevels vrstvami. To znamená, že vrstva wfs kontroluje, zda-li minZoomLevel není zbytek z minulosti.Nelze to ovšem vyjmout bez možnosti, že bychom rozbili aplikace postavené na OL, které by na tom mohly záviset. Proto tuto vlastnost nedoporučujeme používat --  kontrola minZoomLevel bude odstraněna ve verzi 3.0. Použijte prosím raději nastavení min/max podle příkaldu popsaného na: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS Transaction: ÚSPĚCH ${response}",

    'commitFailed': "WFS Transaction: CHYBA ${response}",

    'googleWarning': "Nepodařilo se správně načíst vrstvu Google.\x3cbr\x3e\x3cbr\x3eAbyste se zbavili této zprávy, zvolte jinou základní vrstvu v přepínači vrstev.\x3cbr\x3e\x3cbr\x3eTo se většinou stává, pokud nebyl načten skript, nebo neobsahuje správný klíč pro API pro tuto stránku.\x3cbr\x3e\x3cbr\x3eVývojáři: Pro pomoc, aby tohle fungovalo , \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eklikněte sem\x3c/a\x3e",

    'getLayerWarning': "The ${layerType} Layer was unable to load correctly.\x3cbr\x3e\x3cbr\x3eTo get rid of this message, select a new BaseLayer in the layer switcher in the upper-right corner.\x3cbr\x3e\x3cbr\x3eMost likely, this is because the ${layerLib} library script was either not correctly included.\x3cbr\x3e\x3cbr\x3eDevelopers: For help getting this working correctly, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclick here\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Měřítko = 1 : ${scaleDenom}",

    'reprojectDeprecated': "Použil jste volbu \'reproject\' ve vrstvě ${layerName}. Tato volba není doporučená: byla zde proto, aby bylo možno zobrazovat data z okomerčních serverů, ale tato funkce je nyní zajištěna pomocí podpory Spherical Mercator. Více informací naleznete na http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Tato metoda je zavržená a bude ve verzi 3.0 odstraněna. Prosím, použijte raději ${newMethod}."

});
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["da-DK"]
 * Dictionary for Danish.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang['da-DK'] = {

    'unhandledRequest': "En ikke håndteret forespørgsel returnerede ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Kortlag",

    'Base Layer': "Baggrundslag",

    'noFID': "Kan ikke opdateret en feature (et objekt) der ikke har et FID.",

    'browserNotSupported':
        "Din browser understøtter ikke vektor visning. Følgende vektor visninger understøttes:\n${renderers}",

    // console message
    'minZoomLevelError':
        "Egenskaben minZoomLevel er kun beregnet til brug " +
        "med FixedZoomLevels. At dette WFS lag kontrollerer " +
        "minZoomLevel egenskaben, er et levn fra en tidligere " +
        "version. Vi kan desværre ikke fjerne dette uden at risikere " +
        "at ødelægge eksisterende OL baserede programmer der " +
        " benytter denne funktionalitet. " +
        "Egenskaben bør derfor ikke anvendes, og minZoomLevel " +
        "kontrollen herunder vil blive fjernet i version 3.0. " +
        "Benyt istedet min/max opløsnings indstillingerne, som " +
        "er beskrevet her: " +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS transaktion: LYKKEDES ${response}",

    'commitFailed': "WFS transaktion: MISLYKKEDES ${response}",

    'googleWarning':
        "Google laget kunne ikke indlæses.<br><br>" +
        "For at fjerne denne besked, vælg et nyt bagrundskort i " +
        "lagskifteren i øverste højre hjørne.<br><br>" +
        "Fejlen skyldes formentlig at Google Maps bibliotekts " +
        "scriptet ikke er inkluderet, eller ikke indeholder den " +
        "korrkte API nøgle for dit site.<br><br>" +
        "Udviklere: For hjælp til at få dette til at fungere, " +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>klik her</a>",

    'getLayerWarning':
        "${layerType}-laget kunne ikke indlæses.<br><br>" +
        "For at fjerne denne besked, vælg et nyt bagrundskort i " +
        "lagskifteren i øverste højre hjørne.<br><br>" +
        "Fejlen skyldes formentlig at ${layerLib} bibliotekts " +
        "scriptet ikke er inkluderet.<br><br>" +
        "Udviklere: For hjælp til at få dette til at fungere, " +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>klik her</a>",

    'Scale = 1 : ${scaleDenom}': "Målforhold = 1 : ${scaleDenom}",

    // console message
    'reprojectDeprecated':
        "Du anvender indstillingen 'reproject' på laget ${layerName}." + 
        "Denne indstilling bør ikke længere anvendes. Den var beregnet " +
        "til at vise data ovenpå kommercielle grundkort, men den funktionalitet " +
        "bør nu opnås ved at anvende Spherical Mercator understøttelsen. " +
        "Mere information er tilgængelig her: " +
        "http://trac.openlayers.org/wiki/SphericalMercator.",

    // console message
    'methodDeprecated':
        "Denne funktion bør ikke længere anvendes, og vil blive fjernet i version 3.0. " +
        "Anvend venligst funktionen ${newMethod} istedet."
};
/* Translators (2009 onwards):
 *  - Grille chompa
 *  - Nikiwaibel
 *  - Umherirrender
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["de"]
 * Dictionary for Deutsch.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["de"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Unbehandelte Anfragerückmeldung ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Overlays",

    'Base Layer': "Grundkarte",

    'noFID': "Ein Feature, für das keine FID existiert, kann nicht aktualisiert werden.",

    'browserNotSupported': "Ihr Browser unterstützt keine Vektordarstellung. Aktuell unterstützte Renderer:\n${renderers}",

    'minZoomLevelError': "Die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Eigenschaft ist nur für die Verwendung mit \x3ccode\x3eFixedZoomLevels\x3c/code\x3e-untergeordneten Layers vorgesehen. Das dieser \x3ctt\x3ewfs\x3c/tt\x3e-Layer die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Eigenschaft überprüft ist ein Relikt der Vergangenheit. Wir können diese Überprüfung nicht entfernen, ohne das OL basierende Applikationen nicht mehr funktionieren. Daher markieren wir es als veraltet - die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Überprüfung wird in Version 3.0 entfernt werden. Bitte verwenden Sie stattdessen die Min-/Max-Lösung, wie sie unter http://trac.openlayers.org/wiki/SettingZoomLevels beschrieben ist.",

    'commitSuccess': "WFS-Transaktion: Erfolgreich ${response}",

    'commitFailed': "WFS-Transaktion: Fehlgeschlagen ${response}",

    'googleWarning': "Der Google-Layer konnte nicht korrekt geladen werden.\x3cbr\x3e\x3cbr\x3eUm diese Meldung nicht mehr zu erhalten, wählen Sie einen anderen Hintergrundlayer aus dem LayerSwitcher in der rechten oberen Ecke.\x3cbr\x3e\x3cbr\x3eSehr wahrscheinlich tritt dieser Fehler auf, weil das Skript der Google-Maps-Bibliothek nicht eingebunden wurde oder keinen gültigen API-Schlüssel für Ihre URL enthält.\x3cbr\x3e\x3cbr\x3eEntwickler: Besuche \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3edas Wiki\x3c/a\x3e für Hilfe zum korrekten Einbinden des Google-Layers",

    'getLayerWarning': "Der ${layerType}-Layer konnte nicht korrekt geladen werden.\x3cbr\x3e\x3cbr\x3eUm diese Meldung nicht mehr zu erhalten, wählen Sie einen anderen Hintergrundlayer aus dem LayerSwitcher in der rechten oberen Ecke.\x3cbr\x3e\x3cbr\x3eSehr wahrscheinlich tritt dieser Fehler auf, weil das Skript der \'${layerLib}\'-Bibliothek nicht eingebunden wurde.\x3cbr\x3e\x3cbr\x3eEntwickler: Besuche \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3edas Wiki\x3c/a\x3e für Hilfe zum korrekten Einbinden von Layern",

    'Scale = 1 : ${scaleDenom}': "Maßstab = 1 : ${scaleDenom}",

    'W': "W",

    'E': "O",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Sie verwenden die „Reproject“-Option des Layers ${layerName}. Diese Option ist veraltet: Sie wurde entwickelt um die Anzeige von Daten auf kommerziellen Basiskarten zu unterstützen, aber diese Funktion sollte jetzt durch Unterstützung der „Spherical Mercator“ erreicht werden. Weitere Informationen sind unter http://trac.openlayers.org/wiki/SphericalMercator verfügbar.",

    'methodDeprecated': "Die Methode ist veraltet und wird in 3.0 entfernt. Bitte verwende stattdessen ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Omnipaedista
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["el"]
 * Dictionary for Ελληνικά.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["el"] = OpenLayers.Util.applyDefaults({

    'Scale = 1 : ${scaleDenom}': "Κλίμακα ~ 1 : ${scaleDenom}"

});
/**
 * @requires OpenLayers/Lang/en.js
 */

/**
 * Namespace: OpenLayers.Lang["en-CA"]
 * Dictionary for English-CA.  This dictionary inherits from the standard
 *     English dictionary.  Override only those entries with language specific
 *     to the CA region.
 *     
 * Keys for entries are used in calls to <OpenLayers.Lang.translate>.  Entry
 *     bodies are normal strings or strings formatted for use with
 *     <OpenLayers.String.format> calls.
 */
OpenLayers.Lang['en-CA'] = OpenLayers.Util.applyDefaults({

    // add any entries specific for this region here
    // e.g.
    // "someKey": "Some regionally specific value"
    
}, OpenLayers.Lang["en"]);
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["en"]
 * Dictionary for English.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang.en = {

    'unhandledRequest': "Unhandled request return ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Overlays",

    'Base Layer': "Base Layer",

    'noFID': "Can't update a feature for which there is no FID.",

    'browserNotSupported':
        "Your browser does not support vector rendering. Currently supported renderers are:\n${renderers}",

    // console message
    '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 = 1 : ${scaleDenom}': "Scale = 1 : ${scaleDenom}",
    
    //labels for the graticule control
    'W': 'W',
    'E': 'E',
    'N': 'N',
    'S': 'S',
    'Graticule': 'Graticule',

    // console message
    '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.",

    // console message
    'methodDeprecated':
        "This method has been deprecated and will be removed in 3.0. " +
        "Please use ${newMethod} instead.",

    // **** end ****
    'end': ''
    
};
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["es"]
 * Dictionary for Spanish, UTF8 encoding. Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang.es = {

    'unhandledRequest': "Respuesta a petición no gestionada ${statusText}",

    'Permalink': "Enlace permanente",

    'Overlays': "Capas superpuestas",

    'Base Layer': "Capa Base",

    'noFID': "No se puede actualizar un elemento para el que no existe FID.",

    'browserNotSupported':
        "Su navegador no soporta renderización vectorial. Los renderizadores soportados actualmente son:\n${renderers}",

    // console message
    'minZoomLevelError':
        "La propiedad minZoomLevel debe sólo utilizarse " +
        "con las capas que tienen FixedZoomLevels. El hecho de que " +
        "una capa wfs compruebe minZoomLevel es una reliquia del " +
        "pasado. Sin embargo, no podemos eliminarla sin discontinuar " +
        "probablemente las aplicaciones OL que puedan depender de ello. " +
        "Así pues estamos haciéndolo obsoleto --la comprobación " +
        "minZoomLevel se eliminará en la versión 3.0. Utilice el ajuste " +
        "de resolution min/max en su lugar, tal como se describe aquí: " +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transacción WFS: ÉXITO ${response}",

    'commitFailed': "Transacción WFS: FALLÓ ${response}",

    'googleWarning':
        "La capa Google no pudo ser cargada correctamente.<br><br>" +
        "Para evitar este mensaje, seleccione una nueva Capa Base " +
        "en el selector de capas en la esquina superior derecha.<br><br>" +
        "Probablemente, esto se debe a que el script de la biblioteca de " +
        "Google Maps no fue correctamente incluido en su página, o no " +
        "contiene la clave del API correcta para su sitio.<br><br>" +
        "Desarrolladores: Para ayudar a hacer funcionar esto correctamente, " +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>haga clic aquí</a>",

    'getLayerWarning':
        "La capa ${layerType} no pudo ser cargada correctamente.<br><br>" +
        "Para evitar este mensaje, seleccione una nueva Capa Base " +
        "en el selector de capas en la esquina superior derecha.<br><br>" +
        "Probablemente, esto se debe a que el script de " +
        "la biblioteca ${layerLib} " +
        "no fue correctamente incluido en su página.<br><br>" +
        "Desarrolladores: Para ayudar a hacer funcionar esto correctamente, " +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>haga clic aquí</a>",

    'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",

    //labels for the graticule control
    'W': 'O',
    'E': 'E',
    'N': 'N',
    'S': 'S',
    'Graticule': 'Retícula',
    
    // console message
    'reprojectDeprecated':
        "Está usando la opción 'reproject' en la capa " +
        "${layerName}. Esta opción es obsoleta: su uso fue diseñado " +
        "para soportar la visualización de datos sobre mapas base comerciales, " + 
        "pero ahora esa funcionalidad debería conseguirse mediante el soporte " +
        "de la proyección Spherical Mercator. Más información disponible en " +
        "http://trac.openlayers.org/wiki/SphericalMercator.",

    // console message
    'methodDeprecated':
        "Este método es obsoleto y se eliminará en la versión 3.0. " +
        "Por favor utilice el método ${newMethod} en su lugar.",

    // **** end ****
    'end': ''

};
/* Translators (2009 onwards):
 *  - Nike
 *  - Str4nd
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["fi"]
 * Dictionary for Suomi.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["fi"] = OpenLayers.Util.applyDefaults({

    'Permalink': "Ikilinkki",

    'Overlays': "Kerrokset",

    'Base Layer': "Peruskerros",

    'W': "L",

    'E': "I",

    'N': "P",

    'S': "E"

});
/* Translators (2009 onwards):
 *  - Damouns
 *  - IAlex
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["fr"]
 * Dictionary for Français.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["fr"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Requête non gérée, retournant ${statusText}",

    'Permalink': "Permalien",

    'Overlays': "Calques",

    'Base Layer': "Calque de base",

    'noFID': "Impossible de mettre à jour un objet sans identifiant (fid).",

    'browserNotSupported': "Votre navigateur ne supporte pas le rendu vectoriel. Les renderers actuellement supportés sont : \n${renderers}",

    '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.\x3cbr\x3e\x3cbr\x3ePour supprimer ce message, choisissez une nouvelle BaseLayer dans le sélecteur de couche en haut à droite.\x3cbr\x3e\x3cbr\x3eCela 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.\x3cbr\x3e\x3cbr\x3eDéveloppeurs : pour savoir comment corriger ceci, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ecliquez ici\x3c/a\x3e",

    'getLayerWarning': "La couche ${layerType} n\'est pas en mesure de se charger correctement.\x3cbr\x3e\x3cbr\x3ePour supprimer ce message, choisissez une nouvelle BaseLayer dans le sélecteur de couche en haut à droite.\x3cbr\x3e\x3cbr\x3eCela est possiblement causé par la non-inclusion de la librairie ${layerLib}.\x3cbr\x3e\x3cbr\x3eDéveloppeurs : pour savoir comment corriger ceci, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3ecliquez ici\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Echelle ~ 1 : ${scaleDenom}",

    'W': "O",

    'E': "E",

    'N': "N",

    'S': "S",

    '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.",

    'methodDeprecated': "Cette méthode est dépréciée, et sera supprimée à la version 3.0. Merci d\'utiliser ${newMethod} à la place."
});
/* Translators (2009 onwards):
 *  - Klenje
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["fur"]
 * Dictionary for Furlan.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["fur"] = OpenLayers.Util.applyDefaults({

    'Permalink': "Leam Permanent",

    'Overlays': "Livei parsore",

    'Base Layer': "Livel di base",

    'browserNotSupported': "Il to sgarfadôr nol supuarte la renderizazion vetoriâl. Al moment a son supuartâts:\n${renderers}",

    'Scale = 1 : ${scaleDenom}': "Scjale = 1 : ${scaleDenom}",

    'W': "O",

    'E': "E",

    'N': "N",

    'S': "S"

});
/* Translators (2009 onwards):
 *  - Toliño
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["gl"]
 * Dictionary for Galego.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["gl"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Solicitude non xerada; a resposta foi: ${statusText}",

    'Permalink': "Ligazón permanente",

    'Overlays': "Capas superpostas",

    'Base Layer': "Capa base",

    'noFID': "Non se pode actualizar a funcionalidade para a que non hai FID.",

    'browserNotSupported': "O seu navegador non soporta a renderización de vectores. Os renderizadores soportados actualmente son:\n${renderers}",

    'minZoomLevelError': "A propiedade minZoomLevel é só para uso conxuntamente coas capas FixedZoomLevels-descendent. O feito de que esa capa wfs verifique o minZoomLevel é unha reliquia do pasado. Non podemos, con todo, eliminala sen a posibilidade de non romper as aplicacións baseadas en OL que poidan depender dela. Por iso a estamos deixando obsoleta (a comprobación minZoomLevel de embaixo será eliminada na versión 3.0). Por favor, no canto diso use o axuste de resolución mín/máx tal e como está descrito aquí: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transacción WFS: ÉXITO ${response}",

    'commitFailed': "Transacción WFS: FALLIDA ${response}",

    'googleWarning': "A capa do Google non puido cargarse correctamente.\x3cbr\x3e\x3cbr\x3ePara evitar esta mensaxe, escolla unha nova capa base no seleccionador de capas na marxe superior dereita.\x3cbr\x3e\x3cbr\x3eProbablemente, isto acontece porque a escritura da libraría do Google Maps ou ben non foi incluída ou ben non contén a clave API correcta para o seu sitio.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: para axudar a facer funcionar isto correctamente, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3epremede aquí\x3c/a\x3e",

    'getLayerWarning': "A capa ${layerType} foi incapaz de cargarse correctamente.\x3cbr\x3e\x3cbr\x3ePara evitar esta mensaxe, escolla unha nova capa base no seleccionador de capas na marxe superior dereita.\x3cbr\x3e\x3cbr\x3eProbablemente, isto acontece porque a escritura da libraría ${layerLib} non foi ben incluída.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: para axudar a facer funcionar isto correctamente, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3epremede aquí\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",

    'W': "O",

    'E': "L",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Está usando a opción \"reproject\" na capa ${layerName}. Esta opción está obsoleta: o seu uso foi deseñado para a visualización de datos sobre mapas base comerciais, pero esta funcionalidade debera agora ser obtida utilizando a proxección Spherical Mercator. Hai dispoñible máis información en http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Este método está obsoleto e será eliminado na versión 3.0. Por favor, no canto deste use ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Als-Holder
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["gsw"]
 * Dictionary for Alemannisch.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["gsw"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Nit behandleti Aafrogsruckmäldig ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Iberlagerige",

    'Base Layer': "Grundcharte",

    'noFID': "E Feature, wu s kei FID derfir git, cha nit aktualisiert wäre.",

    'browserNotSupported': "Dyy Browser unterstitzt kei Vektordarstellig. Aktuäll unterstitzti Renderer:\n${renderers}",

    'minZoomLevelError': "D minZoomLevel-Eigeschaft isch nume dänk fir d Layer, wu vu dr FixedZoomLevels abstamme. Ass dää wfs-Layer minZoomLevel prieft, scih e Relikt us dr Vergangeheit. Mir chenne s aber nit ändere ohni OL_basierti Aawändige villicht kaputt gehn, wu dervu abhänge.  Us däm Grund het die Funktion d Eigeschaft \'deprecated\' iberchuu. D minZoomLevel-Priefig unte wird in dr Version 3.0 usegnuu. Bitte verwänd statt däm e min/max-Uflesig wie s do bschriben isch: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS-Transaktion: ERFOLGRYCH ${response}",

    'commitFailed': "WFS-Transaktion: FÄHLGSCHLAA ${response}",

    'googleWarning': "Dr Google-Layer het nit korräkt chenne glade wäre.\x3cbr\x3e\x3cbr\x3eGo die Mäldig nimi z kriege, wehl e andere Hintergrundlayer us em LayerSwitcher im rächte obere Ecke.\x3cbr\x3e\x3cbr\x3eDää Fähler git s seli hyfig, wel s Skript vu dr Google-Maps-Bibliothek nit yybunde woren isch oder wel s kei giltige API-Schlissel fir Dyy URL din het.\x3cbr\x3e\x3cbr\x3eEntwickler: Fir Hilf zum korräkte Yybinde vum Google-Layer \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3edoo drucke\x3c/a\x3e",

    'getLayerWarning': "Dr ${layerType}-Layer het nit korräkt chenne glade wäre.\x3cbr\x3e\x3cbr\x3eGo die Mäldig nimi z kriege, wehl e andere Hintergrundlayer us em LayerSwitcher im rächte obere Ecke.\x3cbr\x3e\x3cbr\x3eDää Fähler git s seli hyfig, wel s Skript vu dr \'${layerLib}\'-Bibliothek nit yybunde woren isch oder wel s kei giltige API-Schlissel fir Dyy URL din het.\x3cbr\x3e\x3cbr\x3eEntwickler: Fir Hilf zum korräkte Yybinde vu Layer \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3edoo drucke\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Maßstab = 1 : ${scaleDenom}",

    'W': "W",

    'E': "O",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Du bruchsch d \'reproject\'-Option bim ${layerName}-Layer. Die Option isch nimi giltig: si isch aagleit wore go   Date iber kommerziälli Grundcharte lege, aber des sott mer jetz mache mit dr Unterstitzig vu Spherical Mercator. Meh Informatione git s uf http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Die Methode isch veraltet un wird us dr Version 3.0 usegnuu. Bitte verwäbnd statt däm ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Mvrban
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["hr"]
 * Dictionary for Hrvatski.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["hr"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Nepodržani zahtjev ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Overlays",

    'Base Layer': "Osnovna karta",

    'noFID': "Ne mogu ažurirati značajku za koju ne postoji FID.",

    'browserNotSupported': "Vaš preglednik ne podržava vektorsko renderiranje. Trenutno podržani rendereri su: ${renderers}",

    'commitSuccess': "WFS Transakcija: USPJEŠNA ${response}",

    'commitFailed': "WFS Transakcija: NEUSPJEŠNA ${response}",

    'Scale = 1 : ${scaleDenom}': "Mjerilo = 1 : ${scaleDenom}",

    'methodDeprecated': "Ova metoda nije odobrena i biti će maknuta u 3.0. Koristite ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Michawiki
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["hsb"]
 * Dictionary for Hornjoserbsce.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["hsb"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Wotmołwa njewobdźěłaneho naprašowanja ${statusText}",

    'Permalink': "Trajny wotkaz",

    'Overlays': "Naworštowanja",

    'Base Layer': "Zakładna runina",

    'noFID': "Funkcija, za kotruž FID njeje, njeda so aktualizować.",

    'browserNotSupported': "Twój wobhladowak wektorowe rysowanje njepodpěruje. Tuchwilu podpěrowane rysowaki su:\n${renderers}",

    'minZoomLevelError': "Kajkosć minZoomLevel je jenož za wužiwanje z worštami myslena, kotrež wot FixedZoomLevels pochadźeja. Zo tuta woršta wfs za minZoomLevel přepruwuje, je relikt zańdźenosće. Njemóžemy wšak ju wotstronić, bjeztoho zo aplikacije, kotrež na OpenLayers bazěruja a snano tutu kajkosć wužiwaja, hižo njefunguja. Tohodla smy ju jako zestarjenu woznamjenili -- přepruwowanje za minZoomLevel budu so we wersiji 3.0 wotstronjeć. Prošu wužij město toho nastajenje min/max, kaž je tu wopisane: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS-Transakcija: WUSPĚŠNA ${response}",

    'commitFailed': "WFS-Transakcija: NJEPORADŹENA ${response}",

    'googleWarning': "Woršta Google njemóžeše so korektnje začitać.\x3cbr\x3e\x3cbr\x3eZo by tutu zdźělenku wotbył, wubjer nowy BaseLayer z wuběra worštow horjeka naprawo.\x3cbr\x3e\x3cbr\x3eNajskerje so to stawa, dokelž skript biblioteki Google Maps pak njebu zapřijaty pak njewobsahuje korektny kluč API za twoje sydło.\x3cbr\x3e\x3cbr\x3eWuwiwarjo: Za pomoc ke korektnemu fungowanju worštow\n\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3etu kliknyć\x3c/a\x3e",

    'getLayerWarning': "Woršta ${layerType} njemóžeše so korektnje začitać.\x3cbr\x3e\x3cbr\x3eZo by tutu zdźělenku wotbył, wubjer nowy BaseLayer z wuběra worštow horjeka naprawo.\x3cbr\x3e\x3cbr\x3eNajskerje so to stawa, dokelž skript biblioteki ${layerLib} njebu korektnje zapřijaty.\x3cbr\x3e\x3cbr\x3eWuwiwarjo: Za pomoc ke korektnemu fungowanju worštow\n\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3etu kliknyć\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Měritko = 1 : ${scaleDenom}",

    'W': "Z",

    'E': "W",

    'N': "S",

    'S': "J",

    'reprojectDeprecated': "Wužiwaš opciju \"reproject\" wořšty ${layerName}. Tuta opcija je zestarjena: jeje wužiwanje bě myslene, zo by zwobraznjenje datow nad komercielnymi bazowymi kartami podpěrało, ale funkcionalnosć měła so nětko z pomocu Sperical Mercator docpěć. Dalše informacije steja na http://trac.openlayers.org/wiki/SphericalMercator k dispoziciji.",

    'methodDeprecated': "Tuta metoda je so njeschwaliła a budźe so w 3.0 wotstronjeć. Prošu wužij ${newMethod} město toho."

});
/* Translators (2009 onwards):
 *  - City-busz
 *  - Glanthor Reviol
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["hu"]
 * Dictionary for Magyar.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["hu"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Nem kezelt kérés visszatérése ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Rávetítések",

    'Base Layer': "Alapréteg",

    'noFID': "Nem frissíthető olyan jellemző, amely nem rendelkezik FID-del.",

    'browserNotSupported': "A böngészője nem támogatja a vektoros renderelést. A jelenleg támogatott renderelők:\n${renderers}",

    'minZoomLevelError': "A minZoomLevel tulajdonságot csak a következővel való használatra szánták: FixedZoomLevels-leszármazott fóliák. Ez azt jelenti, hogy a minZoomLevel wfs fólia jelölőnégyzetei már a múlté. Mi azonban nem távolíthatjuk el annak a veszélye nélkül, hogy az esetlegesen ettől függő OL alapú alkalmazásokat tönkretennénk. Ezért ezt érvénytelenítjük -- a minZoomLevel az alul levő jelölőnégyzet a 3.0-s verzióból el lesz távolítva. Kérjük, helyette használja a  min/max felbontás beállítást, amelyről az alábbi helyen talál leírást: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS tranzakció: SIKERES ${response}",

    'commitFailed': "WFS tranzakció: SIKERTELEN ${response}",

    'googleWarning': "A Google fólia betöltése sikertelen.\x3cbr\x3e\x3cbr\x3eAhhoz, hogy ez az üzenet eltűnjön, válasszon egy új BaseLayer fóliát a jobb felső sarokban található fóliakapcsoló segítségével.\x3cbr\x3e\x3cbr\x3eNagy valószínűséggel ez azért van, mert a Google Maps könyvtár parancsfájlja nem található, vagy nem tartalmazza az Ön oldalához tartozó megfelelő API-kulcsot.\x3cbr\x3e\x3cbr\x3eFejlesztőknek: A helyes működtetésre vonatkozó segítség az alábbi helyen érhető el, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ekattintson ide\x3c/a\x3e",

    'getLayerWarning': "A(z) ${layerType} fólia nem töltődött be helyesen.\x3cbr\x3e\x3cbr\x3eAhhoz, hogy ez az üzenet eltűnjön, válasszon egy új BaseLayer fóliát a jobb felső sarokban található fóliakapcsoló segítségével.\x3cbr\x3e\x3cbr\x3eNagy valószínűséggel ez azért van, mert a(z) ${layerLib} könyvtár parancsfájlja helytelen.\x3cbr\x3e\x3cbr\x3eFejlesztőknek: A helyes működtetésre vonatkozó segítség az alábbi helyen érhető el, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3ekattintson ide\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Lépték = 1 : ${scaleDenom}",

    'W': "Ny",

    'E': "K",

    'N': "É",

    'S': "D",

    'reprojectDeprecated': "Ön a \'reproject\' beállítást használja a(z) ${layerName} fólián. Ez a beállítás érvénytelen: használata az üzleti alaptérképek fölötti adatok megjelenítésének támogatására szolgált, de ezt a funkció ezentúl a Gömbi Mercator használatával érhető el. További információ az alábbi helyen érhető el: http://trac.openlayers.org/wiki/SphericalMercator",

    'methodDeprecated': "Ez a módszer érvénytelenítve lett és a 3.0-s verzióból el lesz távolítva. Használja a(z) ${newMethod} módszert helyette."

});
/* Translators (2009 onwards):
 *  - McDutchie
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["ia"]
 * Dictionary for Interlingua.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["ia"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Le responsa a un requesta non esseva maneate: ${statusText}",

    'Permalink': "Permaligamine",

    'Overlays': "Superpositiones",

    'Base Layer': "Strato de base",

    'noFID': "Non pote actualisar un elemento sin FID.",

    'browserNotSupported': "Tu navigator non supporta le rendition de vectores. Le renditores actualmente supportate es:\n${renderers}",

    'minZoomLevelError': "Le proprietate minZoomLevel es solmente pro uso con le stratos descendente de FixedZoomLevels. Le facto que iste strato WFS verifica minZoomLevel es un reliquia del passato. Nonobstante, si nos lo remove immediatemente, nos pote rumper applicationes a base de OL que depende de illo. Ergo nos lo declara obsolete; le verification de minZoomLevel in basso essera removite in version 3.0. Per favor usa in su loco le configuration de resolutiones min/max como describite a: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transaction WFS: SUCCESSO ${response}",

    'commitFailed': "Transaction WFS: FALLEVA ${response}",

    'googleWarning': "Le strato Google non poteva esser cargate correctemente.\x3cbr\x3e\x3cbr\x3ePro disfacer te de iste message, selige un nove BaseLayer in le selector de strato in alto a dextra.\x3cbr\x3e\x3cbr\x3eMulto probabilemente, isto es proque le script del libreria de Google Maps non esseva includite o non contine le clave API correcte pro tu sito.\x3cbr\x3e\x3cbr\x3eDisveloppatores: Pro adjuta de corriger isto, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclicca hic\x3c/a",

    'getLayerWarning': "Le strato ${layerType} non poteva esser cargate correctemente.\x3cbr\x3e\x3cbr\x3ePro disfacer te de iste message, selige un nove BaseLayer in le selector de strato in alto a dextra.\x3cbr\x3e\x3cbr\x3eMulto probabilemente, isto es proque le script del libreria de ${layerLib} non esseva correctemente includite.\x3cbr\x3e\x3cbr\x3eDisveloppatores: Pro adjuta de corriger isto, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclicca hic\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Scala = 1 : ${scaleDenom}",

    'W': "W",

    'E': "E",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Tu usa le option \'reproject\' in le strato ${layerName} layer. Iste option es obsolescente: illo esseva pro poter monstrar datos super cartas de base commercial, ma iste functionalitate pote ora esser attingite con le uso de Spherical Mercator. Ulterior information es disponibile a http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Iste methodo ha essite declarate obsolescente e essera removite in version 3.0. Per favor usa ${newMethod} in su loco."

});
/* Translators (2009 onwards):
 *  - Irwangatot
 *  - IvanLanin
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["id"]
 * Dictionary for Bahasa Indonesia.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["id"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Permintaan yang tak tertangani menghasilkan ${statusText}",

    'Permalink': "Pranala permanen",

    'Overlays': "Hamparan",

    'Base Layer': "Lapisan Dasar",

    'noFID': "Tidak dapat memperbarui fitur yang tidak memiliki FID.",

    'browserNotSupported': "Peramban Anda tidak mendukung penggambaran vektor. Penggambar yang didukung saat ini adalah:\n${renderers}",

    'minZoomLevelError': "Properti minZoomLevel hanya ditujukan bekerja dengan lapisan FixedZoomLevels-descendent. Pengecekan minZoomLevel oleh lapisan wfs adalah peninggalan masa lalu. Kami tidak dapat menghapusnya tanpa kemungkinan merusak aplikasi berbasis OL yang mungkin bergantung padanya. Karenanya, kami menganggapnya tidak berlaku -- Cek minZoomLevel di bawah ini akan dihapus pada 3.0. Silakan gunakan penyetelan resolusi min/maks seperti dijabarkan di sini: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS Transaksi: BERHASIL ${respon}",

    'commitFailed': "WFS Transaksi: GAGAL ${respon}",

    'googleWarning': "Lapisan Google tidak dapat dimuat dengan benar.\x3cbr\x3e\x3cbr\x3eUntuk menghilangkan pesan ini, pilih suatu BaseLayer baru melalui penukar lapisan (layer switcher) di ujung kanan atas.\x3cbr\x3e\x3cbr\x3eKemungkinan besar ini karena pustaka skrip Google Maps tidak disertakan atau tidak mengandung kunci API yang tepat untuk situs Anda.\x3cbr\x3e\x3cbr\x3ePengembang: Untuk bantuan mengatasi masalah ini, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eklik di sini\x3c/a\x3e",

    'getLayerWarning': "Lapisan ${layerType} tidak dapat dimuat dengan benar.\x3cbr\x3e\x3cbr\x3eUntuk menghilangkan pesan ini, pilih suatu BaseLayer baru melalui penukar lapisan (layer switcher) di ujung kanan atas.\x3cbr\x3e\x3cbr\x3eKemungkinan besar ini karena pustaka skrip Google Maps tidak disertakan dengan benar.\x3cbr\x3e\x3cbr\x3ePengembang: Untuk bantuan mengatasi masalah ini, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklik di sini\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Sekala = 1 : ${scaleDenom}",

    'W': "B",

    'E': "T",

    'N': "U",

    'S': "S",

    'reprojectDeprecated': "Anda menggunakan opsi \'reproject\' pada lapisan ${layerName}. Opsi ini telah ditinggalkan: penggunaannya dirancang untuk mendukung tampilan data melalui peta dasar komersial, tapi fungsionalitas tersebut saat ini harus dilakukan dengan menggunakan dukungan Spherical Mercator. Informasi lebih lanjut tersedia di http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Metode ini telah usang dan akan dihapus di 3.0. Sebaliknya, harap gunakan ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Malafaya
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["io"]
 * Dictionary for Ido.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["io"] = OpenLayers.Util.applyDefaults({

    'Scale = 1 : ${scaleDenom}': "Skalo = 1 : ${scaleDenom}"

});
/* Translators (2009 onwards):
 *  - Ævar Arnfjörð Bjarmason
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["is"]
 * Dictionary for Íslenska.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["is"] = OpenLayers.Util.applyDefaults({

    'Permalink': "Varanlegur tengill",

    'Overlays': "Þekjur",

    'Base Layer': "Grunnlag",

    'Scale = 1 : ${scaleDenom}': "Skali = 1 : ${scaleDenom}",

    'methodDeprecated': "Þetta fall hefur verið úrelt og verður fjarlægt í 3.0. Notaðu ${newMethod} í staðin."

});
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["it"]
 * Dictionary for Italian.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang.it = {

    'unhandledRequest': "Codice di ritorno della richiesta ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Overlays",

    'Base Layer': "Livello base",

    'noFID': "Impossibile aggiornare un elemento grafico che non abbia il FID.",

    'browserNotSupported':
        "Il tuo browser non supporta il rendering vettoriale. I renderizzatore attualemnte supportati sono:\n${renderers}",

    // console message
    'minZoomLevelError':
        "La proprietà minZoomLevel è da utilizzare solamente " +
        "con livelli che abbiano FixedZoomLevels. Il fatto che " +
        "questo livello wfs controlli la proprietà minZoomLevel è " +
        "un retaggio del passato. Non possiamo comunque rimuoverla " +
        "senza rompere le vecchie applicazioni che dipendono su di essa." +
        "Quindi siamo costretti a deprecarla -- minZoomLevel " +
        "e sarà rimossa dalla vesione 3.0. Si prega di utilizzare i " +
        "settaggi di risoluzione min/max come descritto qui: " +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transazione WFS: SUCCESS ${response}",

    'commitFailed': "Transazione WFS: FAILED ${response}",

    'googleWarning':
        "Il livello Google non è riuscito a caricare correttamente.<br><br>" +
        "Per evitare questo messaggio, seleziona un nuovo BaseLayer " +
        "nel selettore di livelli nell'angolo in alto a destra.<br><br>" +
        "Più precisamente, ciò accade perchè la libreria Google Maps " +
        "non è stata inclusa nella pagina, oppure non contiene la " +
        "corretta API key per il tuo sito.<br><br>" +
        "Sviluppatori: Per aiuto su come farlo funzionare correttamente, " +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>clicca qui</a>",

    'getLayerWarning':
        "Il livello ${layerType} non è riuscito a caricare correttamente.<br><br>" +
        "Per evitare questo messaggio, seleziona un nuovo BaseLayer " +
        "nel selettore di livelli nell'angolo in alto a destra.<br><br>" +
        "Più precisamente, ciò accade perchè la libreria ${layerLib} " +
        "non è stata inclusa nella pagina.<br><br>" +
        "Sviluppatori: Per aiuto su come farlo funzionare correttamente, " +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>clicca qui</a>",

    'Scale = 1 : ${scaleDenom}': "Scala = 1 : ${scaleDenom}",

    // console message
    'reprojectDeprecated':
        "Stai utilizzando l'opzione 'reproject' sul livello ${layerName}. " +
        "Questa opzione è deprecata: il suo utilizzo è stato introdotto per" +
        "supportare il disegno dei dati sopra mappe commerciali, ma tale " + 
        "funzionalità dovrebbe essere ottenuta tramite l'utilizzo della proiezione " +
        "Spherical Mercator. Per maggiori informazioni consultare qui " +
        "http://trac.openlayers.org/wiki/SphericalMercator.",

    // console message
    'methodDeprecated':
        "Questo metodo è stato deprecato e sarà rimosso dalla versione 3.0. " +
        "Si prega di utilizzare il metodo ${newMethod} in alternativa.",

    'end': ''
};
/* Translators (2009 onwards):
 *  - Fryed-peach
 *  - Mage Whopper
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["ja"]
 * Dictionary for 日本語.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["ja"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "未処理の要求は ${statusText} を返します",

    'Permalink': "パーマリンク",

    'Overlays': "オーバーレイ",

    'Base Layer': "基底レイヤー",

    'noFID': "FID のない地物は更新できません。",

    'browserNotSupported': "あなたのブラウザはベクターグラフィックスの描写に対応していません。現時点で対応しているソフトウェアは以下のものです。\n${renderers}",

    'minZoomLevelError': "minZoomLevel プロパティは FixedZoomLevels を継承するレイヤーでの使用のみを想定しています。この minZoomLevel に対する WFS レイヤーの検査は歴史的なものです。しかしながら、この検査を除去するとそれに依存する OpenLayers ベースのアプリケーションを破壊してしまう可能性があります。よって廃止が予定されており、この minZoomLevel 検査はバージョン3.0で除去されます。代わりに、http://trac.openlayers.org/wiki/SettingZoomLevels で解説されている、最小および最大解像度設定を使用してください。",

    'commitSuccess': "WFS トランザクション: 成功 ${response}",

    'commitFailed': "WFS トランザクション: 失敗 ${response}",

    'googleWarning': "Google レイヤーが正しく読み込みを行えませんでした。\x3cbr\x3e\x3cbr\x3eこのメッセージを消すには、右上の隅にあるレイヤー切り替え部分で新しい基底レイヤーを選んでください。\x3cbr\x3e\x3cbr\x3eおそらく、これは Google マップ用ライブラリのスクリプトが組み込まれていないか、あなたのサイトに対応する正しい API キーが設定されていないためです。\x3cbr\x3e\x3cbr\x3e開発者の方へ: 正しい動作をさせるために\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eこちらのウィキ\x3c/a\x3eを参照してください。",

    'getLayerWarning': "${layerType} レイヤーが正しく読み込みを行えませんでした。\x3cbr\x3e\x3cbr\x3eこのメッセージを消すには、右上の隅にあるレイヤー切り替え部分で新しい基底レイヤーを選んでください。\x3cbr\x3e\x3cbr\x3eおそらく、これは ${layerLib} ライブラリのスクリプトが正しく組み込まれていないためです。\x3cbr\x3e\x3cbr\x3e開発者の方へ: 正しい動作をさせるために\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eこちらのウィキ\x3c/a\x3eを参照してください。",

    'Scale = 1 : ${scaleDenom}': "縮尺 = 1 : ${scaleDenom}",

    'W': "西",

    'E': "東",

    'N': "北",

    'S': "南",

    'reprojectDeprecated': "あなたは「${layerName}」レイヤーで reproject オプションを使っています。このオプションは商用の基底地図上に情報を表示する目的で設計されましたが、現在ではその機能は Spherical Mercator サポートを利用して実現されており、このオプションの使用は非推奨です。追加の情報は http://trac.openlayers.org/wiki/SphericalMercator で入手できます。",

    'methodDeprecated': "このメソッドは廃止が予定されており、バージョン3.0で除去されます。代わりに ${newMethod} を使用してください。"

});
/* Translators (2009 onwards):
 *  - វ័ណថារិទ្ធ
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["km"]
 * Dictionary for ភាសាខ្មែរ.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["km"] = OpenLayers.Util.applyDefaults({

    'Permalink': "តំណភ្ជាប់អចិន្ត្រៃយ៍",

    'Base Layer': "ស្រទាប់បាត​",

    'Scale = 1 : ${scaleDenom}': "មាត្រដ្ឋាន = ១ ៖ ${scaleDenom}"

});
/* Translators (2009 onwards):
 *  - Purodha
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["ksh"]
 * Dictionary for Ripoarisch.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["ksh"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Met dä Antwoot op en Aanfrooch ham_mer nix aanjefange: ${statusText}",

    'Permalink': "Lengk op Duuer",

    'Overlays': "Drövver jelaat",

    'Base Layer': "Jrund-Nivoh",

    'noFID': "En Saach, woh kein \x3ci lang=\"en\"\x3eFID\x3c/i\x3e för doh es, löht sesch nit ändere.",

    'browserNotSupported': "Dinge Brauser kann kein Väktore ußjävve. De Zoote Ußjaabe, di em Momang jon, sen:\n${renderers}",

    'minZoomLevelError': "De Eijeschaff „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“ es bloß doför jedaach, dat mer se met dä Nivvohß bruch, di vun \x3ccode lang=\"en\"\x3eFixedZoomLevels\x3c/code\x3e affhange don. Dat dat \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Nivvoh övverhoup de Eijeschaff „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“ pröhfe deiht, es noch övveresch vun fröhjer. Mer künne dat ävver jez nit fott lohße, oohne dat mer Jevaa loufe, dat Aanwendunge vun OpenLayers nit mieh loufe, di sesch doh velleijsch noch drop am verlohße sin. Dröm sare mer, dat mer et nit mieh han welle, un de „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“-Eijeschaff weed hee vun de Version 3.0 af nit mieh jeprööf wäde. Nemm doför de Enstellung för de hühßte un de kleinßte Oplöhsung, esu wi et en http://trac.openlayers.org/wiki/SettingZoomLevels opjeschrevve es.",

    'commitSuccess': "Dä \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Vörjang es joot jeloufe: ${response}",

    'commitFailed': "Dä \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Vörjang es scheif jejange: ${response}",

    'googleWarning': "Dat Nivvoh \x3ccode lang=\"en\"\x3eGoogle\x3c/code\x3e kunnt nit reschtesch jelaade wääde.\x3cbr /\x3e\x3cbr /\x3eÖm hee di Nohreesch loß ze krijje, donn en ander Jrund-Nivvoh ußsöhke, rähß bovve en de Äk.\x3cbr /\x3e\x3cbr /\x3eWascheinlesch es dat wiel dat \x3ci lang=\"en\"\x3eGoogle-Maps\x3c/i\x3e-Skrepp entweeder nit reschtesch enjebonge wood, udder nit dä reschtejje \x3ci lang=\"en\"\x3eAPI\x3c/i\x3e-Schlößel för Ding Web-ßait scheke deiht.\x3cbr /\x3e\x3cbr /\x3eFör Projrammierer jidd_et Hölp do_drövver, \x3ca href=\"http://trac.openlayers.org/wiki/Google\" target=\"_blank\"\x3ewi mer dat aan et Loufe brengk\x3c/a\x3e.",

    'getLayerWarning': "Dat Nivvoh \x3ccode\x3e${layerType}\x3c/code\x3e kunnt nit reschtesch jelaade wääde.\x3cbr /\x3e\x3cbr /\x3eÖm hee di Nohreesch loß ze krijje, donn en ander Jrund-Nivvoh ußsöhkre, rähß bovve en de Äk.\x3cbr /\x3e\x3cbr /\x3eWascheinlesch es dat, wiel dat Skrepp \x3ccode\x3e${layerLib}\x3c/code\x3e nit reschtesch enjebonge wood.\x3cbr /\x3e\x3cbr /\x3eFör Projrammierer jidd_Et Hölp do_drövver, \x3ca href=\"http://trac.openlayers.org/wiki/${layerLib}\" target=\"_blank\"\x3ewi mer dat aan et Loufe brengk\x3c/a\x3e.",

    'Scale = 1 : ${scaleDenom}': "Mohßshtaab = 1 : ${scaleDenom}",

    'W': "W",

    'E': "O",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Do bruchs de Ußwahl \x3ccode\x3ereproject\x3c/code\x3e op däm Nivvoh \x3ccode\x3e${layerName}\x3c/code\x3e. Di Ußwahl es nit mieh jähn jesinn. Se wohr doför jedaach, öm Date op jeschääfsmäßesch eruß jejovve Kaate bovve drop ze moole, wat ävver enzwesche besser met dä Öngershtözung för de ßfääresche Mäkaator Beldscher jeiht. Doh kanns De mieh drövver fenge op dä Sigg: http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Hee di Metood es nim_mih aktoäll un et weed se en dä Version 3.0 nit mieh jävve. Nemm \x3ccode\x3e${newMethod}\x3c/code\x3e doföör."

});
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["lt"]
 * Dictionary for Lithuanian.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang['lt'] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Neapdorota užklausa gražino ${statusText}",

    'Permalink': "Pastovi nuoroda",

    'Overlays': "Papildomi sluoksniai",

    'Base Layer': "Pagrindinis sluoksnis",

    'noFID': "Negaliu atnaujinti objekto, kuris neturi FID.",

    'browserNotSupported':
    "Jūsų naršyklė nemoka parodyti vektorių. Šiuo metu galima naudotis tokiais rodymo varikliais:\n{renderers}",

    'commitSuccess': "WFS Tranzakcija: PAVYKO ${response}",

    'commitFailed': "WFS Tranzakcija: ŽLUGO ${response}",

    'Scale = 1 : ${scaleDenom}': "Mastelis = 1 : ${scaleDenom}",
    
    //labels for the graticule control
    'W': 'V',
    'E': 'R',
    'N': 'Š',
    'S': 'P',
    'Graticule': 'Tinklelis',

    // console message
    'methodDeprecated':
    "Šis metodas yra pasenęs ir 3.0 versijoje bus pašalintas. " +
    "Prašome naudoti ${newMethod}.",

    // **** end ****
    'end': ''
    
});
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["nb"]
 * Dictionary for norwegian bokmål (Norway). Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["nb"] = {

    'unhandledRequest': "Ubehandlet forespørsel returnerte ${statusText}",

    'Permalink': "Kobling til denne siden",

    'Overlays': "Kartlag",

    'Base Layer': "Bakgrunnskart",

    'noFID': "Kan ikke oppdatere et feature (et objekt) som ikke har FID.",

    'browserNotSupported':
        "Din nettleser støtter ikke vektortegning. Tegnemetodene som støttes er:\n${renderers}",

    // console message
    'minZoomLevelError':
        "Egenskapen minZoomLevel er kun ment til bruk på lag " +
        "basert på FixedZoomLevels. At dette wfs-laget sjekker " +
        "minZoomLevel er en etterlevning fra tidligere versjoner. Det kan dog ikke " +
        "tas bort uten å risikere at OL-baserte applikasjoner " +
        "slutter å virke, så det er merket som foreldet: " +
        "minZoomLevel i sjekken nedenfor vil fjernes i 3.0. " +
        "Vennligst bruk innstillingene for min/maks oppløsning " +
        "som er beskrevet her: "+
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS-transaksjon: LYKTES ${response}",

    'commitFailed': "WFS-transaksjon: MISLYKTES ${response}",

    'googleWarning':
        "Google-laget kunne ikke lastes.<br><br>" +
        "Bytt til et annet bakgrunnslag i lagvelgeren i " +
        "øvre høyre hjørne for å slippe denne meldingen.<br><br>" +
        "Sannsynligvis forårsakes feilen av at Google Maps-biblioteket " +
        "ikke er riktig inkludert på nettsiden, eller at det ikke er " +
        "angitt riktig API-nøkkel for nettstedet.<br><br>" +
        "Utviklere: For hjelp til å få dette til å virke se "+
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>her</a>.",

    'getLayerWarning':
        "${layerType}-laget kunne ikke lastes.<br><br>" +
        "Bytt til et annet bakgrunnslag i lagvelgeren i " +
        "øvre høyre hjørne for å slippe denne meldingen.<br><br>" +
        "Sannsynligvis forårsakes feilen av at " +
        "${layerLib}-biblioteket ikke var riktig inkludert " +
        "på nettsiden.<br><br>" +
        "Utviklere: For hjelp til å få dette til å virke se " +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>her</a>.",

    'Scale = 1 : ${scaleDenom}': "<strong>Skala</strong> 1 : ${scaleDenom}",

    // console message
    'reprojectDeprecated':
        "Du bruker innstillingen 'reproject' på laget ${layerName}. " +
        "Denne innstillingen er foreldet, den var ment for å støtte " +
        "visning av kartdata over kommersielle bakgrunnskart, men det " +
        "bør nå gjøres med støtten for Spherical Mercator. Mer informasjon " +
        "finnes på http://trac.openlayers.org/wiki/SphericalMercator.",

    // console message
    'methodDeprecated':
        "Denne metoden er markert som foreldet og vil bli fjernet i 3.0. " +
        "Vennligst bruk ${newMethod} i stedet.",

    'end': ''
};

OpenLayers.Lang["no"] = OpenLayers.Lang["nb"];
/* Translators (2009 onwards):
 *  - Slomox
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["nds"]
 * Dictionary for Plattdüütsch.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["nds"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Unbehannelt Trüchmellels för de Anfraag ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Overlays",

    'Base Layer': "Achtergrundkoort",

    'noFID': "En Feature, dat keen FID hett, kann nich aktuell maakt warrn.",

    'browserNotSupported': "Dien Browser ünnerstütt keen Vektorbiller. Ünnerstütt Renderers:\n${renderers}",

    'commitSuccess': "WFS-Transakschoon: hett klappt ${response}",

    'commitFailed': "WFS-Transakschoon: hett nich klappt ${response}",

    'Scale = 1 : ${scaleDenom}': "Skaal = 1 : ${scaleDenom}",

    'methodDeprecated': "Disse Methood is oold un schall dat in 3.0 nich mehr geven. Bruuk dor man beter ${newMethod} för."

});
/* Translators (2009 onwards):
 *  - Siebrand
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["nl"]
 * Dictionary for Nederlands.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["nl"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Het verzoek is niet afgehandeld met de volgende melding: ${statusText}",

    'Permalink': "Permanente verwijzing",

    'Overlays': "Overlays",

    'Base Layer': "Achtergrondkaart",

    'noFID': "Een optie die geen FID heeft kan niet bijgewerkt worden.",

    'browserNotSupported': "Uw browser ondersteunt het weergeven van vectoren niet.\nMomenteel ondersteunde weergavemogelijkheden:\n${renderers}",

    'minZoomLevelError': "De eigenschap minZoomLevel is alleen bedoeld voor gebruik lagen met die afstammen van FixedZoomLevels-lagen.\nDat deze WFS-laag minZoomLevel controleert, is een overblijfsel uit het verleden.\nWe kunnen deze controle echter niet verwijderen zonder op OL gebaseerde applicaties die hervan afhankelijk zijn stuk te maken.\nDaarom heeft deze functionaliteit de eigenschap \'deprecated\' gekregen - de minZoomLevel wordt verwijderd in versie 3.0.\nGebruik in plaats van deze functie de mogelijkheid om min/max voor resolutie in te stellen zoals op de volgende pagina wordt beschreven:\nhttp://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS-transactie: succesvol ${response}",

    'commitFailed': "WFS-transactie: mislukt ${response}",

    'googleWarning': "De Google-Layer kon niet correct geladen worden.\x3cbr /\x3e\x3cbr /\x3e\nOm deze melding niet meer te krijgen, moet u een andere achtergrondkaart kiezen in de laagwisselaar in de rechterbovenhoek.\x3cbr /\x3e\x3cbr /\x3e\nDit komt waarschijnlijk doordat de bibliotheek ${layerLib} niet correct ingevoegd is.\x3cbr /\x3e\x3cbr /\x3e\nOntwikkelaars: \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklik hier\x3c/a\x3e om dit werkend te krijgen.",

    'getLayerWarning': "De laag ${layerType} kon niet goed geladen worden.\x3cbr /\x3e\x3cbr /\x3e\nOm deze melding niet meer te krijgen, moet u een andere achtergrondkaart kiezen in de laagwisselaar in de rechterbovenhoek.\x3cbr /\x3e\x3cbr /\x3e\nDit komt waarschijnlijk doordat de bibliotheek ${layerLib} niet correct is ingevoegd.\x3cbr /\x3e\x3cbr /\x3e\nOntwikkelaars: \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklik hier\x3c/a\x3e om dit werkend te krijgen.",

    'Scale = 1 : ${scaleDenom}': "Schaal = 1 : ${scaleDenom}",

    'W': "W",

    'E': "O",

    'N': "N",

    'S': "Z",

    'reprojectDeprecated': "U gebruikt de optie \'reproject\' op de laag ${layerName}.\nDeze optie is vervallen: deze optie was ontwikkeld om gegevens over commerciële basiskaarten weer te geven, maar deze functionaliteit wordt nu bereikt door ondersteuning van Spherical Mercator.\nMeer informatie is beschikbaar op http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Deze methode is verouderd en wordt verwijderd in versie 3.0.\nGebruik ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Harald Khan
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["nn"]
 * Dictionary for ‪Norsk (nynorsk)‬.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["nn"] = OpenLayers.Util.applyDefaults({

    'Scale = 1 : ${scaleDenom}': "Skala = 1 : ${scaleDenom}"

});
/* Translators (2009 onwards):
 *  - Cedric31
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["oc"]
 * Dictionary for Occitan.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["oc"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Requèsta pas gerida, retorna ${statusText}",

    'Permalink': "Permaligam",

    'Overlays': "Calques",

    'Base Layer': "Calc de basa",

    'noFID': "Impossible de metre a jorn un objècte sens identificant (fid).",

    'browserNotSupported': "Vòstre navegidor supòrta pas lo rendut vectorial. Los renderers actualament suportats son : \n${renderers}",

    'minZoomLevelError': "La proprietat minZoomLevel deu èsser utilizada solament per de jaces FixedZoomLevels-descendent. Lo fach qu\'aqueste jaç WFS verifique la preséncia de minZoomLevel es una relica del passat. Çaquelà, la podèm suprimir sens copar d\'aplicacions que ne poirián dependre. Es per aquò que la depreciam -- la verificacion del minZoomLevel serà suprimida en version 3.0. A la plaça, mercés d\'utilizar los paramètres de resolucions min/max tal coma descrich sus : http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transaccion WFS : SUCCES ${response}",

    'commitFailed': "Transaccion WFS : FRACAS ${response}",

    'googleWarning': "Lo jaç Google es pas estat en mesura de se cargar corrèctament.\x3cbr\x3e\x3cbr\x3ePer suprimir aqueste messatge, causissètz una BaseLayer novèla dins lo selector de jaç en naut a drecha.\x3cbr\x3e\x3cbr\x3eAquò es possiblament causat par la non-inclusion de la librariá Google Maps, o alara perque que la clau de l\'API correspond pas a vòstre site.\x3cbr\x3e\x3cbr\x3eDesvolopaires : per saber cossí corregir aquò, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclicatz aicí\x3c/a\x3e",

    'getLayerWarning': "Lo jaç ${layerType} es pas en mesura de se cargar corrèctament.\x3cbr\x3e\x3cbr\x3ePer suprimir aqueste messatge, causissètz una  BaseLayer novèla dins lo selector de jaç en naut a drecha.\x3cbr\x3e\x3cbr\x3eAquò es possiblament causat per la non-inclusion de la librariá ${layerLib}.\x3cbr\x3e\x3cbr\x3eDesvolopaires : per saber cossí corregir aquí, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclicatz aicí\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Escala ~ 1 : ${scaleDenom}",

    'W': "O",

    'E': "È",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Utilizatz l\'opcion \'reproject\' sul jaç ${layerName}. Aquesta opcion es despreciada : Son usatge permetiá d\'afichar de donadas al dessús de jaces raster comercials. Aquesta foncionalitat ara es suportada en utilizant lo supòrt de la projeccion Mercator Esferica. Mai d\'informacion es disponibla sus http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Aqueste metòde es despreciada, e serà suprimida a la version 3.0. Mercés d\'utilizar ${newMethod} a la plaça."

});
/* Translators:
 *  - Arkadiusz Grabka
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["pl"]
 * Dictionary for Polish.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["pl"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Nieobsługiwane żądanie zwróciło ${statusText}",

    'Permalink': "Permalink",

    'Overlays': "Nakładki",

    'Base Layer': "Warstwa podstawowa",

    'noFID': "Nie można zaktualizować funkcji, dla których nie ma FID.",

    'browserNotSupported':
        "Twoja przeglądarka nie obsługuje renderowania wektorów. Obecnie obsługiwane renderowanie to:\n${renderers}",

    // console message
    'minZoomLevelError':
        "Właściwość minZoomLevel jest przeznaczona tylko do użytku " +
        "z warstwami FixedZoomLevels-descendent." +
        "Warstwa wfs, która sprawdza minZoomLevel jest reliktem przeszłości." +
        "Nie możemy jej jednak usunąc bez mozliwości łamania OL aplikacji, " +
        "które mogą być od niej zależne. " +
        "Dlatego jesteśmy za deprecjację -- minZoomLevel " +
        "zostanie usunięta w wersji 3.0. W zamian prosze użyj " +
        "min/max rozdzielczości w sposób opisany tutaj: " +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transakcja WFS: SUKCES ${response}",

    'commitFailed': "Transakcja WFS: FAILED ${response}",

    'googleWarning':
        "Warstwa Google nie był w stanie załadować się poprawnie.<br><br>" +
        "Aby pozbyć się tej wiadomości, wybierz nową Warstwe podstawową " +
        "w przełączniku warstw w górnym prawym rogu mapy.<br><br>" +
        "Najprawdopodobniej jest to spowodowane tym, że biblioteka Google Maps " +
        "nie jest załadowana, lub nie zawiera poprawnego klucza do API dla twojej strony<br><br>" +
        "Programisto: Aby uzyskać pomoc , " +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>kliknij tutaj</a>",

    'getLayerWarning':
        "Warstwa ${layerType} nie mogła zostać załadowana poprawnie.<br><br>" +
        "Aby pozbyć się tej wiadomości, wybierz nową Warstwe podstawową " +
        "w przełączniku warstw w górnym prawym rogu mapy.<br><br>" +
        "Najprawdopodobniej jest to spowodowane tym, że biblioteka ${layerLib} " +
        "nie jest załadowana, lub może(o ile biblioteka tego wymaga) " +
        "byc potrzebny klucza do API dla twojej strony<br><br>" +
        "Programisto: Aby uzyskać pomoc , " +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>kliknij tutaj</a>",

    'Scale = 1 : ${scaleDenom}': "Skala = 1 : ${scaleDenom}",
    
    //labels for the graticule control
    'W': 'ZACH',
    'E': 'WSCH',
    'N': 'PN',
    'S': 'PD',
    'Graticule': 'Siatka',

    // console message
    'reprojectDeprecated':
        "w warstwie ${layerName} używasz opcji 'reproject'. " +
        "Ta opcja jest przestarzała: " +
        "jej zastosowanie został zaprojektowany, aby wspierać wyświetlania danych przez komercyjne mapy, "+
        "jednak obecnie ta funkcjonalność powinien zostać osiągnięty za pomocą Spherical Mercator " +
        "its use was designed to support displaying data over commercial. Więcje informacji na ten temat możesz znaleźć na stronie " + 
        "http://trac.openlayers.org/wiki/SphericalMercator.",

    // console message
    'methodDeprecated':
        "Ta metoda jest przestarzała i będzie usunięta od wersji 3.0. " +
        "W zamian użyj ${newMethod}."
});/* Translators (2009 onwards):
 *  - Luckas Blade
 *  - Rodrigo Avila
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["pt-br"]
 * Dictionary for Português do Brasil.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["pt-BR"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "A requisição retornou um erro não tratado: ${statusText}",

    'Permalink': "Link para essa página",

    'Overlays': "Camadas de Sobreposição",

    'Base Layer': "Camada Base",

    'noFID': "Não é possível atualizar uma feição que não tenha um FID.",

    'browserNotSupported': "Seu navegador não suporta renderização de vetores. Os renderizadores suportados atualmente são:\n${renderers}",

    'minZoomLevelError': "A propriedade minZoomLevel é de uso restrito das camadas descendentes de FixedZoomLevels. A verificação dessa propriedade pelas camadas wfs é um resíduo do passado. Não podemos, entretanto não é possível removê-la sem possívelmente quebrar o funcionamento de aplicações OL que possuem depência com ela. Portanto estamos tornando seu uso obsoleto -- a verificação desse atributo será removida na versão 3.0. Ao invés, use as opções de resolução min/max como descrito em: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transação WFS : SUCESSO ${response}",

    'commitFailed': "Transação WFS : ERRO ${response}",

    'googleWarning': "Não foi possível carregar a camada Google corretamente.\x3cbr\x3e\x3cbr\x3ePara se livrar dessa mensagem, selecione uma nova Camada Base, na ferramenta de alternação de camadas localização do canto superior direito.\x3cbr\x3e\x3cbr\x3eMuito provavelmente, isso foi causado porque o script da biblioteca do Google Maps não foi incluído, ou porque ele não contém a chave correta da API para o seu site.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: Para obter ajuda em solucionar esse problema \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ecliquem aqui\x3c/a\x3e",

    'getLayerWarning': "Não foi possível carregar a camada ${layerType} corretamente.\x3cbr\x3e\x3cbr\x3ePara se livrar dessa mensagem, selecione uma nova Camada Base, na ferramenta de alternação de camadas localização do canto superior direito.\x3cbr\x3e\x3cbr\x3eMuito provavelmente, isso foi causado porque o script da biblioteca ${layerLib} não foi incluído corretamente.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: Para obter ajuda em solucionar esse problema \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3ecliquem aqui\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",

    'W': "O",

    'E': "L",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Você está usando a opção \'reproject\' na camada ${layerName}. Essa opção está obsoleta: seu uso foi projetado para suportar a visualização de dados sobre bases de mapas comerciais, entretanto essa funcionalidade deve agora ser alcançada usando o suporte à projeção Mercator. Mais informação está disponível em: http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Esse método está obsoleto e será removido na versão 3.0. Ao invés, por favor use ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Hamilton Abreu
 *  - Malafaya
 *  - Waldir
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["pt"]
 * Dictionary for Português.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["pt"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Servidor devolveu erro não contemplado ${statusText}",

    'Permalink': "Ligação permanente",

    'Overlays': "Sobreposições",

    'Base Layer': "Camada Base",

    'noFID': "Não é possível atualizar um elemento para a qual não há FID.",

    'browserNotSupported': "O seu navegador não suporta renderização vetorial. Actualmente os renderizadores suportados são:\n${renderers}",

    'minZoomLevelError': "A propriedade minZoomLevel só deve ser usada com as camadas descendentes da FixedZoomLevels. A verificação da propriedade por esta camada wfs é uma relíquia do passado. No entanto, não podemos removê-la sem correr o risco de afectar aplicações OL que dependam dela. Portanto, estamos a torná-la obsoleta -- a verificação minZoomLevel será removida na versão 3.0. Em vez dela, por favor, use as opções de resolução min/max descritas aqui: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transacção WFS: SUCESSO ${response}",

    'commitFailed': "Transacção WFS: FALHOU ${response}",

    'googleWarning': "A Camada Google não foi correctamente carregada.\x3cbr\x3e\x3cbr\x3ePara deixar de receber esta mensagem, seleccione uma nova Camada-Base no \'\'switcher\'\' de camadas no canto superior direito.\x3cbr\x3e\x3cbr\x3eProvavelmente, isto acontece porque o \'\'script\'\' da biblioteca do Google Maps não foi incluído ou não contém a chave API correcta para o seu sítio.\x3cbr\x3e\x3cbr\x3eProgramadores: Para ajuda sobre como solucionar o problema \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclique aqui\x3c/a\x3e .",

    'getLayerWarning': "A camada ${layerType} não foi correctamente carregada.\x3cbr\x3e\x3cbr\x3ePara desactivar esta mensagem, seleccione uma nova Camada-Base no \'\'switcher\'\' de camadas no canto superior direito.\x3cbr\x3e\x3cbr\x3eProvavelmente, isto acontece porque o \'\'script\'\' da biblioteca ${layerLib} não foi incluído correctamente.\x3cbr\x3e\x3cbr\x3eProgramadores: Para ajuda sobre como solucionar o problema \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclique aqui\x3c/a\x3e .",

    'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",

    'W': "O",

    'E': "E",

    'N': "N",

    'S': "S",

    'reprojectDeprecated': "Está usando a opção \'reproject\' na camada ${layerName}. Esta opção é obsoleta: foi concebida para permitir a apresentação de dados sobre mapas-base comerciais, mas esta funcionalidade é agora suportada pelo Mercator Esférico. Mais informação está disponível em http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Este método foi declarado obsoleto e será removido na versão 3.0. Por favor, use ${newMethod} em vez disso."

});
/**
 * @requires OpenLayers/Lang.js
 */
/**
 * Namespace: OpenLayers.Lang["ro"]
 * Dictionary for Romanian.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
*/
OpenLayers.Lang.ro = {
    'unhandledRequest': "Cerere nesoluționată return ${statusText}",
    'Permalink': "Legatură permanentă",
    'Overlays': "Straturi vector",
    'Base Layer': "Straturi de bază",
    'noFID': "Nu pot actualiza un feature pentru care nu există FID.",
    'browserNotSupported':
        "Browserul tău nu suportă afișarea vectorilor. Supoetul curent pentru randare:\n${renderers}",
    // console message
    'minZoomLevelError':
        "Proprietatea minZoomLevel este doar pentru a fi folosită " +
        "cu straturile FixedZoomLevels-descendent. De aceea acest " +
        "strat wfs verifică dacă minZoomLevel este o relicvă" +
        ". Nu îl putem , oricum, înlătura fără " +
        "a afecta aplicațiile Openlayers care depind de ea." +
        " De aceea considerăm depreciat -- minZoomLevel " +
        "și îl vom înlătura în 3.0. Folosește " +
        "min/max resolution cum este descrisă aici: " +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",
    'commitSuccess': "Tranzacție WFS: SUCCES ${response}",
    'commitFailed': "Tranzacție WFS : EȘEC ${response}",
    'googleWarning':
        "Stratul Google nu a putut fi încărcat corect.<br><br>" +
        "Pentru a elimina acest mesaj, selectează un nou strat de bază " +
        "în Layerswitcher din colțul dreata-sus.<br><br>" +
        "Asta datorită, faptului că Google Maps library " +
        "script nu este inclus, sau nu conține " +
        "cheia API corectă pentru situl tău.<br><br>" +
        "Developeri: Pentru ajutor, " +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>apăsați aici</a>",
    'getLayerWarning':
        "Stratul ${layerType} nu a putut fi încărcat corect.<br><br>" +
        "pentru a înlătura acest mesaj, selectează un nou strat de bază " +
        "Acesta eroare apare de obicei când ${layerLib} library " +
        "script nu a fost încărcat corect.<br><br>" +
        "Developeri: Pentru ajutor privind utilizarea corectă, " +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>apasă aici</a>",
    'Scale = 1 : ${scaleDenom}': "Scara = 1 : ${scaleDenom}",
    //labels for the graticule control
    'W': 'V',
    'E': 'E',
    'N': 'N',
    'S': 'S',
    'Graticule': 'Graticule',
    // console message
    'reprojectDeprecated':
        "folosești opțiunea 'reproject' " +
        "pentru stratul ${layerName} . Această opțiune este depreciată: " +
        "a fost utilizată pentru afișarea straturilor de bază comerciale " + 
        "Mai multe informații despre proiecția Mercator sunt disponibile aici " +
        "http://trac.openlayers.org/wiki/SphericalMercator.",
    // console message
    'methodDeprecated':
        "Această metodă este depreciată și va fi înlăturată in versiunea 3.0. " +
        "folosește metoda ${newMethod}.",
    // **** end ****
    'end': ''
};
/* Translators (2009 onwards):
 *  - Ferrer
 *  - Komzpa
 *  - Lockal
 *  - Александр Сигачёв
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["ru"]
 * Dictionary for Русский.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["ru"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Необработанный запрос вернул ${statusText}",

    'Permalink': "Постоянная ссылка",

    'Overlays': "Слои",

    'Base Layer': "Основной слой",

    'noFID': "Невозможно обновить объект, для которого нет FID.",

    'browserNotSupported': "Ваш браузер не поддерживает векторную графику. На данный момент поддерживаются:\n${renderers}",

    'minZoomLevelError': "Свойство minZoomLevel предназначено только для использования со слоями, являющимися потомками FixedZoomLevels. То, что этот WFS-слой проверяется на minZoomLevel — реликт прошлого. Однако мы не можем удалить эту функцию, так как, возможно, от неё зависят некоторые основанные на OpenLayers приложения. Функция объявлена устаревшей — проверка minZoomLevel будет удалена в 3.0. Пожалуйста, используйте вместо неё настройку мин/макс разрешения, описанную здесь: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Транзакция WFS: УСПЕШНО ${response}",

    'commitFailed': "Транзакция WFS: ОШИБКА ${response}",

    'googleWarning': "Слой Google не удалось нормально загрузить.\x3cbr\x3e\x3cbr\x3eЧтобы избавиться от этого сообщения, выбите другой основной слой в переключателе в правом верхнем углу.\x3cbr\x3e\x3cbr\x3eСкорее всего, причина в том, что библиотека Google Maps не была включена или не содержит корректного API-ключа для вашего сайта.\x3cbr\x3e\x3cbr\x3eРазработчикам: чтобы узнать, как сделать, чтобы всё заработало, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eщёлкните тут\x3c/a\x3e",

    'getLayerWarning': "Слой ${layerType} не удалось нормально загрузить. \x3cbr\x3e\x3cbr\x3eЧтобы избавиться от этого сообщения, выбите другой основной слой в переключателе в правом верхнем углу.\x3cbr\x3e\x3cbr\x3eСкорее всего, причина в том, что библиотека ${layerLib} не была включена или была включена некорректно.\x3cbr\x3e\x3cbr\x3eРазработчикам: чтобы узнать, как сделать, чтобы всё заработало, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eщёлкните тут\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Масштаб = 1 : ${scaleDenom}",

    'W': "З",

    'E': "В",

    'N': "С",

    'S': "Ю",

    'reprojectDeprecated': "Вы используете опцию \'reproject\' для слоя ${layerName}. Эта опция является устаревшей: ее использование предполагалось для поддержки показа данных поверх коммерческих базовых карт, но теперь этот функционал несёт встроенная поддержка сферической проекции Меркатора. Больше сведений доступно на http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Этот метод считается устаревшим и будет удалён в версии 3.0. Пожалуйста, пользуйтесь ${newMethod}."

});
/* Translators (2009 onwards):
 *  - Helix84
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["sk"]
 * Dictionary for Slovenčina.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["sk"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Neobslúžené požiadavky vracajú ${statusText}",

    'Permalink': "Trvalý odkaz",

    'Overlays': "Prekrytia",

    'Base Layer': "Základná vrstva",

    'noFID': "Nie je možné aktualizovať vlastnosť, pre ktorú neexistuje FID.",

    'browserNotSupported': "Váš prehliadač nepodporuje vykresľovanie vektorov. Momentálne podporované vykresľovače sú:\n${renderers}",

    'minZoomLevelError': "Vlastnosť minZoomLevel je určený iba na použitie s vrstvami odvodenými od FixedZoomLevels. To, že táto wfs vrstva kontroluje minZoomLevel je pozostatok z minulosti. Nemôžeme ho však odstrániť, aby sme sa vyhli možnému porušeniu aplikácií založených na Open Layers, ktoré na tomto môže závisieť. Preto ho označujeme ako zavrhovaný - dolu uvedená kontrola minZoomLevel bude odstránená vo verzii 3.0. Použite prosím namiesto toho kontrolu min./max. rozlíšenia podľa tu uvedeného popisu: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Transakcia WFS: ÚSPEŠNÁ ${response}",

    'commitFailed': "Transakcia WFS: ZLYHALA ${response}",

    'googleWarning': "Vrstvu Google nebolo možné správne načítať.\x3cbr\x3e\x3cbr\x3eAby ste sa tejto správy zbavili vyberte novú BaseLayer v prepínači vrstiev v pravom hornom rohu.\x3cbr\x3e\x3cbr\x3eToto sa stalo pravdepodobne preto, že skript knižnice Google Maps buď nebol načítaný alebo neobsahuje správny kľúč API pre vašu lokalitu.\x3cbr\x3e\x3cbr\x3eVývojári: Tu môžete získať \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3epomoc so sfunkčnením\x3c/a\x3e",

    'getLayerWarning': "Vrstvu ${layerType} nebolo možné správne načítať.\x3cbr\x3e\x3cbr\x3eAby ste sa tejto správy zbavili vyberte novú BaseLayer v prepínači vrstiev v pravom hornom rohu.\x3cbr\x3e\x3cbr\x3eToto sa stalo pravdepodobne preto, že skript knižnice ${layerType} buď nebol načítaný alebo neobsahuje správny kľúč API pre vašu lokalitu.\x3cbr\x3e\x3cbr\x3eVývojári: Tu môžete získať \x3ca href=\'http://trac.openlayers.org/wiki/${layerType}\' target=\'_blank\'\x3epomoc so sfunkčnením\x3c/a\x3e",

    'Scale = 1 : ${scaleDenom}': "Mierka = 1 : ${scaleDenom}",

    'reprojectDeprecated': "Používate voľby „reproject“ vrstvy ${layerType}. Táto voľba je zzavrhovaná: jej použitie bolo navrhnuté na podporu zobrazovania údajov nad komerčnými základovými mapami, ale túto funkcionalitu je teraz možné dosiahnuť pomocou Spherical Mercator. Ďalšie informácie získate na stránke http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Táto metóda je zavrhovaná a bude odstránená vo verzii 3.0. Použite prosím namiesto nej metódu ${newMethod}."
});
/* Translators (2009 onwards):
 *  - Sannab
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["sv"]
 * Dictionary for Svenska.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["sv"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Ej hanterad fråga retur ${statusText}",

    'Permalink': "Permalänk",

    'Overlays': "Kartlager",

    'Base Layer': "Bakgrundskarta",

    'noFID': "Kan ej uppdatera feature (objekt) för vilket FID saknas.",

    'browserNotSupported': "Din webbläsare stöder inte vektorvisning. För närvarande stöds följande visning:\n${renderers}",

    'minZoomLevelError': "Egenskapen minZoomLevel är endast avsedd att användas med lager med FixedZoomLevels. Att detta WFS-lager kontrollerar minZoomLevel är en relik från äldre versioner. Vi kan dock inte ta bort det utan att riskera att OL-baserade tillämpningar som använder detta slutar fungera. Därför är det satt som deprecated, minZoomLevel kommer att tas bort i version 3.0. Använd i stället inställning av min/max resolution som beskrivs här: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS-transaktion: LYCKADES ${response}",

    'commitFailed': "WFS-transaktion: MISSLYCKADES ${response}",

    'googleWarning': "Google-lagret kunde inte laddas korrekt.\x3cbr\x3e\x3cbr\x3eFör att slippa detta meddelande, välj en annan bakgrundskarta i lagerväljaren i övre högra hörnet.\x3cbr\x3e\x3cbr\x3eSannolikt beror felet på att Google Maps-biblioteket inte är inkluderat på webbsidan eller på att sidan inte anger korrekt API-nyckel för webbplatsen.\x3cbr\x3e\x3cbr\x3eUtvecklare: hjälp för att åtgärda detta, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eklicka här\x3c/a\x3e.",

    'getLayerWarning': "${layerType}-lagret kunde inte laddas korrekt.\x3cbr\x3e\x3cbr\x3eFör att slippa detta meddelande, välj en annan bakgrundskarta i lagerväljaren i övre högra hörnet.\x3cbr\x3e\x3cbr\x3eSannolikt beror felet på att ${layerLib}-biblioteket inte är inkluderat på webbsidan.\x3cbr\x3e\x3cbr\x3eUtvecklare: hjälp för att åtgärda detta, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklicka här\x3c/a\x3e.",

    'Scale = 1 : ${scaleDenom}': "\x3cstrong\x3eSkala\x3c/strong\x3e 1 : ${scaleDenom}",

    'reprojectDeprecated': "Du använder inställningen \'reproject\' på lagret ${layerName}. Denna inställning markerad som deprecated: den var avsedd att användas för att stödja visning av kartdata på kommersiella bakgrundskartor, men nu bör man i stället använda Spherical Mercator-stöd för den funktionaliteten. Mer information finns på http://trac.openlayers.org/wiki/SphericalMercator.",

    'methodDeprecated': "Denna metod är markerad som deprecated och kommer att tas bort i 3.0. Använd ${newMethod} i stället."

});
/* Translators (2009 onwards):
 *  - Veeven
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["te"]
 * Dictionary for తెలుగు.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["te"] = OpenLayers.Util.applyDefaults({

    'Permalink': "స్థిరలింకు",

    'W': "ప",

    'E': "తూ",

    'N': "ఉ",

    'S': "ద"

});
/* Translators (2009 onwards):
 *  - Minh Nguyen
 */

/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["vi"]
 * Dictionary for Tiếng Việt.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["vi"] = OpenLayers.Util.applyDefaults({

    'unhandledRequest': "Không xử lý được phản hồi ${statusText} cho yêu cầu",

    'Permalink': "Liên kết thường trực",

    'Overlays': "Lấp bản đồ",

    'Base Layer': "Lớp nền",

    'noFID': "Không thể cập nhật tính năng thiếu FID.",

    'browserNotSupported': "Trình duyệt của bạn không hỗ trợ chức năng vẽ bằng vectơ. Hiện hỗ trợ các bộ kết xuất:\n${renderers}",

    'minZoomLevelError': "Chỉ nên sử dụng thuộc tính minZoomLevel với các lớp FixedZoomLevels-descendent. Việc lớp wfs này tìm cho minZoomLevel là di tích còn lại từ xưa. Tuy nhiên, nếu chúng tôi dời nó thì sẽ vỡ các chương trình OpenLayers mà dựa trên nó. Bởi vậy chúng tôi phản đối sử dụng nó\x26nbsp;– bước tìm cho minZoomLevel sẽ được dời vào phiên bản 3.0. Xin sử dụng thiết lập độ phân tích tối thiểu / tối đa thay thế, theo hướng dẫn này: http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "Giao dịch WFS: THÀNH CÔNG ${response}",

    'commitFailed': "Giao dịch WFS: THẤT BẠI ${response}",

    'googleWarning': "Không thể tải lớp Google đúng đắn.\x3cbr\x3e\x3cbr\x3eĐể tránh thông báo này lần sau, hãy chọn BaseLayer mới dùng điều khiển chọn lớp ở góc trên phải.\x3cbr\x3e\x3cbr\x3eChắc script thư viện Google Maps hoặc không được bao gồm hoặc không chứa khóa API hợp với website của bạn.\x3cbr\x3e\x3cbr\x3e\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eTrợ giúp về tính năng này\x3c/a\x3e cho người phát triển.",

    'getLayerWarning': "Không thể tải lớp ${layerType} đúng đắn.\x3cbr\x3e\x3cbr\x3eĐể tránh thông báo này lần sau, hãy chọn BaseLayer mới dùng điều khiển chọn lớp ở góc trên phải.\x3cbr\x3e\x3cbr\x3eChắc script thư viện ${layerLib} không được bao gồm đúng kiểu.\x3cbr\x3e\x3cbr\x3e\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eTrợ giúp về tính năng này\x3c/a\x3e cho người phát triển.",

    'Scale = 1 : ${scaleDenom}': "Tỷ lệ = 1 : ${scaleDenom}",

    'W': "T",

    'E': "Đ",

    'N': "B",

    'S': "N",

    'reprojectDeprecated': "Bạn đang áp dụng chế độ “reproject” vào lớp ${layerName}. Chế độ này đã bị phản đối: nó có mục đích hỗ trợ lấp dữ liệu trên các nền bản đồ thương mại; nên thực hiện hiệu ứng đó dùng tính năng Mercator Hình cầu. Có sẵn thêm chi tiết tại http://trac.openlayers.org/wiki/SphericalMercator .",

    'methodDeprecated': "Phương thức này đã bị phản đối và sẽ bị dời vào phiên bản 3.0. Xin hãy sử dụng ${newMethod} thay thế."

});
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["zh-CN"]
 * Dictionary for Simplified Chinese.  Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["zh-CN"] = {

    'unhandledRequest': "未处理的请求，返回值为 ${statusText}",

    'Permalink': "永久链接",

    'Overlays': "叠加层",

    'Base Layer': "基础图层",

    'noFID': "无法更新feature，缺少FID。",

    'browserNotSupported':
        "你使用的浏览器不支持矢量渲染。当前支持的渲染方式包括：\n${renderers}",

    // console message
    'minZoomLevelError':
        "minZoomLevel属性仅适合用于" +
        "使用了固定缩放级别的图层。这个 " +
        "wfs 图层检查 minZoomLevel 是过去遗留下来的。" +
        "然而，我们不能移除它，" +
        "而破坏依赖于它的基于OL的应用程序。" +
        "因此，我们废除了它 -- minZoomLevel " +
        "将会在3.0中被移除。请改用 " +
        "min/max resolution 设置，参考：" +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS Transaction: 成功。 ${response}",

    'commitFailed': "WFS Transaction: 失败。 ${response}",

    'googleWarning':
        "Google图层不能正确加载。<br><br>" +
        "要消除这个信息，请在右上角的" +
        "图层控制面板中选择其他的基础图层。<br><br>" +
        "这种情况很可能是没有正确的包含Google地图脚本库，" +
        "或者是没有包含在你的站点上" +
        "使用的正确的Google Maps API密匙。<br><br>" +
        "开发者：获取使其正确工作的帮助信息，" +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>点击这里</a>",

    'getLayerWarning':
        "${layerType} 图层不能正确加载。<br><br>" +
        "要消除这个信息，请在右上角的" +
        "图层控制面板中选择其他的基础图层。<br><br>" +
        "这种情况很可能是没有正确的包含" +
        "${layerLib} 脚本库。<br><br>" +
        "开发者：获取使其正确工作的帮助信息，" +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>点击这里</a>",

    'Scale = 1 : ${scaleDenom}': "比例尺 = 1 : ${scaleDenom}",

    // console message
    'reprojectDeprecated':
        "你正在使用 ${layerName} 图层上的'reproject'选项。" +
        "这个选项已经不再使用：" +
        "它是被设计用来支持显示商业的地图数据，" + 
        "不过现在该功能可以通过使用Spherical Mercator来实现。" +
        "更多信息可以参阅" +
        "http://trac.openlayers.org/wiki/SphericalMercator.",

    // console message
    'methodDeprecated':
        "该方法已经不再被支持，并且将在3.0中被移除。" +
        "请使用 ${newMethod} 方法来替代。",

    'end': ''
};
/**
 * @requires OpenLayers/Lang.js
 */

/**
 * Namespace: OpenLayers.Lang["zh-TW"]
 * Dictionary for Traditional Chinese. (Used Mainly in Taiwan) 
 * Keys for entries are used in calls to
 *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
 *     strings formatted for use with <OpenLayers.String.format> calls.
 */
OpenLayers.Lang["zh-TW"] = {

    'unhandledRequest': "未處理的請求，傳回值為 ${statusText}。",

    'Permalink': "永久連結",

    'Overlays': "額外圖層",

    'Base Layer': "基礎圖層",

    'noFID': "因為沒有 FID 所以無法更新 feature。",

    'browserNotSupported':
        "您的瀏覽器未支援向量渲染. 目前支援的渲染方式是:\n${renderers}",

    // console message
    'minZoomLevelError':
        "minZoomLevel 屬性僅適合用在 " +
        "FixedZoomLevels-descendent 類型的圖層. 這個" +
        "wfs layer 的 minZoomLevel 是過去所遺留下來的，" +
        "然而我們不能移除它而不讓它將" +
        "過去的程式相容性給破壞掉。" +
        "因此我們將會迴避使用它 -- minZoomLevel " +
        "會在3.0被移除，請改" +
        "用在這邊描述的 min/max resolution 設定: " +
        "http://trac.openlayers.org/wiki/SettingZoomLevels",

    'commitSuccess': "WFS Transaction: 成功 ${response}",

    'commitFailed': "WFS Transaction: 失敗 ${response}",

    'googleWarning':
        "The Google Layer 圖層無法被正確的載入。<br><br>" +
        "要迴避這個訊息, 請在右上角的圖層改變器裡，" +
        "選一個新的基礎圖層。<br><br>" +
        "很有可能是因為 Google Maps 的函式庫" +
        "腳本沒有被正確的置入，或沒有包含 " +
        "您網站上正確的 API key <br><br>" +
        "開發者: 要幫助這個行為正確完成，" +
        "<a href='http://trac.openlayers.org/wiki/Google' " +
        "target='_blank'>請按這裡</a>",

    'getLayerWarning':
        "${layerType} 圖層無法被正確的載入。<br><br>" +
        "要迴避這個訊息, 請在右上角的圖層改變器裡，" +
        "選一個新的基礎圖層。<br><br>" +
        "很有可能是因為 ${layerLib} 的函式庫" +
        "腳本沒有被正確的置入。<br><br>" +
        "開發者: 要幫助這個行為正確完成，" +
        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
        "target='_blank'>請按這裡</a>",

    'Scale = 1 : ${scaleDenom}': "Scale = 1 : ${scaleDenom}",

    // console message
    'reprojectDeprecated':
        "你正使用 'reproject' 這個選項 " +
        "在 ${layerName} 層。這個選項已經不再使用:" +
        "它的使用原本是設計用來支援在商業地圖上秀出資料，" + 
        "但這個功能已經被" +
        "Spherical Mercator所取代。更多的資訊可以在 " +
        "http://trac.openlayers.org/wiki/SphericalMercator 找到。",

    // console message
    'methodDeprecated':
        "這個方法已經不再使用且在3.0將會被移除，" +
        "請使用 ${newMethod} 來代替。",

    'end': ''
};
Fusion.configuration=/* This is the fusion configuration file.  Adjust as necessary. */
{
    /* The general section contains settings that are general to
       fusion as a whole. */
    "general": {
	    /* PHP is the default and only script language supported.
	       Don"t change this. */
	    "scriptLanguage": "php",

	    /* A directory on this system that fusion can use for
	       temporary files. */
	    "temporaryDirectory": "/ms4w/tmp"
    },
    /* The MapGuide section is required if you are installing
       fusion for MapGuide. */
    "mapguide": {
	    /* The WebTierURL is the url to mapguide, it should start
	       with http and end with /mapguide.  If you have installed
	       fusion inside the www directory of MapGuide, then you can
	       leave this empty as it will be automatically calculated. */
           "webTierUrl": "",
      /* for MapGuide OS version > 2.1 and MGE2010 and higher set this to true*/
          "useAsyncOverlay": true,
	    /* The tileCacheUrl is the root url to a static mapguide tile cache.
	       A relative URL will be relative to your application.
	       The root url will be appended with the 'Sn'
         directories for each zoom level. */
           "tileCacheUrl": "http://localhost:8008/sheboygan"
    },
    /* The MapServer section is required if you are installing fusion
       for MapServer. */
   "mapserver": {
	    /* The URL of the mapserver CGI that you want to use.  */
	    "cgi": "/cgi-bin/mapserv",
	    /* The file system path to where mapserver should put
	        web-accessible temporary images. */
	    "imagePath": "/ms4w/tmp/ms_tmp",
	    /* The URL to the image path configured above. */
	    "imageUrl": "http://localhost/ms_tmp",
	    /* The file system path to where mapserver should put
	        web-accessible temporary legend images. */
	    "legendIconCacheDir":"/ms4w/tmp/ms_tmp",
            /* The file  system path to where the maps are stored. */
        "mapFileRoot":"/ms4w/apps/",
        /*The file  system path where the saved map sessions are stored.*/
         "mapRestoreState":"/opt/fgs/apps/savedSession/",
        /*The file  system path where the php sessions are stored.*/
         "mapRestoreStateSession":"/opt/fgs/tmp/"
    }
}
/**
 * Fusion.Widget.About
 *
 * $Id: About.js 2585 2012-09-07 14:01:25Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/********************************************************************
* Class: Fusion.Widget.About
*
* About widget to display a pop-up window about the application.  The contents
* of the page are come from an HTML page set as the AboutUrl extension property.
*
* Inherits from:
*  - <Fusion.Widget>
* **********************************************************************/

Fusion.Widget.About = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    _nWidth: 500,
    _nHeight: 400,
    
    /* the url to open.  If specified, it is relative to the
     * application, not fusion
     */
     _sAboutUrl: null,
     
    /* the default url 
     */
    _sDefaultUrl: 'widgets/About/About.html',

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this._sAboutUrl = (json.AboutURL) ? json.AboutURL[0] : this._sDefaultUrl;
        
        if (this._sAboutUrl == this._sDefaultUrl) {
          this._sAboutUrl = Fusion.getFusionURL() + this._sAboutUrl;
        }
        
        this.enable();
    },

    /**
     * Function: activate
     *
     * opens a pop-up window with the about information when invoked
     * 
     */
    activate: function() {
      //console.log('About.execute');

      var sFeatures = 'menubar=no,location=no,resizable=no,status=no';
      sFeatures += ',width=' + this._nWidth;
      sFeatures += ',height=' + this._nHeight;
      window.open(this._sAboutUrl, 'AboutPopup', sFeatures);
    }
});
/**
 * Fusion.Widget.ActivityIndicator
 *
 * $Id: ActivityIndicator.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/*****************************************************************************
 * Class: Fusion.Widget.ActivityIndicator
 *
 * AcitivityIndicator is a widget that shows or hides its DOM element
 * based on whether the map widget is busy or not.  The map widget
 * becomes busy when loading maps, reloading maps or redrawing
 * layers.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **************************************************************************/


Fusion.Widget.ActivityIndicator = OpenLayers.Class(Fusion.Widget, {
    element: null,
    initializeWidget: function(widgetTag) {
        this.element = this.domObj;
        var json = widgetTag.extension;
        if (json.ElementId) {
            var elm = $(json.ElementId[0]);
            if (elm && elm != this.domObj) {
                this.domObj.appendChild(elm);
                this.element = elm;
            }
        }
        this.element.style.visibility = 'hidden';
        this.getMap().registerForEvent(Fusion.Event.MAP_BUSY_CHANGED, 
                              OpenLayers.Function.bind(this.busyChanged, this));
    },
    
    busyChanged: function() {
        this.element.style.visibility = this.getMap().isBusy() ? 'visible' : 'hidden';
    }
});
/**
 * Fusion.Widget.AddWMSLayer
 *
 * $Id: Print.js 1906 2009-09-23 22:07:49Z chrisclaydon $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.AddWMSLayer
 *
 * Opens a dialog box with a list of layers from a WMS server that can be added
 * to the map.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.AddWMSLayer = OpenLayers.Class(Fusion.Widget, {
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        this.template = json.Template ? json.Template[0] : '<li id="{id}" class="jxListItemContainer"><a class="jxListItem" href="javascript::void(0)" alt="{layerTitle}" title="{layerTitle}">{layerTitle}</a></li>';
        this.serviceURL = json.ServiceURL ? json.ServiceURL : [];
        
        this.dialogContentURL = Fusion.getFusionURL() + widgetTag.location + 'AddWMSLayer/AddWMSLayer.html';
        this.addWMSLayerURL = widgetTag.location + 'AddWMSLayer/AddWMSLayer.php';
        Fusion.addWidgetStyleSheet(widgetTag.location + 'AddWMSLayer/AddWMSLayer.css');
        
        this.draw();
    },
    
    draw: function() {
      var form = new Jx.Form({
        name: 'addWMSLayer',
        formClass: 'jxFormBlock'
      }).addTo(this.domObj);
      
    // First Fieldset
    
      var fieldSet1 = new Jx.Fieldset({
        legend: 'Show WMS layers from:',
        id: 'serviceURLFieldset',
        fieldsetClass: 'jxFormInlineblock'
      }).addTo(form);
      
      var serviceURLs = [];
      for (var i=0; i<this.serviceURL.length; ++i) {
        serviceURLs.push({label: this.serviceURL[i]}); 
      }
      this.serviceList = new Jx.Field.Combo({
        id: 'serviceURL',
        name: 'serviceURL',
        label: 'Server URL',
        readonly: false,
        items: serviceURLs
      }).addTo(fieldSet1);
    
      var button = new Jx.Field.Button({
        buttonOptions:{
          id: 'listButton',
          name: 'listButton',
          label: 'List Layers',
          onClick: function() {
            console.log('Go!');
          }
        },
        defaultAction: true
      }).addTo(fieldSet1);
      button.addEvent('click', OpenLayers.Function.bind(this.initializeWMS, this));
      
      this.outputDiv = new Element('div', {
          id: 'layerList',
          'class': 'layerList'
      });
      this.domObj.adopt(this.outputDiv);
    },
    
    initializeWMS: function() {
        this.listLayersWait();
        //prep the server URL to remove WMS params
        var serverURL = this.serviceList.getValue();
        if (serverURL.length >0) {
          var newParams = [];
          var urlParams = serverURL.split('?')
          if (urlParams.length > 1) {
            var params = urlParams[1].split('&');
            for (var j=0; j<params.length; ++j) {
              if (params[j].toLowerCase().indexOf('request')!=-1) continue;
              if (params[j].toLowerCase().indexOf('version')!=-1) continue;
              newParams.push(params[j]);
            }
            urlParams[1] = newParams.join('&');
          }
          serverURL = urlParams.join('?');
          
          var map = this.getMapLayer(); 
          var opts = {
              parameters: {
                  session: map.getSessionID(),
                  mapname: map._sMapname,
                  action: 'listLayersWMS',
                  server: serverURL
              }, 
              onComplete: OpenLayers.Function.bind(this.wmsListLayers, this)
          };
          Fusion.ajaxRequest(this.addWMSLayerURL, opts);
        }
    },
    
/* 
function catalogListLayers - CB from catalogManagerInitialize() with object create the html
                             required to add the layers to the map. clicking on image spawns
                             addCatalogLayer which inturn add's the clicked layer to the map.
*/    
    wmsListLayers: function(r) {
      if (r.responseText) {
        var gCatalogLayersObj;
        try {
          gCatalogLayersObj = Fusion.parseJSON(r.responseText);
        } catch (e) {
          gCatalogLayersObj = {'error': e.stack};
        }
        if (gCatalogLayersObj) {
        
          this.outputDiv.set('html','');
            
          if (gCatalogLayersObj.error) {
            this.outputDiv.set('html', gCatalogLayersObj.error + '<br>' + gCatalogLayersObj.message);
            return;
          }
            
          var layerList = new Jx.ListView({
            'id': 'catalogListLayerUL'
          }).addTo(this.outputDiv); 
          
          for(var i=0;i<gCatalogLayersObj.length;i++){
              var szOwsTitle = gCatalogLayersObj[i].owstitle;
              if (szOwsTitle.length < 1) {
                szOwsTitle = gCatalogLayersObj[i].name;
              }
              
              var templStr = this.template.substitute({
                  'id': gCatalogLayersObj[i].name,
                  'layerTitle':szOwsTitle
              });
              var listItem = new Jx.ListItem({template: templStr });
              listItem.addTo(layerList);
              OpenLayers.Event.observe(listItem.domObj, 'click', OpenLayers.Function.bind(this.addWMSLayer, this, gCatalogLayersObj[i]));
          }
        }
      }
    },
    
    listLayersWait: function() {
        this.outputDiv.set('html', 'Request in progress...');
    },
    
    /* 
    function addWMSLayer - adds the clicked layer from the interface created by catalogListLayers
                               then loads browseCatalog.php to add the clicked layer to the current 
                               session map file. calls addCatalogLayerCB for a return response.
    
    */    
    addWMSLayer: function(cb){
        var map = this.getMapLayer();
        var supportedSRS = cb.srs.toUpperCase();
        var sourceSrs = map.mapWidget.oMapOL.baseLayer.projection.projCode;  //first try the code of the base map
        if (supportedSRS.indexOf(sourceSrs) < 0 ) {
          sourceSrs = "EPSG:4326";  //default to use 4326
          if (supportedSRS.indexOf(sourceSrs) < 0 ) {
            sourceSrs = supportedSRS.split(" ").shift();//just pick the first one
          }
        }
        
        //prep the servername to remove existing WMS params
        var params = {
            session: map.getSessionID(),
            mapname: map._sMapname,
            action: 'addLayer',
            layertype: cb.layertype,
            layername: cb.name,
            group: cb.group,
            owstitle: cb.owstitle,
            srs: sourceSrs,
            imageFormat: cb.imageformat,
            servername: cb.servername,
            wmsservicetitle: cb.wmsservicetitle,
            queryable: cb.queryable,
            metadataurl: cb.metadataurl ? cb.metadataurl : ''
        }
        
        // switch image to a different src.
        //cb.src = 'images/icons/legend-layer.png';
        var opts = {parameters: params, onComplete: OpenLayers.Function.bind(this.addWMSLayerCB, this)};
        Fusion.ajaxRequest(this.addWMSLayerURL, opts);
    },
    
    /* 
    function addWMSLayerCB - CB func from addWMSLayer. The Layer is now added to mapfile, 
                                 if o.addedLayer = true else something when wrong.
    */    
    addWMSLayerCB: function(r) {
        var o = Fusion.parseJSON(r.responseText);    

        if(o.addedLayer == true){
          var map = this.oMap; 
          map.triggerEvent(Fusion.Event.WMS_LAYER_ADDED, new Array(o));
          this.getMapLayer().reloadMap();
        } else {
           // d.log('addCatalogLayerCB:could not add layer');
        }
    }
    
});
/**
 * Fusion.Widget.BasemapSwitcher
 *
 * $Id: BasemapSwitcher.js 
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.BasemapSwitcher
 *
 * A widget to allow selection of the basemaps display under the overlay MapGuide layer
 * Currently, Google Street, Google Satellite, Google Hybrid, Yahoo Street, Yahoo Satellite,
 * Yahoo Hybrid, OpenStreetMap Mapnik, OpenStreetMap Cycle, OpenStreetMap Transport,
 * Bing Street, Bing Satellite and Bing Hybrid is supported. 
 * 
 *  Inherits from:
 *  - <Fusion.Widget>
 ****************************************************************************/

Fusion.Widget.BasemapSwitcher = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Menu,

    options: {},

    baseMaps: {},

    defaultBasemap: null,

    activeBasemap: null,

    menuItems: {},
    
    zoomOffsets: {
        'G_NORMAL_MAP': 0,
        'G_SATELLITE_MAP': 0,
        'G_HYBRID_MAP': 0,
        'G_PHYSICAL_MAP': 0,
        'YAHOO_MAP_REG': 0,
        'YAHOO_MAP_SAT': 0,
        'YAHOO_MAP_HYB': 0,
        'Road': -1,
        'Aerial': -1,
        'Hybrid': -1,
        'Mapnik': 0,
        'TransportMap': 0,
        'CycleMap': 0,
        'None': 0
    },

    initializeWidget: function(widgetTag) {
        this.getMap().registerForEvent(Fusion.Event.MAP_MAP_GROUP_LOADED, OpenLayers.Function.bind(this.setDefaultBasemap, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.checkZoomOffsets, this));
    },

    refreshSettings: function() {
        this.baseMaps = {};
        this.defaultBasemap = null;
        this.menuItems = {};
        this.options = {
            'G_NORMAL_MAP': null,
            'G_SATELLITE_MAP': null,
            'G_HYBRID_MAP': null,
            'G_PHYSICAL_MAP': null,
            'YAHOO_MAP_REG': null,
            'YAHOO_MAP_SAT': null,
            'YAHOO_MAP_HYB': null,
            'Road': null,
            'Aerial': null,
            'Hybrid': null,
            'Mapnik': null,
            'TransportMap': null,
            'CycleMap': null,
            'None': null
        };
    },

    generateOptions: function() {
        // Clear previous settings 
        this.refreshSettings();

        var maps = this.getMap().aMaps;
        for (var i = 0, len = maps.length; i < len; i++) {
            var map = maps[i];
            switch (map.layerType) {
                case 'MapGuide':
                    this.options['None'] = OpenLayers.i18n('noCommercialMapLayers');
                    this.baseMaps['None'] = map;
                    break;
                case 'Google':
                    // if user didn't indicate basemap types, use the default Google Street
                    if (!map.mapTag.extension.Options || !map.mapTag.extension.Options[0].type) {
                        this.options['G_NORMAL_MAP'] = OpenLayers.i18n('googleStreet');
                        this.baseMaps['G_NORMAL_MAP'] = map;

                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = "G_NORMAL_MAP";
                        }
                    }
                    else {
                        switch (map.mapTag.extension.Options[0].type[0]) {
                            case 'G_NORMAL_MAP':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['G_NORMAL_MAP'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['G_NORMAL_MAP'] = OpenLayers.i18n('googleStreet');
                                }
                                this.baseMaps['G_NORMAL_MAP'] = map;
                                break;
                            case 'G_SATELLITE_MAP':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['G_SATELLITE_MAP'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['G_SATELLITE_MAP'] = OpenLayers.i18n('googleSatellite');
                                }
                                this.baseMaps['G_SATELLITE_MAP'] = map;
                                break;
                            case 'G_HYBRID_MAP':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['G_HYBRID_MAP'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['G_HYBRID_MAP'] = OpenLayers.i18n('googleHybrid');
                                }
                                this.baseMaps['G_HYBRID_MAP'] = map;
                                break;
                            case 'G_PHYSICAL_MAP':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['G_PHYSICAL_MAP'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['G_PHYSICAL_MAP'] = OpenLayers.i18n('googleTerrain');
                                }
                                this.baseMaps['G_PHYSICAL_MAP'] = map;
                                break;
                            default:
                                break;
                        }

                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = map.mapTag.extension.Options[0].type[0];
                        }
                    }
                    break;

                case 'Yahoo':
                    // if user didn't indicate basemap types, use the default Yahoo Street
                    if (!map.mapTag.extension.Options || !map.mapTag.extension.Options[0].type) {
                        this.options['YAHOO_MAP_REG'] = OpenLayers.i18n('yahooStreet');
                        this.baseMaps['YAHOO_MAP_REG'] = map;

                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = "YAHOO_MAP_REG";
                        }
                    }
                    else {
                        switch (map.mapTag.extension.Options[0].type[0]) {
                            case 'YAHOO_MAP_REG':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['YAHOO_MAP_REG'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['YAHOO_MAP_REG'] = "Yahoo Street";
                                }
                                this.baseMaps['YAHOO_MAP_REG'] = map;
                                break;
                            case 'YAHOO_MAP_SAT':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['YAHOO_MAP_SAT'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['YAHOO_MAP_SAT'] = OpenLayers.i18n('yahooStreet');
                                }
                                this.baseMaps['YAHOO_MAP_SAT'] = map;
                                break;
                            case 'YAHOO_MAP_HYB':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['YAHOO_MAP_HYB'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['YAHOO_MAP_HYB'] = OpenLayers.i18n('yahooHybrid');
                                }
                                this.baseMaps['YAHOO_MAP_HYB'] = map;
                                break;
                            default:
                                break;
                        }
                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = map.mapTag.extension.Options[0].type[0];
                        }
                    }
                    break;
                case 'VirtualEarth':
                    // if user didn't indicate basemap types, use the default Bing Street
                    if (!map.mapTag.extension.Options || !map.mapTag.extension.Options[0].type) {
                        this.options['Road'] = OpenLayers.i18n('bingStreet');
                        this.baseMaps['Road'] = map;
                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = "Road";
                        }
                    }
                    else {

                        switch (map.mapTag.extension.Options[0].type[0]) {
                            case 'Road':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['Road'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['Road'] = OpenLayers.i18n('bingStreet');
                                }
                                this.baseMaps['Road'] = map;
                                break;
                            case 'Aerial':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['Aerial'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['Aerial'] = OpenLayers.i18n('bingSatellite');
                                }
                                this.baseMaps['Aerial'] = map;
                                break;
                            case 'Hybrid':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['Hybrid'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['Hybrid'] = OpenLayers.i18n('bingHybrid');
                                }
                                this.baseMaps['Hybrid'] = map;
                                break;
                            default:
                                break;
                        }
                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = map.mapTag.extension.Options[0].type[0];
                        }
                    }
                    break;
                case 'OpenStreetMap':
                    // if user didn't indicate basemap types, use the default OSM Mapnik render
                    if (!map.mapTag.extension.Options || !map.mapTag.extension.Options[0].type) {
                        this.options['Mapnik'] = OpenLayers.i18n('openStreetMap');
                        this.baseMaps['Mapnik'] = map;
                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = "Mapnik";
                        }
                    }
                    else {

                        switch (map.mapTag.extension.Options[0].type[0]) {
                            case 'TransportMap':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['TransportMap'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['TransportMap'] = OpenLayers.i18n('openStreetMapTransportMap');
                                }
                                this.baseMaps['TransportMap'] = map;
                                break;
                            case 'CycleMap':
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['CycleMap'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['CycleMap'] =  OpenLayers.i18n('openStreetMapCycleMap');
                                }
                                this.baseMaps['CycleMap'] = map;
                                break;
                            default: //Default to Mapnik tileset
                                if (map.mapTag.extension.Options[0].name) {
                                    this.options['Mapnik'] = map.mapTag.extension.Options[0].name[0];
                                }
                                else {
                                    this.options['Mapnik'] =  OpenLayers.i18n('openStreetMap');
                                }
                                this.baseMaps['Mapnik'] = map;
                                break;
                        }
                        // The first non-MapGuide basemap will be the default basemap
                        if (!this.defaultBasemap) {
                            this.defaultBasemap = map.mapTag.extension.Options[0].type[0];
                        }
                    }
                    break;
                default:
                    break;
            }
        }
        if (!this.defaultBasemap) {
            this.defaultBasemap = "None";
        }
    },

    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        this.setDefaultBasemap();
    },
    
    checkZoomOffsets: function() {
        if (this.activeBasemap != null) {
            this.applyZoomOffset(this.activeBasemap);
        }
    },
    
    applyZoomOffset: function(baseMap) {
        var offset = 0;
        if (this.zoomOffsets[baseMap])
            offset = this.zoomOffsets[baseMap];
        for (var i = 0; i < this.getMap().aMaps.length; i++) {
            var layer = this.getMap().aMaps[i];
            if (layer.arch == "MapGuide" && layer.bUsesCommercialLayerScaleList) {
                layer.applyZoomOffset(offset);
            }
        }
    },

    setBasemap: function(baseMap) {
        if ("None" != baseMap && this.getMap().oMapOL.baseLayer.CLASS_NAME == "OpenLayers.Layer.MapGuide") {
            var visibility = this.baseMaps["None"].oLayerOL.visibility;
            this.getMap().setExternalBaseLayer(this.baseMaps[baseMap], false);
            this.applyZoomOffset(baseMap);
            // Keep the MapGuide layers visibility
            this.baseMaps["None"].oLayerOL.visibility = visibility;
            this.baseMaps["None"].oLayerOL.redraw();
        }
        else {
            this.getMap().setExternalBaseLayer(this.baseMaps[baseMap], false);
            this.applyZoomOffset(baseMap);
        }
        this.activeBasemap = baseMap;
    },

    setDefaultBasemap: function() {
        this.generateOptions();

        //clean current menu items
        this.uiObj.subDomObj.empty();

        //set up the root menu
        var buttonSet = new Jx.ButtonSet();
        for (var key in this.options) {
            if (this.options[key]) {
                var menuItem = new Jx.Menu.Item({
                    label: OpenLayers.i18n(this.options[key]),
                    toggle: true,
                    onDown: OpenLayers.Function.bind(this.setBasemap, this, key)
                });
                buttonSet.add(menuItem);
                this.uiObj.add(menuItem);
                this.menuItems[key] = menuItem;
            }
        }
        this.menuItems[this.defaultBasemap].setActive(true);
        this.setBasemap(this.defaultBasemap);
    }
});
/**
 * Fusion.Widget.Buffer
 *
 * $Id: Buffer.js 2670 2013-03-22 15:33:22Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/*****************************************************************************
 * Class: Fusion.Widget.Buffer
 *
 * The Buffer widget prompts the user for some inputs and then creates a buffer
 * around the current selection based on those inputs.
 *
 * NOTE: This version of the widget is not currently being used.  
 * Use BufferPanel instead. The 2 widgets will be merged eventually.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **************************************************************************/

Fusion.Widget.Buffer = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    isExclusive: false,
    layerName: null,
    layerNameInput: null,
    bufferDistance: null,
    bufferDistanceInput: null,
    bufferUnits: null,
    bufferUnitsInput: null,
    borderColor: null,
    borderColorInput: null,
    fillColor: null,
    fillColorInput: null,
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        /* pick up default values */
        this.layerName = json.LayerName ? json.LayerName[0] : '';
        this.layerNameInput = json.LayerNameInput ? json.LayerNameInput[0] : null;
        this.bufferDistance = json.BufferDistance ? parseFloat(json.BufferDistance[0]) : '';
        this.bufferDistanceInput = json.BufferDistanceInput ? json.BufferDistanceInput[0] : null;
        this.bufferUnits = Fusion.unitFromName(json.BufferUnits ? json.BufferUnits[0] : 'meters');
        this.bufferUnitsInput = json.BufferUnitsInput ? json.BufferUnitsInput[0] : null;
        this.borderColor = json.BorderColor ? json.BorderColor[0] :'00000000';
        this.borderColorInput = json.BorderColorInput ? json.BorderColorInput[0] : null;
        this.fillColor = json.FillColor ? json.FillColor[0] : '00000000';
        this.fillColorInput = json.FillColorInput ? json.FillColorInput[0] : null;
        
        /* initialize inputs with defaults */
        if (this.layerNameInput) {
            this.layerNameInput = $(this.layerNameInput);
            this.setValue(this.layerNameInput, this.layerName);
        }
        if (this.bufferDistanceInput) {
            this.bufferDistanceInput = $(this.bufferDistanceInput);
            this.setValue(this.bufferDistanceInput, this.bufferDistance);
        }
        if (this.bufferUnitsInput) {
            this.bufferUnitsInput = $(this.bufferUnitsInput);
            this.setValue(this.bufferUnitsInput, this.bufferUnits);
        }
        if (this.borderColorInput) {
            this.borderColorInput = $(this.borderColorInput);
            this.setValue(this.borderColorInput, this.borderColor);
        }
        if (this.fillColorInput) {
            this.fillColorInput = $(this.fillColorInput);
            this.setValue(this.fillColorInput, this.fillColor);
        }
        
        /* override selection behaviour */
        this.enable = Fusion.Widget.Buffer.prototype.enable;
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.enable, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.disable, this));
    },
    
    setValue: function(input, value) {
        if (input.tagName.toLowerCase() == "input") {
            switch(input.type) {
                case 'radio':
                case 'checkbox':
                    for (var i=0; i<input.length; i++) {
                        if (input[i].value == value) {
                            input[i].checked = true;
                        }
                    }
                    break;
                case 'file':
                    break;
                case 'button':
                case 'hidden':
                case 'image':
                case 'password':
                case 'reset':
                case 'submit':
                case 'text':
                    input.value = value;
                    break;
                default:
            }
        }
        if (input.tagName.toLowerCase() == 'textarea') {
            input.value = value;
        }
        if (input.tagName.toLowerCase() == 'select') {
            for (var i=0; i<input.options.length; i++) {
                if (input.options[i].value == value) {
                    input.options[i].selected = true;
                    break;
                }
            }
        }
    },
    
    getValue: function(input) {
        if (input.tagName.toLowerCase() == "input") {
            switch(input.type) {
                case 'radio':
                case 'checkbox':
                    return input.value;
                    break;
                case 'file':
                case 'button':
                case 'hidden':
                case 'image':
                case 'password':
                case 'reset':
                case 'submit':
                case 'text':
                    return input.value;
                    break;
                default:
            }
        }
        if (input.tagName.toLowerCase() == 'textarea') {
            return input.value;
        }
        if (input.tagName.toLowerCase() == 'select') {
            return input.options[input.selectedIndex].value;
        }
    },
    
    enable: function() {
        if (this.oMap && this.oMap.hasSelection()) {
            Fusion.Widget.prototype.enable.apply(this, []);
        } else {
            this.disable();
        }
    },
    
    execute: function() {
        if (this.layerNameInput) {
            this.layerName = this.getValue(this.layerNameInput);
        }
        var layer = '&layer=' + this.layerName;
        
        var d;
        if (this.bufferDistanceInput) {
            d = this.getValue(this.bufferDistanceInput);
        } else {
            d = this.bufferDistance;
        }
        
        var du;
        if (this.bufferUnitsInput) {
            du = this.getValue(this.bufferUnitsInput);
        } else {
            du = this.bufferUnits;
        }
        
        /* convert distance to meters client side */
        var distance = '&distance='+Fusion.toMeter(Fusion.unitFromName(du), d);
        
        var borderColor = '&bordercolor=';
        if (this.borderColorInput) {
            borderColor += this.getValue(this.borderColorInput);
        } else {
            borderColor += this.borderColor;
        }
        
        var fillColor = '&fillcolor=';
        if (this.fillColorInput) {
            fillColor += this.getValue(this.fillColorInput);
        } else {
            fillColor += this.fillColor;
        }
        
        var mapWidget = this.getMap();
        var widgetLayer = this.getMapLayer();        
        var s = widgetLayer.arch + '/' + Fusion.getScriptLanguage() + "/Buffer." + Fusion.getScriptLanguage();
        var params = {};
        params.parameters = 'locale='+Fusion.locale +
                            '&merge=1' +
                            '&session='+widgetLayer.getSessionID() +
                            '&mapname='+ widgetLayer.getMapName()+
                            layer+distance+borderColor+fillColor; 
        params.onComplete = OpenLayers.Function.bind(this.bufferCreated, this);
        Fusion.ajaxRequest(s, params);
    },
    
    bufferCreated: function() {
        var widgetLayer = this.getMapLayer();        
        var layer = widgetLayer.getLayerByName(this.layerName);
        if (layer) {
          layer.noCache = true;
        }
        widgetLayer.reloadMap();
        widgetLayer.drawMap();
    }
});
/**
 * Fusion.Widget.BufferPanel
 *
 * $Id: BufferPanel.js 2670 2013-03-22 15:33:22Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.BufferPanel
 *
 * The BufferPanel widget prompts the user for some inputs and then creates a 
 * buffer around the current selection based on those inputs.
 *
 * If the Target property points to TaskPane widget, the task will be listed in
 * the menu list of the TaskPane and loaded there.
 * Otherwise if the target is an existing IFrame in the page it will be loaded 
 * there, otherwise it will open a new window with that name.
 * 
 *  Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.BufferPanel = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    sFeatures : 'menubar=no,location=no,resizable=no,status=no',
    isExclusive: false,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "BufferPanelWindow";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/BufferPanel/BufferPanel.php';
        
        this.bSelectionOnly = (json.DisableIfSelectionEmpty &&
                           (json.DisableIfSelectionEmpty[0] == 'true' ||
                            json.DisableIfSelectionEmpty[0] == '1')) ? true : false;
                            
        this.additionalParameters = [];
        if (json.AdditionalParameter) {
            for (var i=0; i<json.AdditionalParameter.length; i++) {
                var p = json.AdditionalParameter[i];
                var k = p.Key[0];
                var v = p.Value[0];
                this.additionalParameters.push(k+'='+encodeURIComponent(v));
            }
        }
        
        this.enable = Fusion.Widget.BufferPanel.prototype.enable;
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.enable, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.enable, this));
        this.disable();
    },

    enable: function() {
        var map = this.getMap();
        if (this.bSelectionOnly || !map) {
            if (map && map.hasSelection()) {
                if (this.action) {
                    this.action.setEnabled(true);
                } else {
                    Fusion.Widget.prototype.enable.apply(this,[]);
                }
            } else {
                if (this.action) {
                    this.action.setEnabled(false);
                } else {
                    this.disable();
                }
            }
        } else {
            if (this.action) {
                this.action.setEnabled(true);
            } else {
                Fusion.Widget.prototype.enable.apply(this,[]);
            }
        }
    },
    
    activate: function() {
        var url = this.sBaseUrl;
        //add in other parameters to the url here
        
        var map = this.getMap();
        var widgetLayer = this.getMapLayer();        
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        var pageElement = $(this.sTarget);

        var params = [];
        params.push('locale='+Fusion.locale);
        params.push('session='+widgetLayer.getSessionID());
        params.push('mapname='+widgetLayer.getMapName());
        if (taskPaneTarget || pageElement) {
          params.push('popup=false');
        } else {
          params.push('popup=true');
        }
        params.push('us=0');  //TODO: get displayunits from viewOptions; as it stands. us=1 means miles, anything else is kilometers
        params = params.concat(this.additionalParameters);

        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
        } else {
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                window.open(url, this.sTarget, this.sWinFeatures);
            }
        }
    }
});
/**
 * Fusion.Widget.CenterSelection
 *
 * $Id: CenterSelection.js 2682 2013-03-28 09:02:03Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.CenterSelection
 *
 * Center the current selection, if any, but maintain the current scale
 * if possible.  Zoom out if not.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.CenterSelection = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    initializeWidget: function(widgetTag) {
        this.enable = Fusion.Widget.CenterSelection.prototype.enable;
        
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.enable, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.disable, this));
    },

    /**
     * get the selection from the map (which may not be loaded yet).
     * zoomToSelection is called when the selection is ready.
     */
    activate: function() {
        this.getMap().getSelection(OpenLayers.Function.bind(this.centerSelection, this));
    },

    /**
     * set the extents of the map based on the pixel coordinates
     * passed
     * 
     * @param selection the active selection, or null if there is none
     */
    centerSelection : function(selection) {
        var map = this.getMap(); 
        var extents = map.getCurrentExtents();
        var curWidth = extents[2] - extents[0];
        var curHeight = extents[3] - extents[1];
        
        var mapName = null;
        var mlayers = map.getAllMaps();
        for (var i = 0; i < mlayers.length; i++) {
            if (mlayers[i].arch == "MapGuide") {
                mapName = mlayers[i].getMapName();
                break;
            }
        }
        
        if (mapName != null) {
            var ll = selection[mapName].getLowerLeftCoord();
            var ur = selection[mapName].getUpperRightCoord();
            
            var newWidth = ur.x - ll.x;
            var newHeight = ur.y - ll.y;
            
            if (newWidth < curWidth && newHeight < curHeight) {
                var cx = (ur.x + ll.x) / 2;
                var cy = (ur.y + ll.y) / 2;
                map.zoom(cx,cy,1);
            } else {
                var buffer = 0.1;
                var minx = ll.x-newWidth*buffer;
                var miny = ll.y-newHeight*buffer;
                var maxx = ur.x+newWidth*buffer;
                var maxy = ur.y+newHeight*buffer;
                map.setExtents(new OpenLayers.Bounds(minx,miny,maxx,maxy));
            }
        }
    },

    enable: function() {
        if (this.oMap && this.oMap.hasSelection()) {
            Fusion.Widget.prototype.enable.apply(this, []);
        } else {
            this.disable();
        }
    }
});
/**
 * Fusion.Widget.ClearSelection
 *
 * $Id: ClearSelection.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.ClearSelection
 *
 * Clears the current selection, if any.
 * 
 *  Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.ClearSelection = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    initializeWidget: function(widgetTag) {        
        this.enable = Fusion.Widget.ClearSelection.prototype.enable;
        
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.enable, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.disable, this));
    },
    
    /**
     * clears slection on map.
     */
    activate: function() {
        this.getMap().clearSelection();
    },
    
    enable: function() {
        if (this.oMap && this.oMap.hasSelection()) {
            Fusion.Widget.prototype.enable.apply(this, []);
        } else {
            this.disable();
        }
    }
});
/**
 * Fusion.Widget.ColorPicker
 *
 * $Id: ColorPicker.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.ColorPicker
 *
 * The user can pick from a palette of web-safe colours or enter a hex value. 
 * The colour selected will be entered into the configured input element.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.ColorPicker = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button.Color,
    
    /* HTML input element that is used to store both the initial
       value for this widget and receives the color value as the
       color changes */
    colorInput: null,

    /* Int (0-100) containing the alpha chosen by the user */
    alpha: 100,
    
    /* String containing the HEX value of the color chosen by the
       user, in RRGGBB format */
    color: '#000000',
    
    colorButton: null,
    
    initializeWidget: function(widgetTag) {      
        var json = widgetTag.extension;
        if (json.ColorInputId) {
            this.colorInput = $(json.ColorInputId[0]);
        }
        
        if (this.colorInput) {
            this.alpha = 100 * parseInt('0x'+this.colorInput.value.substring(0,2))/255;
            this.color = '#'+this.colorInput.value.substring(2);
            this.colorInput.widget = this;
        }
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        this.uiObj.setColor(this.color);
        this.uiObj.addEvent('change', OpenLayers.Function.bind(this.colorChanged, this));
    },
    
    colorChanged: function(button) {
        var a = parseInt(button.options.alpha*255/100).toString(16);
        var c = a + button.options.color.substring(1);
        if (this.colorInput) {
            this.colorInput.value = c;
        }
    }
});/*********************************************************************
 * Class: Fusion.Widget.CoordinateTracker
 *
 * The CoordinateTracker widget allows the user to view mouse coordinates
 * in various projections. The projections must be in EPSG format
 *
 * Inherits from:
 *  - <Fusion.Widget>
 ***********************************************************************/
 
Fusion.Widget.CoordinateTracker = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,
    
    template: "x: {x}<br/> y: {y}",
    taskPane: null,
    olMapProj: null,
    projections: {},
    sWinFeatures: "menubar=no,location=no,resizable=no,status=no,width=250,height=300",

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        /*
        <Projection>EPSG:4326</Projection>
        <Projection>EPSG:900913</Projection>
        */
        if (json.Projection) {
            for (var i = 0; i < json.Projection.length; i++) {
                var code = json.Projection[i];
                this.projections[code] = new OpenLayers.Projection(code);
            }
        }
        
        this.sTarget = json.Target ? json.Target[0] : "";
        if (this.sTarget)
            this.taskPane = new Fusion.Widget.CoordinateTracker.DefaultTaskPane(this);
    },
    getDomId: function(epsgCode) {
        return epsgCode.replace(":", "_");
    },
    activate: function() {
        if (this.taskPane) {
            this.taskPane.loadDisplayPanel();
        }
    },
    deactivate: function() {
        this.stopListening();
    },
    startListening: function() {
        this.olMapProj = this.getMap().oMapOL.getProjectionObject();
        this.getMap().oMapOL.events.register("mousemove", this.taskPane, this.taskPane.updateCoordinates);
        //console.log("start listening");
    },
    stopListening: function() {
        if (this.olMapProj) {
            this.olMapProj = null;
            this.getMap().oMapOL.events.unregister("mousemove", this.taskPane, this.taskPane.updateCoordinates);
            //console.log("stop listening");
        }
    }
});

Fusion.Widget.CoordinateTracker.DefaultTaskPane = OpenLayers.Class({
    widget: null,
    taskPaneWin: null,
    panelUrl: "widgets/CoordinateTracker/CoordinateTracker.php",
    map: null,
    
    initialize: function(widget) {
        this.widget = widget;
        this.map = this.widget.getMap().oMapOL;
    },
    
    loadDisplayPanel: function() {
        var url = Fusion.getFusionURL() + this.panelUrl;
        var params = [];
        params.push("LOCALE=" + Fusion.locale);
        
        if (url.indexOf("?") < 0) {
            url += "?";
        } else if (url.slice(-1) != "&") {
            url += "&";
        }
        url += params.join("&");
        var taskPaneTarget = Fusion.getWidgetById(this.widget.sTarget);
        var outputWin = window;
        if (taskPaneTarget) {
            taskPaneTarget.setContent(url);
            outputWin = taskPaneTarget.iframe.contentWindow;
        } else {
            outputWin = window.open(url, this.widget.sTarget, this.widget.sWinFeatures);
        }
        
        this.taskPaneWin = outputWin;
        var initFunction = OpenLayers.Function.bind(this.initPanel, this);
        setTimeout(initFunction, 300);
    },
    
    updateCoordinates: function(evt) {
        lonLat = this.map.getLonLatFromPixel(evt.xy);
        if (!lonLat)
            return;
        if (!this.widget.olMapProj)
            return;
        
        for (var code in this.widget.projections) {
            var txLonLat = lonLat.clone();
            txLonLat.transform(this.widget.olMapProj, 
                               this.widget.projections[code]); 
            
            var el = this.taskPaneWin.document.getElementById(this.widget.getDomId(code));
            if (el) {
                var template = this.widget.template.replace("{x}", txLonLat.lon)
                                                   .replace("{y}", txLonLat.lat);
                el.innerHTML = template;
            }
        }
    },
    
    initPanel: function() {
        var bReady = false;
        try {
            bReady = this.taskPaneWin.bReady;
        } catch (e) {
            if (!bReady) {
                var initFunction = OpenLayers.Function.bind(this.initPanel, this);
                setTimeout(initFunction, 300);
                return;
            }
        }
        
        this.taskPaneWin.setWidget(this.widget);
    }
});
/**
 * Fusion.Widget.CursorPosition
 *
 * $Id: CursorPosition.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.CursorPosition
 *
 * Displays the geographic position of the cursor when it is over the map.
 *
 * Precision (integer, optional)
 *
 * The number of digits to round the output to.  The geographic location
 * is calculated with arbitrary precision and is often not necessary. A
 * value of less than 0 means no rounding (the default if parameter is
 * missing).
 *
 * Template (string, optional) 
 *
 * The format of the output string.  Use {x} and {y} as placeholders for
 * the x and y location.  The default template is:
 *
 * x: {x}, y: {y}
 *
 * You can embed HTML in the template, but you must escape any characters
 * that result in illegal HTML.  This would include:
 *
 * < is &lt;
 * > is &gt;
 * & is &amp;
 *
 * So a two-line display would be:
 *
 * x: {x}&lt;br/&gt;y: {y}
 * 
 *  Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.CursorPosition = OpenLayers.Class(Fusion.Widget, {
    defaultTemplate: 'x: {x}, y: {y}',
    domSpan: null,
    
    /* the units to display distances in */
    units: Fusion.UNKNOWN,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        this.template = json.Template ? json.Template[0] : this.defaultTemplate;
        this.precision = json.Precision ? parseInt(json.Precision[0]) : -1;
        this.units = json.Units ? Fusion.unitFromName(json.Units[0]) : Fusion.UNKOWN;

        this.domSpan = document.createElement('span');
        this.domSpan.className = 'spanCursorPosition';
        this.domSpan.innerHTML = this.emptyText;
        this.emptyText = json.EmptyText ? json.EmptyText[0] : 
            (this.domObj ? this.domObj.innerHTML : '');
        if (this.domObj) {
            this.domObj.innerHTML = '';
            this.domObj.appendChild(this.domSpan);
        } 
        this.displayProjection = json.DisplayProjection ? new OpenLayers.Projection(json.DisplayProjection[0]) : null;
        
        this.control = new OpenLayers.Control.MousePosition({
        		div: this.domSpan,
        		formatOutput: OpenLayers.Function.bind(this.formatHTML, this),
        		emptyString: this.emptyText,
        		displayProjection: this.displayProjection
        });
        this.getMap().oMapOL.addControl(this.control);

        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.setUnits, this));
        this.registerParameter('Units');
        
        Fusion.addWidgetStyleSheet(widgetTag.location + '/CursorPosition/CursorPosition.css');
        
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        if (this.uiObj.domObj) {
            this.uiObj.domObj.appendChild(this.domSpan);
        } else {
            this.uiObj.appendChild(this.domSpan);
        }
    },
    
    formatHTML: function(p) {
        if (!this.displayProjection) {
            var mapProj = this.getMap().projection;
            var mapUnit = mapProj.getUnits();

            // convertion from linear units to degree unit.
            if(this.units == Fusion.DEGREES && mapUnit != 'dd' && mapUnit != 'degrees' ) {
                // coordinate transformation from map CS to EPSG:4326.
                var dest = new OpenLayers.Projection("GEOGCS[\"LL84\",DATUM[\"WGS84\",SPHEROID[\"WGS84\",6378137.000,298.25722293]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.01745329251994]]");
                p = p.transform(this.getMap().projection, dest);
            }
            //else
            //{
                // TODO: convertion from degree unit to linear units
            //}
            
            /* old code for converting between units */
            else if (this.units != Fusion.UNKNOWN) {
                var convFactor = this.getMap().getMetersPerUnit();
                p.lon = Fusion.fromMeter(this.units, p.lon * convFactor);
                p.lat = Fusion.fromMeter(this.units, p.lat * convFactor);
            }
            
            if (this.precision >= 0) {
                var factor = Math.pow(10,this.precision);
                p.lon = Math.round(p.lon * factor)/factor;
                p.lat = Math.round(p.lat * factor)/factor;
            }
        }
        var unitAbbr = Fusion.unitAbbr(this.units);
        var innerHTML = this.template.replace('{x}',p.lon.toFixed(this.precision));
        innerHTML = innerHTML.replace('{y}',p.lat.toFixed(this.precision));
        innerHTML = innerHTML.replace('{units}', unitAbbr).replace('{units}', unitAbbr);
        return innerHTML;
    },

    setUnits: function() {
                if (this.units == Fusion.UNKNOWN) {
                    this.setParameter('Units',this.getMap().getUnits());
                }
    },

    setParameter: function(param, value) {
        if (param == 'Units') {
            var unitName = value;
            if (this.displayProjection) {
                unitName = this.displayProjection.getUnits();
            }
          this.units = Fusion.unitFromName(unitName);
        }
    }
});
/**
 * Fusion.Widget.EditableScale
 *
 * $Id: EditableScale.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.EditableScale
 *
 * The user can manually type in a new scale
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.EditableScale = OpenLayers.Class(Fusion.Widget, {
    precision: 4,
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        this.domPrefix = document.createElement('span');
        this.domPrefix.className = 'inputEditableScalePrefix';
        this.domPrefix.innerHTML = '1: ';
        this.domScale = document.createElement('input');
        this.domScale.className = 'inputEditableScale';
        OpenLayers.Event.observe(this.domScale, 'keypress', 
           OpenLayers.Function.bindAsEventListener(this.keyPressHandler, this));
        this.precision = json.Precision ? parseInt(json.Precision[0]) : this.precision;
        
        this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, OpenLayers.Function.bind(this.scaleChanged, this));
        
        Fusion.addWidgetStyleSheet(widgetTag.location + '/EditableScale/EditableScale.css');
        
        if (this.domObj) {
            this.domObj.appendChild(this.domPrefix);
            this.domObj.appendChild(this.domScale);
        }
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        if (this.uiObj.domObj) {
            this.uiObj.domObj.appendChild(this.domPrefix);
            this.uiObj.domObj.appendChild(this.domScale);
        } else {
            this.uiObj.appendChild(this.domPrefix);
            this.uiObj.appendChild(this.domScale);
        }
    },
    
    scaleChanged: function() {
        this.domScale.value = this.scaleToString(this.getMap().oMapOL.getScale());
    },
    
    scaleToString: function(scale) {
        scale = Math.abs(parseFloat(scale));
        return "" + Math.round(scale * Math.pow(10,this.precision))/Math.pow(10,this.precision);
    },
    
    keyPressHandler: function(e) {
        if (e.keyCode == OpenLayers.Event.KEY_RETURN) {
            this.zoomToScale();
        }
    },
    
    zoomToScale: function(e) {
        var scale = parseFloat(this.domScale.value);
        if (scale) {
            this.getMap().zoomToScale(scale);
        }
    }
});/**
 * Fusion.Widget.ExtentHistory
 *
 * $Id: ExtentHistory.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.ExtentHistory
 *
 * Maintain and navigate through a history of extents
 * 
 *  Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Event.HISTORY_CHANGED = Fusion.Event.lastEventId++;

Fusion.Widget.ExtentHistory = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    events: [],
    aHistory: [],
    sDirection: null,
    EPS: 0.00000001,  //percentage difference allowed in bounds values for test for equal
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        var sDirection = json.Direction ? json.Direction[0].toLowerCase() : 'previous';
        if (sDirection != 'previous' && sDirection != 'next') {
            this.sDirection = 'previous';
        } else {
            this.sDirection = sDirection;
        }
        
        if (!this.aHistory['history']) {
            this.aHistory['history'] = [];
            this.aHistory['index'] = -1;
            this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, 
                          OpenLayers.Function.bind(this.extentsChanged, this));
            this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, 
                          OpenLayers.Function.bind(this.reset, this));
            
        }
        this.enable = Fusion.Widget.ExtentHistory.prototype.historyChanged;
        
        this.disable = Fusion.Widget.ExtentHistory.prototype.historyChanged;
        
        this.registerEventID(Fusion.Event.HISTORY_CHANGED);
        
        this.registerForEvent(Fusion.Event.HISTORY_CHANGED, 
                          OpenLayers.Function.bind(this.historyChanged, this));
        //console.log(this.events[Fusion.Event.HISTORY_CHANGED].length);
        this.disable();
    },
    
    reset: function() {
        if (this.getMap().isMapLoaded()) {
            this.aHistory['history'] = [this.getMap().getCurrentExtents()];
            this.aHistory['index'] = 0;
        } else {
            this.aHistory['history'] = [];
            this.aHistory['index'] = -1;
        }
        this.historyChanged();
    },
    
    extentsChanged: function() {
        var extents = this.getMap().getCurrentExtents();
        if (this.aHistory['history'].length == 0) {
            this.aHistory['history'].push(extents);
            this.aHistory['index'] = 0;
        } else {
            var aExtents = this.aHistory['history'][this.aHistory['index']];
            if (this.boundsEqual(extents, aExtents)) {
                return;
            }
            //clear forward history if we zoom to a different extent than contained in the history
            if (this.aHistory['index'] != (this.aHistory['history'].length - 1)) {
                this.aHistory['history'] = this.aHistory['history'].slice(0, this.aHistory['index'] + 1);
            }
            this.aHistory['history'].push(extents);
            this.aHistory['index'] = this.aHistory['history'].length - 1;
        }
        this.triggerEvent(Fusion.Event.HISTORY_CHANGED);
    },
    
    historyChanged: function() {
        if (this.sDirection == 'previous') {
            if (this.aHistory['index'] > 0) {
                Fusion.Widget.prototype.enable.apply(this,[]);
            } else {
                Fusion.Widget.prototype.disable.apply(this,[]);
            }
        } else {
            if (this.aHistory['index'] < (this.aHistory['history'].length - 1)) {
                Fusion.Widget.prototype.enable.apply(this,[]);
            } else {
                Fusion.Widget.prototype.disable.apply(this,[]);
            }
        }
    },

    activate: function() {
        if (this.sDirection == 'previous') {
            if (this.aHistory['index'] > 0) {
                this.aHistory['index'] --;
                this.getMap().setExtents(this.aHistory['history'][this.aHistory['index']]);
                this.triggerEvent(Fusion.Event.HISTORY_CHANGED);
            }
        } else {
            if (this.aHistory['index'] < (this.aHistory['history'].length - 1)) {
                this.aHistory['index'] ++;
                this.getMap().setExtents(this.aHistory['history'][this.aHistory['index']]);
                this.triggerEvent(Fusion.Event.HISTORY_CHANGED);
            }
        }
    },
    
    /* 
      * test if 2 OpenLayers.Bounds objects are equal to within some precision
      */
    boundsEqual: function(b1, b2) {
      var equal = false;
      
      //prevent divide by 0 errors
      var offset = 100;
      if (b2.top == 0) {
        b1.top += offset;
        b2.top += offset;
      }
      if (b2.bottom == 0) {
        b1.bottom += offset;
        b2.bottom += offset;
      }
      if (b2.left == 0) {
        b1.left += offset;
        b2.left += offset;
      }
      if (b2.right == 0) {
        b1.right += offset;
        b2.right += offset;
      }
      //calculate difference as percentage so all ranges of coordinates can be accommodated
      equal = (Math.abs((b1.top - b2.top) / b2.top) < this.EPS &&
               Math.abs((b1.bottom - b2.bottom) / b2.bottom) < this.EPS &&
               Math.abs((b1.left - b2.left) / b2.left) < this.EPS &&
               Math.abs((b1.right - b2.right) / b2.right) < this.EPS);
      return equal;
    }
});

/**
 * Fusion.Widget.FeatureInfo
 *
 * $Id: $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.FeatureInfo
 *
 * The FeatureInfo widget displays information about selected polygons.
 *
 * If the Target property points to TaskPane widget, the task will be listed in
 * the menu list of the TaskPane and loaded there.
 * Otherwise if the target is an existing IFrame in the page it will be loaded
 * there, otherwise it will open a new window with that name.
 * 
 *  Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.FeatureInfo = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,
    sFeatures: 'menubar=no,location=no,resizable=no,status=no',
    oTarget: null,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "FeatureInfoWindow";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/FeatureInfo/featureinfomain.php';
    },

    activate: function() {
        var url = this.sBaseUrl;
        //add in other parameters to the url here

        this.mapWidget = this.getMap();
        var widgetLayer = this.getMapLayer();
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        var pageElement = $(this.sTarget);

        var params = [];
        params.push('LOCALE='+Fusion.locale);
        params.push('SESSION='+widgetLayer.getSessionID());
        params.push('MAPNAME='+widgetLayer.getMapName());
        if (taskPaneTarget || pageElement) {
          params.push('POPUP=false');
        } else {
          params.push('POPUP=true');
        }

        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
            this.oTarget = taskPaneTarget.iframe.contentWindow;
        } else {
            if ( pageElement ) {
                pageElement.src = url;
                this.oTarget = pageElement;
            } else {
                this.oTarget = window.open(url, this.sTarget, this.sWinFeatures);
            }
        }
    },

    deactivate: function() {
        this.mapWidget.message.clear();
        //This function exists if MapGuideViewerApi.js was included in
        if (this.oTarget && this.oTarget.ClearDigitization) {
            this.oTarget.ClearDigitization(true);
        }
    }
});
/**
 * Fusion.Widget.Geolocation
 *
 * $Id: About.js 2585 2012-09-07 14:01:25Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/********************************************************************
* Class: Fusion.Widget.Geolocation
*
* A widget that pans (and optionally zoom) to the user's current geographic location
*
* Inherits from:
*  - <Fusion.Widget>
* **********************************************************************/

Fusion.Widget.Geolocation = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    
    nZoom: null,
    bEnableHighAccuracy: false,
    nTimeout: 5000,
    nMaximumAge: 0,
    geoProj: null,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        var geoOptions = {};
        if (json.ZoomLevel)
            this.nZoom = parseInt(json.ZoomLevel[0]);
        if (json.EnableHighAccuracy)
            geoOptions.enableHighAccuracy = (json.EnableHighAccuracy[0] == "true");
        if (json.Timeout)
            geoOptions.timeout = parseInt(json.Timeout[0]);
        if (json.MaximumAge)
            geoOptions.maximumAge = parseInt(json.MaximumAge[0]);
            
        this.geoOptions = geoOptions;
        
        this.enable();
    },
    
    onPositionError: function(error) {
        //Maybe use MapMessage for non-intrusive error display?
        switch(error.code) {
            case 1: //Permission denied
                this.getMap().message.error(error.message);
            case 2: //Position unavailable
                this.getMap().message.error(error.message);
            case 3: //Timeout
                this.getMap().message.error(error.message);
            default:
                console.error("Error with geolocation: (" + error.code + ") " + error.message);
        }
    },
    
    onPositionRetrieved: function(pos) {
        var mapWidget = this.getMap();
        var olMap = mapWidget.oMapOL;
        
        if (this.geoProj == null)
            this.geoProj = new OpenLayers.Projection("EPSG:4326");
        var mapProj = olMap.projection;
        
        var lonlat = new OpenLayers.LonLat(pos.coords.longitude, pos.coords.latitude);
        lonlat.transform(this.geoProj, mapProj);
        
        var msg = null;
        if (!mapWidget.initialExtents.contains(lonlat)) {
            msg = OpenLayers.i18n("currentPositionOutsideInitialView");
        } else if (!olMap.maxExtent.contains(lonlat)) {
            msg = OpenLayers.i18n("currentPositionOutsideMaxExtent");
        }
        if (msg != null) {
            var msgbar = this.getMap().message;
            msgbar.warn(msg);
            setTimeout(function() { msgbar.hideDesignatedMessage(msg); }, 5000);
        }

        if (this.nZoom == null) {
            olMap.moveTo(lonlat);
        } else {
            var extent = mapWidget.getExtentFromPoint(lonlat.lon, lonlat.lat, this.nZoom);
            mapWidget.setExtents(extent);
        }
    },

    /**
     * Function: activate
     *
     * Pan (and optionally zoom) to the user's current geographic location
     * 
     */
    activate: function() {
        if (typeof(navigator.geolocation) == 'undefined') {
            alert(OpenLayers.i18n("geolocation_unsupported"));
        } else {
            navigator.geolocation.getCurrentPosition(
                OpenLayers.Function.bind(this.onPositionRetrieved, this),
                OpenLayers.Function.bind(this.onPositionError, this),
                this.geoOptions);
        }
    }
});
/**
 * Fusion.Widget.GetFeatureInfo
 *
 * $Id: GetFeatureInfo.js 1377 2008-04-16 19:27:32Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.GetFeatureInfo
 *
 * A widget that displays a pre-configured search form to the user and then
 * runs the search.  Searches are done on the attributes of specifiedd layers.
 *
 * uses JavaScript Scale Bar for MapServer 
 * (http://mapserver.commenspace.org/tools/scalebar/
 * 
 *  Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Event.GET_FEATURE_INFO_COMPLETE = Fusion.Event.lastEventId++;

Fusion.Widget.GetFeatureInfo = OpenLayers.Class(Fusion.Widget, 
{
    wmsLayers: [],
    isExclusive: true,
    uiClass: Jx.Button,
    
    initializeWidget: function(widgetTag) {
        //console.log('GetFeatureInfo.initialize');
        var json = widgetTag.extension;
        
        var map = this.getMap().oMapOL;
        // create a new event handler for single click query
        this.handler = new OpenLayers.Handler.Click({ 'map': map }, {
            'click': OpenLayers.Function.bind(this.issueRequest, this) }); 
        
        this.registerEventID(Fusion.Event.GET_FEATURE_INFO_COMPLETE);
        this.getMap().registerEventID(Fusion.Event.WMS_LAYER_ADDED);
        this.getMap().registerForEvent(Fusion.Event.WMS_LAYER_ADDED, 
                          OpenLayers.Function.bind(this.layerAdded,this));
        this.cursorNormal = ["url('images/grab.cur'),move", 'grab', '-moz-grab', 'move'];
    },
    
    /**
     * (public) activate()
     *
     * activate the measure tool
     */
    activateTool: function() {
        this.getMap().activateWidget(this);
    },

    activate: function() {
        this.handler.activate();
    },
    
    deactivate: function() {
        this.handler.deactivate();
    },
    
    issueRequest: function(e) {
      if (this.wmsLayers.length < 1) {
        alert("there are no WMS layers to query");
        return;
      }
      
      var map = this.getMap();
      var queryLayer = map.getActiveLayer();
      var layer = null;
      if (queryLayer) {
        for (var i=0; i<this.wmsLayers.length; ++i) {
          if (queryLayer.layerName == this.wmsLayers[i].wms_name) {
            layer = this.wmsLayers[i];
            break;
          }
        }
      }
      if (!layer) {
        alert('Please select a WMS layer by clicking on the layer name in the legend.');
        return;
      }
      
      var infoFormat = 'text/html';
      if (e.shiftKey) {
        infoFormat = 'text/plain';
      } else if (e.ctrlKey) {
        infoFormat = 'text/xml';
      }
      var size = map.oMapOL.getSize();
      var mouseLoc = map.oMapOL.getLonLatFromPixel(e.xy);
      //for (var i=0; i<this.wmsLayers.length; ++i) {
        //var layer = this.wmsLayers[i];
        var params = {
                      REQUEST: "GetFeatureInfo",
                      VERSION: layer.wms_version,
                      SRS: layer.wms_srs,
                      FORMAT: layer.wms_format,
                      STYLES: "",
                      EXCEPTIONS: "application/vnd.ogc.se_xml",
                      BBOX: map.getCurrentExtents().toBBOX(),
                      X: e.xy.x,
                      Y: e.xy.y,
                      INFO_FORMAT: infoFormat,
                      LAYERS: layer.wms_name,
                      QUERY_LAYERS: layer.wms_name,
                      WIDTH: size.w,
                      HEIGHT: size.h};
        var join = (layer.wms_connection.indexOf('?')<0) ? '?' : '&';
        var url = layer.wms_connection + join + OpenLayers.Util.getParameterString(params);
        /*
        var proxy = '/fusion-1.1/ext/coin/php/RemoteQuery.php?rawoutput=true&remoteUrl=';
        url = proxy + url;
        var infoDialog = new Jx.Dialog({
            contentURL: url,
            modal: false,
            title: 'Feature information for '+layer.wms_name,
            resizable: true
        });
        infoDialog.open();
        */
        //console.log(url);
        window.open(url);
        /*
        OpenLayers.Request.GET({
            url: layer.wms_connection, 
            params: params,
            success: OpenLayers.Function.bind(this.displayResults, this), 
            failure: OpenLayers.Function.bind(this.getFeatureInfoFailed, this), 
            scope: this
        });
        */
      //}
      OpenLayers.Event.stop(e);
    },
    
    displayResults: function(arg1, arg2) {
    },
    
    getFeatureInfoFailed: function(arg1, arg2) {
    },
    
    layerAdded: function(eventId, layers) {
      for (var i=0; i<layers.length; ++i) {
        this.wmsLayers.push(layers[i]);
      }
    }
});

Fusion.require("lib/SymbolLayer.js");

/********************************************************************
* Class: Fusion.Widget.GoogleStreetViewer
*
* GoogleStreetViewer widget to display google street.
* 
*  Inherits from:
*  - <Fusion.Widget>
* **********************************************************************/

Fusion.Widget.GoogleStreetViewer = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,
    windowFeatures: "menubar=no,location=no,resizable=no,status=no,width=500,height=400",
    targetWindow: null,
    GoogleSVPage: null,
    targetCs: null,
    additionalParameters: null,
    activated: false,
    symbolLayerName: "GoogleStreetViewerSymbolLayer",
    symbolLayer: null,
    symbolStyle: {
        externalGraphic: Fusion.getFusionURL() + "widgets/GoogleStreetViewer/navigator.png",
        graphicHeight: 32,
        graphicWidth: 48,
        graphicOpacity: 1,
        rotation: 0
    },
    symbolRadius: 16,        // Min(graphicHeight/2, graphicWidth/2)
    showDirection: true,
    directionStyle: {
        fillColor: "black",
        fillOpacity: 0.4,
        strokeColor: "black",
        strokeOpacity: 1,
        strokeWidth: 3,
        strokeLinecap: "round",
        strokeDashstyle: "dash",
        hoverStrokeColor: "black",
        hoverStrokeOpacity: 1,
        hoverStrokeWidth: 0.2
    },
    directionLength: 22,
    csTransformErrorMessage: null,
    serviceUnavailableMessage: null,
    googleBaseMapErrorMessage: null,

    /*
    * Constructor: GoogleStreetViewer
    *
    * Parameters:
    *
    * widgetTag - JSON node for this widget from the Application definition
    *
    */
    initializeWidget: function (widgetTag) {
        Fusion.Widget.GoogleStreetViewer.hasGSV = true;
        this.csTransformErrorMessage = OpenLayers.i18n("googleSteetViewCSTransformError");
        this.serviceUnavailableMessage = OpenLayers.i18n("googleSteetViewNotAvailableError");
        this.googleBaseMapErrorMessage = OpenLayers.i18n("googleBaseMapNotOnError");
        this.targetCs = new OpenLayers.Projection("EPSG:4326");
        var json = widgetTag.extension;
        this.targetWindow = json.Target ? json.Target[0] : "TaskPane";

        if (json.SymbolLayerName != null && json.SymbolLayerName[0].length != 0) {
            this.symbolLayerName = json.SymbolLayerName[0];
        }

        if (json.ShowDirection != null && json.ShowDirection[0] != null) {
            this.showDirection = (json.ShowDirection[0].toLowerCase() === "false") ? false : true;
        }

        if (json.DirectionLength != null && json.DirectionLength[0] != null && json.DirectionLength[0] != "") {
            try {
                var length = parseInt(json.DirectionLength[0]);
                this.directionLength = length;
            } catch (e) {
            }
        }

        this.additionalParameters = [];

        if (json.AdditionalParameter) {
            for (var i = 0; i < json.AdditionalParameter.length; i++) {
                var p = json.AdditionalParameter[i];
                var k = p.Key[0];
                var v = p.Value[0];
                this.additionalParameters.push(k + '=' + encodeURIComponent(v));
            }
        }

        if (!Fusion.Widget.GoogleStreetViewer.prototype.enable)
            this.disable();

        this.getMap().oMapOL.events.register("changebaselayer",this, this.baseMapChanged);
    },
    
    baseMapChanged: function (layer){
        if(!this.isGoogleBaseMapOn()) {
            if(this.activated){
                this.deactivate();
                alert(this.googleBaseMapErrorMessage);
            }
        }
    },
    
    isGoogleBaseMapOn: function(){
        var maps = this.getMap().aMaps;

        for(var i=0; i < maps.length; i++){
            if(maps[i].oLayerOL === this.getMap().oMapOL.baseLayer){
                if(maps[i].layerType === "Google"){  
                    return true;   
                }  
            }     
        }

        return false;
    },

    activate: function() {
        if(!this.isGoogleBaseMapOn()) {
            if (this.activated) {
                this.deactivate();
            }
            alert(this.googleBaseMapErrorMessage);
            return;
        }

        if (this.activated) return;

        //it seems this does nothing
        this.setGoogleSVPage(null);

        if (!this.symbolLayer) {
            this.symbolLayer = Fusion.SymbolLayer.CreateOrGet(this.symbolLayerName, {
                map: this.getMap().oMapOL,
                symbolStyle: this.symbolStyle,
                symbolRadius: this.symbolRadius,
                showDirection: this.showDirection,
                directionStyle: this.directionStyle,
                directionLength: this.directionLength
            });
        }

        this.symbolLayer.registerListener(this);
        this.symbolLayer.activateDragControl();
        this.activated = true;
        var url = Fusion.getFusionURL() + 'widgets/GoogleStreetViewer/GoogleStreetView.php';
        var aimTarget = Fusion.getWidgetById(this.targetWindow);
        var pageElement = $(this.targetWindow);

        var params = [];
        params.push('ts=' + new Date().getTime());
        params.push('locale=' + Fusion.locale);

        if (aimTarget || pageElement) {
            params.push("popup=false");
        }
        else {
            params.push("popup=true");
        }

        params = params.concat(this.additionalParameters);

        if (url.indexOf('?') < 0) {
            url += '?';
        }
        else if (url.slice(-1) != '&') {
            url += '&';
        }

        url += params.join('&');

        if (aimTarget) {
            aimTarget.setContent(url);
        }
        else if (pageElement) {
            pageElement.src = url;
        }
        else {
            window.open(url, this.targetWindow, this.windowFeatures);
        }
    },

    deactivate: function () {
        if (!this.activated) return;

        this.activated = false;

        if(this.symbolLayer != null) {
            this.symbolLayer.deregisterListener(this);
        }

        this.hideMessage(this.csTransformErrorMessage);
        this.hideMessage(this.serviceUnavailableMessage);
        this.hideMessage(this.googleBaseMapErrorMessage);

        if(this.GoogleSVPage != null) {
            this.GoogleSVPage.deregisterViewChangeListener(this);
            this.GoogleSVPage.hidePage();
        }
    },

    onSymbolChanged: function (position) {
        if (!this.GoogleSVPage || !this.symbolLayer || !position) {
            return;
        }

        var lonlat = new OpenLayers.LonLat(position.x, position.y);
        var targetLonLat = new OpenLayers.LonLat(position.targetX, position.targetY);
        var mapProj = this.getMap().projection;

        //Goolge Street View always use the CS EPSG:4326,
        //The Symbol layer always use the same CS as the map
        //must consider if the transform is not correct
        try {
            lonlat.transform(mapProj, this.targetCs);
            targetLonLat.transform(mapProj, this.targetCs);

            this.hideMessage(this.csTransformErrorMessage);

            // Transform Z
            var unitsPerMeter = this.getUnitsPerMeter(this.targetCs);
            var unitsPerMeterInMap = this.getUnitsPerMeter(mapProj);

            this.GoogleSVPage.setView(lonlat.lon, lonlat.lat, position.z * unitsPerMeter / unitsPerMeterInMap,
            targetLonLat.lon, targetLonLat.lat, position.targetZ * unitsPerMeter / unitsPerMeterInMap);

        } catch (e) {
            this.showMessage(this.csTransformErrorMessage);
        }
    },

    getUnitsPerMeter: function (cs) {
        if (cs.proj == null) return 1;
        // This is because Proj4js has a defect about unit. 
        // If CS doesn't include a unit and it has an angular unit, the angular unit will be set to CS unit.
        var unitIndex = cs.proj.unitIndex;
        if (unitIndex == null) {
            var unit = cs.proj.units;
            if (!unit) return 1;    // No units
            unitIndex = Fusion.unitFromName(unit);
            cs.proj.unitIndex = unitIndex;
        }
        return Fusion.fromMeter(unitIndex, 1);
    },

    onStreetViewChange: function (position) {
        if (!this.activated || !this.GoogleSVPage || !position) {
            return;
        }

        var x = position.x;
        var y = position.y;
        var z = position.z;

        var targetX = position.targetX;
        var targetY = position.targetY;
        var targetZ = position.targetZ;

        var source = new OpenLayers.LonLat(x, y);
        var target = new OpenLayers.LonLat(targetX, targetY);
        var mapProj = this.getMap().projection;

        //Goolge Street View always use the CS EPSG:4326,
        //The Symbol layer always use the same CS as the map
        //must consider if the transform is not correct
        try {
            source.transform(this.targetCs, mapProj);
            target.transform(this.targetCs, mapProj);

            this.hideMessage(this.csTransformErrorMessage);

            x = source.lon;
            y = source.lat;
            targetX = target.lon;
            targetY = target.lat;
            // Transform Z
            var unitsPerMeter = this.getUnitsPerMeter(this.targetCs);
            var unitsPerMeterInMap = this.getUnitsPerMeter(mapProj);
            z = z * unitsPerMeterInMap / unitsPerMeter;
            targetZ = targetZ * unitsPerMeterInMap / unitsPerMeter;

            if (!this.getMap().getCurrentExtents().contains(x, y)) {
                this.getMap().zoom(x, y, 1);
            }

            this.symbolLayer.updateSymbolPosition(x, y, z, targetX, targetY, targetZ, this);

        } catch (e) {
            this.showMessage(this.csTransformErrorMessage);
        }
    },

    setGoogleSVPage: function (GoogleSVWindow) {
        if (this.GoogleSVPage != null) {
            this.GoogleSVPage.deregisterViewChangeListener(this);
        }

        this.GoogleSVPage = GoogleSVWindow;

        if (GoogleSVWindow != null) {

            this.GoogleSVPage.registerViewChangeListener(this);

            if (this.GoogleSVPage.camera) {
                this.onStreetViewChange(this.GoogleSVPage.camera);
            }
            else if (!this.symbolLayer && !this.symbolLayer.position) {
                this.onSymbolChanged(this.symbolLayer.position);
            }
            else {
                var currentCenter = this.getMap().getCurrentCenter();
                var viewCenter = this.getMap().geoToPix(currentCenter.x, currentCenter.y);
                var aboveCenter = this.getMap().pixToGeo(viewCenter.x, viewCenter.y - 10);
                //this only happens in the pageload phase of the GoogleSVPage
                //and should initialize both the Street View location and the Symbol Layer location
                this.symbolLayer.updateSymbolPosition(currentCenter.x, currentCenter.y, 0, aboveCenter.x, aboveCenter.y, 0, null);
            }
        }
        else if (this.activated) {
            var instances = Fusion.Widget.uiInstances[this.type];
            for (var i = 0; i < instances.length; i++) {
                var instance = instances[i];
                if (instance.shouldActivateWith(this)) {
                    instance.deactivate();
                }
            }
            this.deactivate();
        }
    },
    showMessage: function (message) {
        this.getMap().message.warn(message);
    },
    hideMessage: function (message) {
        if (this.getMap().message) {
            this.getMap().message.hideDesignatedMessage(message);
        }
    }
});

if (typeof(Proj4js) != 'undefined') {
    Proj4js.reportError = function (msg) {
        if(Fusion.Widget.GoogleStreetViewer.hasGSV) throw msg;
        //console.log(msg);
    };
}

/**
 * Fusion.Widget.Help
 *
 * $Id: Help.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Help
 *
 * Display a user help page.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Help = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    
    /* popup window initialization parameters */
    sFeatures: 'menubar=no,location=no,resizable=no,status=no',

    /* the frame or window name to target.  If set to the Name of a
     * task pane widget, then it will appear in the task pane
     */
    target: 'HelpWindow',
    
    /* the url to open.  If specified, it is relative to the
     * application, not fusion
     */
    baseUrl: null,
    
    /* the default url 
     */
    defaultUrl: 'widgets/Help/Help.html',
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.target = json.Target ? json.Target[0] : "HelpWindow";
        this.baseUrl = json.Url ? json.Url[0] : this.defaultUrl;
        if (this.baseUrl == this.defaultUrl) {
          this.baseUrl = Fusion.getFusionURL() + this.baseUrl;
        }
        
        /* this widget is always enabled unless it was explicitly disabled
         * in the widget tag
         */
        if (!widgetTag.Disabled || widgetTag.Disabled[0].toLowerCase() != 'true') {
            this.enable();                   
        }
    },
    
    activate: function() {
        var url = this.baseUrl;
        
        var map = this.getMapLayer();
        var params = [];
        params.push('LOCALE='+Fusion.locale);
        params.push('SESSION='+map.getSessionID());
        params.push('MAPNAME='+map.getMapName());
        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        
        /* check to see if this is going into a task pane */
        var taskPaneTarget = Fusion.getWidgetById(this.target);
        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
        } else {
            /* check to see if it is going into a frame in the page */
            var pageElement = $(this.target);
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                /* open in a window */
                window.open(url, this.target, this.sWinFeatures);
            }
        }
    }
});
/**
 * Fusion.Widget.InitialMapView
 *
 * $Id: InitialMapView.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.InitialMapView
 *
 * Restore the map to it's full extents determined when it was initially loaded.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/
Fusion.Widget.InitialMapView = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    viewType: 'initial',
    initializeWidget: function(widgetTag) {           
        var json = widgetTag.extension;
        if (json.ViewType && (json.ViewType[0].toLowerCase() == 'full')) {
          this.viewType = 'full';
        }
    },
    
    activate: function() {
        if (this.viewType == 'full') {
          this.getMap().fullExtents();
        } else {
          var mapWidget = this.getMap();
          mapWidget.setExtents(mapWidget.initialExtents);
        }
    }
});
/**
 * Fusion.Widget.InvokeScript
 *
 * $Id: InvokeScript.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.InvokeScript
 *
 * Executes an arbitrary piece of JavaScript code
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.InvokeScript = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    script: null,
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.script = json.Script ? json.Script[0] : '';
    },

    /**
     * called when the button is clicked by the Fusion.Widget widget
     */
    activate: function() {
        eval(this.script);
    }
});/**
 * Fusion.Widget.InvokeURL
 *
 * $Id: InvokeURL.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /***************************************************************************
 * Class: Fusion.Widget.InvokeURL
 *
 * A widget that will open the configured URL in the target element.
 * 
 * If the Target property points to TaskPane widget, the task will be listed in
 * the menu list of the TaskPane and loaded there.
 * Otherwise if the target is an existing HTML elementt in the page it will be 
 * loaded there, otherwise it will open a new window with that name.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.InvokeURL = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    sFeatures: 'menubar=no,location=no,resizable=no,status=no',

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "InvokeUrlWindow";
        this.sBaseUrl = json.Url[0];  //must be supplied
        
        this.bSelectionOnly = (json.DisableIfSelectionEmpty &&
                           (json.DisableIfSelectionEmpty[0] == 'true' ||
                            json.DisableIfSelectionEmpty[0] == '1')) ? true : false;
                            
        this.additionalParameters = [];
        if (json.AdditionalParameter) {
            for (var i=0; i<json.AdditionalParameter.length; i++) {
                var p = json.AdditionalParameter[i];
                var k = p.Key[0];
                var v = p.Value[0];
                this.additionalParameters.push(k+'='+encodeURIComponent(v));
            }
        }
        
        this.enable = Fusion.Widget.InvokeURL.prototype.enable;
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.enable, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.enable, this));
        this.disable();
    },

    enable: function() {
        var map = this.getMap();
        var widgetLayer = this.getMapLayer();
        if (this.bSelectionOnly || !map) {
            if (map && map.hasSelection()) {
                if (widgetLayer._sQueryfile) {
                  this.additionalParameters.push('queryfile='+widgetLayer._sQueryfile);
                }
                if (this.action) {
                    this.action.setEnabled(true);
                } else {
                    Fusion.Widget.prototype.enable.apply(this,[]);
                }
            } else {
                if (this.action) {
                    this.action.setEnabled(false);
                } else {
                    this.disable();
                }
            }
        } else {
            if (this.action) {
                this.action.setEnabled(true);
            } else {
                Fusion.Widget.prototype.enable.apply(this,[]);
            }
        }
    },
    
    activate: function() {
        var url = this.sBaseUrl;
        //add in other parameters to the url here
        
        var map = this.getMapLayer();
        var params = [];
        params.push('locale='+Fusion.locale);
        params.push('session='+map.getSessionID());
        params.push('mapname='+map.getMapName());
        params.push('ts='+new Date().getTime());
        params = params.concat(this.additionalParameters);
        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
        } else {
            var pageElement = $(this.sTarget);
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                window.open(url, this.sTarget, this.sFeatures);
            }
        }
    }
});
/**
 * Fusion.Widget.LayerManager
 *
 * $Id: LayerManager.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /***************************************************************************
 * Class: Fusion.Widget.LayerManager
 * 
 * Displays a LayerManager of all the layers in the map as a collapsable tree.
 *
 * ShowRootFolder (boolean, optional)
 *
 * This controls whether the tree will have a single root node that
 * contains the name of the map as its label.  By default, the root
 * node does not appear.  Set to "true" or "1" to make the root node
 * appear.
 *
 * RootFolderIcon: (string, optional)
 *
 * The url to an image to use for the root folder.  This only has an
 * affect if ShowRootFolder is set to show the root folder.
 *
 * LayerThemeIcon: (string, optional)
 *
 * The url to an image toTopography use for layers that are currently themed.
 *
 * DisabledLayerIcon: (string, optional)
 *
 * The url to an image to use for layers that are out of scale.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.LayerManager = OpenLayers.Class(Fusion.Widget,  {
    currentNode: null,
    bIsDrawn: false,
    map: null, 
    defTemplate: '<li id="{id}" class="jxListItemContainer jxLmanLayer"><a class="jxListItem lmanLabel" href="javascript:void(0);" alt="{label}" title="{label}"><input type="checkbox" class="lmanVisCheck" {visSelect}><img class="lmanLayerIcon" src="'+Jx.aPixel.src+'" style="background-image: url({icon}); background-position: {iconOffset}; width:16px; height:16px;"><span class="lmanLayerLabel">{label}</span><img src="{delIcon}" class="lmanDelIcon"><img src="{infoIcon}" class="lmanInfoIcon"></a></li>',
    
    initializeWidget: function(widgetTag) {
        //console.log("initializeWidget");
        var json = widgetTag.extension;
        this.delIconSrc = json.DeleteIcon ? json.DeleteIcon[0] : 'images/icons/select-delete.png';
        this.infoIconSrc = json.LayerInfoIcon ? json.LayerInfoIcon[0] : 'images/icons/tree_layer_info.png';
        this.template = json.Template ? json.Template[0] : this.defTemplate;
    
        Fusion.addWidgetStyleSheet(widgetTag.location + 'LayerManager/LayerManager.css');
        this.cursorNormal = ["url('images/grab.cur'),move", 'grab', '-moz-grab', 'move'];
        this.cursorDrag = ["url('images/grabbing.cur'),move", 'grabbing', '-moz-grabbing', 'move'];
        this.map = this.getMap();
        this.map.registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.mapLoaded, this));
        this.map.registerForEvent(Fusion.Event.MAP_RELOADED, OpenLayers.Function.bind(this.mapReLoaded, this));
        this.map.registerForEvent(Fusion.Event.MAP_SCALE_RANGE_LOADED, OpenLayers.Function.bind(this.scaleRangeLoaded, this));
        // update changes to the legend in this widget
        this.getMapLayer().registerForEvent(Fusion.Event.LAYER_PROPERTY_CHANGED, OpenLayers.Function.bind(this.layerChanged,this));
    },
    
    scaleRangeLoaded: function() {
        this.draw();
    },
    mapLoaded: function() {
        //this.draw();
    },
    
    layerChanged: function(eventId, layer) {
        this.updateLayer(this.getMapLayer());
        this.updateSessionMapFile();
    },
    mapReLoaded: function(){
        this.draw();
   },
   
   /**
     * remove the dom objects representing the legend layers and groups
     */
    clear: function(node) {
        //console.log("clear");
        while (node.childNodes.length > 0) {
          this.clear(node.childNodes[0]);
            node.removeChild(node.childNodes[0]);
        }
    },
  
    /**
     * Draws the layer manager
     *
     * @param r Object the reponse xhr object
     */
    draw: function(r) {
        //console.log("draw");
      //clear the domOBj
      var contents = this.domObj.getChildren();
      for (var i=0; i<contents.length; ++i) {
        contents[i].destroy();
      }
      
      //this processes the OL layers
      var map = this.getMap();

      for (var i=0; i<map.aMaps.length; ++i) {
        
        var mapBlock = new Element('div', {
            //'class': 'jxLmanHandle',
            id: 'mapBlock_'+i
        });
        mapBlock.map = map;
        var mapHandle = new Element('a', {
            html: map.aMaps[i]._sMapTitle,
            'class': 'jxLmanHandle jxListItem'
        });
        
        mapBlock.adopt(mapHandle);
        this.processMapBlock(mapBlock, map.aMaps[i]);
        this.domObj.adopt(mapBlock);
      }
      
      //create the sortables for re-arranging whole maps
      //TODO: not yet working
      /*
      if (map.aMaps.length >1) {
        var sortableOptions = {
            constrain: true,
            clone: false,
            revert: true,
            onComplete: OpenLayers.Function.bind(this.updateMapBlock, this, map)
        };
        var mySortables = new Sortables(new Elements(this.domObj.getChildren()), sortableOptions);
      }
      */
    },

    processMapBlock: function(mapBlock, map) {
      //console.log("processMapBlock");
      map.layerList = new Jx.ListView({
          'class': 'jxLmanSet',
          'id': 'fusionLayerManager_'+map.getMapName()
      }).addTo(mapBlock);
      
      map.layerPrefix = 'layer_';   //TODO make this unique for each block
      //this process all layers within an OL layer
      var layers = map.aLayers;
      for (var i=0; i<layers.length; ++i) {
        var listItem = this.createItem(layers[i]);
        listItem.addTo(map.layerList);
      }

      var sortableOptions = {
          constrain: true,
          clone: false,
          revert: true,
          onComplete: OpenLayers.Function.bind(this.updateLayer, this, map)
      };
      var mySortables = new Sortables(map.layerList, sortableOptions);
    },
   
    createItem: function(layer) {
      var scale = layer.oMap.getScale();
      var range = layer.getScaleRange(scale);
      var iconUrl = Jx.aPixel.src;
      var iconOffset = '0px 0px';
      var label = layer.legendLabel;
      if (range && range.styles.length>0) {
          var style = range.styles[0];//TODO: handle multiple styles?
          var iconX = 0;
          var iconY = 0;
          if (style && style.iconX >= 0 && style.iconY >= 0) {
              iconX = -1 * (style.iconX);
              iconY = -1 * (style.iconY);
          }
          iconUrl = style.iconOpt.url;
          iconOffset = iconX + 'px ' + iconY + 'px';
          if (style.legendLabel) {
            label = style.legendLabel;
          }
      }
      
      var templStr = this.template.substitute({
        'id': 'layer_'+layer.uniqueId,
        'label': label,
        'icon': iconUrl,
        'iconOffset': iconOffset,
        'delIcon': this.delIconSrc,
        'infoIcon': this.infoIconSrc,
        'visSelect': layer.visible? 'CHECKED': ''
      });
    
    var listItem = new Jx.ListItem({template: templStr });
    
    var icons = listItem.domObj.getElements('img');
    var infoIcon = icons[2];
    var delIcon = icons[1];
    
    var layerInfo = layer.oMap.getLayerInfoUrl(layer.layerName);
    if (!layerInfo) {
        infoIcon.style.display = "none";
    }

    OpenLayers.Event.observe(infoIcon, 'click', OpenLayers.Function.bind(this.showLayerInfo, this, layer));
    OpenLayers.Event.observe(delIcon, 'click', OpenLayers.Function.bind(this.deleteLayer, this, layer));
    OpenLayers.Event.observe(listItem.domObj.getElement('input'), 'click', OpenLayers.Function.bind(this.visChanged, this, layer));
    
    OpenLayers.Event.observe(listItem.domObj, 'mouseover', OpenLayers.Function.bind(this.setGrabCursor, this));
    OpenLayers.Event.observe(listItem.domObj, 'mousedown', OpenLayers.Function.bind(this.setDragCursor, this));
    OpenLayers.Event.observe(listItem.domObj, 'mouseout', OpenLayers.Function.bind(this.setNormalCursor, this));
    
    OpenLayers.Event.observe(listItem.domObj, 'mouseover', OpenLayers.Function.bind(this.setHandleVis, this, delIcon, infoIcon));
    OpenLayers.Event.observe(listItem.domObj, 'mouseout', OpenLayers.Function.bind(this.setHandleHide, this, delIcon, infoIcon));
    
    return listItem;
  },
  
  setHandleVis: function(delIcon, infoIcon) {
    delIcon.style.visibility = 'visible';
    infoIcon.style.visibility = 'visible';
  },
  
  setHandleHide: function(delIcon, infoIcon) {
    delIcon.style.visibility = 'hidden';
    infoIcon.style.visibility = 'hidden';
  },
  
  setGrabCursor: function(ev) {
    var targetLI = (new Event(ev)).target;
    this.setCursor(this.cursorDrag, targetLI);
  },
  
  setDragCursor: function(ev) {
    var targetLI = (new Event(ev)).target;
   this.setCursor(this.cursorDrag, targetLI);
  },
  
  setNormalCursor: function(ev) {
    var targetLI = (new Event(ev)).target;
    this.setCursor('auto', targetLI);
  },
  
  setCursor: function(cursor, domObj) {
      this.cursor = cursor;
      if (cursor && cursor.length && typeof cursor == 'object') {
          for (var i = 0; i < cursor.length; i++) {
              domObj.style.cursor = cursor[i];
              if (domObj.style.cursor == cursor[i]) {
                  break;
              }
          }
      } else if (typeof cursor == 'string') {
          domObj.style.cursor = cursor;
      } else {
          domObj.style.cursor = 'auto';  
      }
  },
  
  updateLayer: function(map) {
   //console.log("updateLayer");
    //reorder the layers in the client as well as the session
    var aLayerIndex = [];
    var aIds = [];
    var layerList = map.layerList.listObj.getChildren();
    var nLayers = layerList.length;
    for (var i=0; i<nLayers; ++i) {
      aIds[i] = layerList[i].id.split('_');
      var index = parseInt(aIds[i].pop());
      aLayerIndex.push(index);
      layerList[i].id = '';
    }
    
    //reset the ID's on the LI elements to be in order
    for (var i=0; i<layerList.length; ++i) {
      var node = layerList[i];
      aIds[i].push(i);
      node.id = aIds[i].join('_');
      //node.childNodes[2].checked = node.layer.isVisible();
    }
    
    //check tos ee if the layer indexes have been modified
    var indexModified = false;
    if (aLayerIndex.length == map.aLayers.length) {
      for (var i=0; i<aLayerIndex.length; ++i) {
        if (aLayerIndex[i] != i) {
          indexModified = true;
          break;
        }
      }
    } else {
      indexModified = true;
    }

    if (indexModified) {
        map.reorderLayers(aLayerIndex);
    }
  },
   
  updateMapBlock: function(map) {
    //reorder the OL layers and the <Map> elements in AppDef
  },
  
  deleteLayer: function(layer, ev) {
   // console.log("deleteLayer");
    var targetLI = (new Event(ev)).target.parentNode.parentNode;
    $(targetLI).dispose();
    layer.oMap.layerList.remove(targetLI);
    
    this.oMap.layerRoot.deleteLayer(layer.uniqueId);
    this.updateLayer(layer.oMap);
  },
  
  showLayerInfo: function(layer, ev) {
    var layerInfoUrl = layer.oMap.getLayerInfoUrl(layer.layerName);
    if (layerInfoUrl) {
      window.open(layerInfoUrl);
    }
  },
  
  visChanged: function(layer, ev) {
    var target = (new Event(ev)).target;
    if (target.checked) {
      layer.show();
    } else {
      layer.hide();
    }
  },
  
  updateSessionMapFile: function(){
   // console.log("updateSessionMapFile");
    var widgetLayer = this.getMapLayer();
    var sessionId = widgetLayer.getSessionID();

    // get all layers
    var oLayers = widgetLayer.aLayers;
    var aLayerNames = [];
    var visibleLayers = [];
    for(var i=0;i<oLayers.length;i++){
        aLayerNames.push(oLayers[i].layerName);
        if(oLayers[i].visible == true){
            visibleLayers.push(oLayers[i].layerName);
        }
    }

    // prepare ajax req
    var params =  '&session='+sessionId+'&mapname='+ widgetLayer.getMapName()+'&visLayers='+visibleLayers+'&layers='+aLayerNames;
    var options = {parameters: params};

    // fire the request no need to return
    var url = 'layers/' + widgetLayer.arch + '/' + Fusion.getScriptLanguage() + "/updateSessionMapFile." + Fusion.getScriptLanguage()
    Fusion.ajaxRequest(url, options);
  }

});
/**
 * Fusion.Widget.Legend
 *
 * $Id: Legend.js 2913 2015-06-04 14:22:34Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Legend
 *
 * A widget to display a legend of all layers.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Legend = OpenLayers.Class(Fusion.Widget,  {

    /**
     * Constant: defaultLayerDWFIcon
     * {String} The default image for DWF layer
     */
    defaultLayerDWFIcon: 'images/icons/legend-DWF.png',
    
    /**
     * Constant: defaultLayerRasterIcon
     * {String} The default image for Raster layer
     */
    defaultLayerRasterIcon: 'images/icons/legend-raster.png',
    
    /**
     * Constant: defaultLayerThemeIcon
     * {String} The default image for layers that are currently themed.
     */
    defaultLayerThemeIcon: 'images/icons/legend-theme.png',

    /**
     * Constant: defaultDisabledLayerIcon
     * {String} The default image for layers that are out of scale.
     */
    defaultDisabledLayerIcon: 'images/icons/legend-layer.png',

    /**
     * Constant: defaultRootFolderIcon
     * {String} The default image for the root folder
     */
    defaultRootFolderIcon: 'images/icons/legend-map.png',
    
    /**
     * Constant: defaultLayerInfoIcon
     * {String} The default image for layer info
     */
    defaultLayerInfoIcon: 'images/icons/layer-info.png',
    
    /**
     * Constant: defaultGroupInfoIcon
     * {String} The default image for groupd info
     */
    defaultGroupInfoIcon: 'images/icons/group-info.png',
    
    /**
     * Constant: outOfRangeIcon
     * {String}: The default image for layers outside the current visible scale range
     */
    outOfRangeIcon: 'images/icons/out-of-range.png',
    
    initializeWidget: function(widgetTag) {
        // TODO: maybe it's a good idea to do a function like Fusion.Widget.BindRenderer.. for limit the code
        // duplication if we plan to apply this pattern to others widgets
        Fusion.addWidgetStyleSheet(widgetTag.location + 'Legend/Legend.css');
        
        // TODO: maybe it's a good idea to do a function like Fusion.Widget.BindRenderer.. for limit the code
        //       duplication if we plan to apply this pattern to others widgets
        var json = widgetTag.extension;
        if (json.LegendRenderer)
        {
            var renderer = eval(json.LegendRenderer[0]);
            if (renderer && renderer.prototype.CLASS_NAME
                && renderer.prototype.CLASS_NAME == "Fusion.Widget.Legend.LegendRenderer") {
                this.renderer = new renderer(this, widgetTag);
            } else if (typeof renderer == "function") {
                var renderFunction = renderer;
                this.renderer = new Fusion.Widget.Legend.LegendRenderer(this);
                this.renderer.mapLoaded = renderFunction;
                this.renderer.mapReloaded = renderFunction;
                this.renderer.mapLoading = false;
            } else {
                this.renderer = new Fusion.Widget.Legend.LegendRendererDefault(this, widgetTag);
            }
        } else {
            this.renderer = new Fusion.Widget.Legend.LegendRendererDefault(this, widgetTag);
        }

        if (this.renderer.mapReloaded)
            this.getMap().registerForEvent(Fusion.Event.MAP_RELOADED,
                                           OpenLayers.Function.bind(this.renderer.mapReloaded, this.renderer));
        if (this.renderer.mapLoading)
            this.getMap().registerForEvent(Fusion.Event.MAP_LOADING,
                                           OpenLayers.Function.bind(this.renderer.mapLoading,this.renderer));
        if (this.renderer.mapLoaded)
            this.getMap().registerForEvent(Fusion.Event.MAP_LOADED,
                                           OpenLayers.Function.bind(this.renderer.mapLoaded, this.renderer));
        if (this.renderer.scaleRangesLoaded)
            this.getMap().registerForEvent(Fusion.Event.MAP_SCALE_RANGE_LOADED,
                                           OpenLayers.Function.bind(this.renderer.scaleRangesLoaded, this.renderer));
    }
});

/* Class: Fusion.Widget.Legend.LegendRenderer
 * This is a class designed to help users to create their own renderer
 * for customize the legend.
 */
Fusion.Widget.Legend.LegendRenderer = OpenLayers.Class(
{
     /**
     * Property: oLegend
     * {<Fusion.Widget.Legend>} The parent widget that uses
     *                                  the renderer.
     */
    oLegend: null,

    /**
     * Property: layerRoot
     * {Groups} The groups of all layers.
     *
     */
    layerRoot: null,

    initialize: function(legend) {
        this.oLegend = legend;
        this.layerRoot = this.getMap().layerRoot;
    },

    /**
     * Method: renderLegend
     * Abstract method that have the main purpose to draw the legend. This method
     * should be implemented by all concrete class.
     *
     */
    renderLegend: function() {},
    
    /**defaultDisabledLayerIcon
     * Method: mapLoading
     * Abstract method that handle the event: Fusion.Event.MAP_LOADING. This method
     * is optional.
     *
     */
    mapLoading: function() {},

    /**
     * Method: mapLoaded
     * Abstract method that handle the event: Fusion.Event.MAP_LOADED. This method
     * occur only at the first load of the map and should be implemented by all concrete class.
     *
     */
    mapLoaded: function() {},

     /**
     * Method: mapReloaded
     * Abstract method that handle the event: Fusion.Event.MAP_RELOADED. This method
     * should be implemented by all concrete class.
     *
     */
    mapReloaded: function() {},

     /**
     * Method: mapRefresh
     * Abstract method that handle the event: Fusion.Event.MAP_LAYER_ORDER_CHANGED. This method
     * should be implemented by all concrete class.
     *
     */
    mapRefresh: function() {},

    /**
     * Method: getMap
     * Helper method to obtains the map.
     *
     * Returns:
     * {<Fusion.Maps>} The map that uses the SelectionPanel Widget.
     */
    getMap: function() {
        return this.oLegend.getMap();
    },

    CLASS_NAME: "Fusion.Widget.Legend.LegendRenderer"
});


/* Class: Fusion.Widget.Legend.LegendRendererDefault
 * This class provide a default legend as a collapsable tree.
 *
 */

Fusion.Widget.Legend.LegendRendererDefault = OpenLayers.Class(Fusion.Widget.Legend.LegendRenderer,
{
    /**
     * Property: showRootFolder
     * {Boolean} This controls whether the tree will have a single root node that
     * contains the name of the map as its label.  By default, the root node does
     * not appear.  Set to "true" or "1" to make the root node appear.
     */
    showRootFolder: false,

    /**
     * Property: currentNode
     * {Jx.TreeNode} The current selected node.
     */
    currentNode: null,
    
    /**
     * Property: bIsDrawn
     * {Boolean} Determine if the map is drawn.
     */
    bIsDrawn: false,

    /**
     * Property: updateDelay
     * {Integer} number of milliseconds to delay the update of legend
     */
    updateDelay: 500,

    /**
     * Property: targetFolder
     * {Jx.TreeFolder} The current TreeFolder that the mouse will interact with.
     */
    targetFolder: null,

    /**
     * Property: bIncludeVisToggle
     * {Boolean} Determine if non-visible layer must be draw in the legend.
     */
    bIncludeVisToggle: true,
    offsetsCalculated: false,
   
    initialize: function(legend, widgetTag) {   
        Fusion.Widget.Legend.LegendRenderer.prototype.initialize.apply(this, [legend]);

        var json = widgetTag.extension;
        this.imgLayerDWFIcon = json.LayerDWFIcon ? json.LayerDWFIcon[0] : this.oLegend.defaultLayerDWFIcon;
        this.imgLayerRasterIcon = json.LayerRasterIcon ? json.LayerRasterIcon[0] : this.oLegend.defaultLayerRasterIcon;
        this.imgLayerThemeIcon = json.LayerThemeIcon ? json.LayerThemeIcon[0] : this.oLegend.defaultLayerThemeIcon;
        this.imgDisabledLayerIcon = json.DisabledLayerIcon ? json.DisabledLayerIcon[0] : this.oLegend.defaultDisabledLayerIcon;
        this.imgLayerInfoIcon = json.LayerInfoIcon ? json.LayerInfoIcon[0] : this.oLegend.defaultLayerInfoIcon;
        this.imgGroupInfoIcon = json.GroupInfoIcon ? json.GroupInfoIcon[0] : this.oLegend.defaultGroupInfoIcon;

        //not used?
        //this.layerInfoURL = json.LayerInfoURL ? json.LayerInfoURL[0] : '';
        this.selectedLayer = null;
        
        this.selection = new Jx.Selection({
            onSelect: function(item) {
                var treeItem = item.retrieve('jxTreeItem');
                var data = treeItem.options.data;
                if (data instanceof Fusion.Layers.Group) {
                    this.getMap().setActiveLayer(null);
                } else {
                    this.getMap().setActiveLayer(data);
                }
            }.bind(this)
        });
       
        this.oTree = new Jx.Tree({
            template: '<ul class="jxTreeRoot fusionLegendTreeRoot"></ul>',
            selection:this.selection
        }).addTo(this.oLegend.domObj);
       
        this.hideInvisibleLayers = (json.HideInvisibleLayers && json.HideInvisibleLayers[0]) == 'true' ? true : false;
        //don't show the root folder by default
        this.showRootFolder = (json.ShowRootFolder && json.ShowRootFolder[0] == 'true') ? true:false;
        //do show the map folder by default
        this.showMapFolder = (json.ShowMapFolder && json.ShowMapFolder[0] == 'false') ? false:true;
        
        if (!this.showRootFolder) {
            //console.log('supressing root folder');
            this.oRoot = this.oTree;
        } else {
           // console.log('showing root folder');
            var opt = {
                label: OpenLayers.i18n('defaultMapTitle'),
                // contextMenu: this.getContextMenu(),
                open: true
            };
            this.oRoot = new Jx.TreeFolder(opt);
            this.oTree.add(this.oRoot);
            // this.oRoot.options.contextMenu.add([
            //     new Jx.Menu.Item({
            //         label: OpenLayers.i18n('collapse'),
            //         onClick: OpenLayers.Function.bind(this.collapseBranch, this, this.oRoot)
            //     }),
            //     new Jx.Menu.Item({
            //         label: OpenLayers.i18n('expand'),
            //         onClick: OpenLayers.Function.bind(this.expandBranch, this, this.oRoot)
            //     })]
            // );
        }
        
        this.extentsChangedWatcher = this.update.bind(this);
    },
    
    getContextMenu: function() {
        return new Jx.Menu.Context(this.name).add([
            new Jx.Menu.Item({
                label: OpenLayers.i18n('refresh'),
                onClick: OpenLayers.Function.bind(this.update, this)
            }),
            new Jx.Menu.Item({
                label: OpenLayers.i18n('collapseAll'),
                onClick: OpenLayers.Function.bind(this.collapseAll, this)
            }),
            new Jx.Menu.Item({
                label: OpenLayers.i18n('expandAll'),
                onClick: OpenLayers.Function.bind(this.expandAll, this)
            })]
        );
    },
    
    expandAll: function(folder) {
        this.oTree.items().each(function(item){
            if (item instanceof Jx.TreeFolder) {
                this.recurseTree('expand', item);
            }
        },this);
        if (this.showRootFolder) {
          this.oRoot.expand();
        }
    },
    
    collapseAll: function(folder) {
        this.oTree.items().each(function(item){
            if (item instanceof Jx.TreeFolder) {
                this.recurseTree('collapse', item);
            }
        },this);
        if (this.showRootFolder) {
          this.oRoot.collapse();
        }
    },
    
    collapseBranch: function(folder) {
        folder.collapse();
    },
    
    expandBranch: function(folder) {
        folder.expand();
    },
    
  /**
     * recursively descend the tree applying the request operation which is either 'collapse' or 'expand'
     *
     * @param op the operation to execute
     * @param the folder to operate on
     */
    recurseTree: function(op, folder) {
        folder.items().each(function(item){
            if (item instanceof Jx.TreeFolder) {
                this.recurseTree(op, item);
                item[op]();
            }
        },this);
    },
   
    scaleRangesLoaded: function() {
        this.layerRoot = this.getMap().layerRoot;
        this.renderLegend();
    },
    
    mapLoading: function() {
        this.getMap().deregisterForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, this.extentsChangedWatcher);
        this.clear();
    },
   
    mapLoaded: function() {
        this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, this.extentsChangedWatcher);
        var baseLayer = this.oLegend.getMapLayer(); 
        baseLayer.registerForEvent(Fusion.Event.MAP_LAYER_ORDER_CHANGED, OpenLayers.Function.bind(this.mapRefresh, this));
        this.layerRoot = this.getMap().layerRoot;
        //this.renderLegend();
    },
    
    mapReloaded: function() {
        this.layerRoot = this.getMap().layerRoot;
        this.renderLegend();
    },
    
    mapRefresh: function() {
        var baseLayer = this.oLegend.getMapLayer();
        baseLayer.parseLayers();
        this.layerRoot = this.getMap().layerRoot;
        this.renderLegend();
    },
    
    /**
     * Callback for legend XML response. Creates a list of layers and sets up event
     * handling. Create groups if applicable.
     * TODO: error handling
     *
     * @param r Object the reponse xhr object
     */
    renderLegend: function(r) {
        this.bIsDrawn = false;
        this.clear();
        if (this.showRootFolder) {
            this.oRoot.setLabel(this.getMap().mapGroup.mapId);
        }
        
        var startGroup = this.layerRoot;
        if (!this.showMapFolder)
            startGroup = this.layerRoot.groups[0];
        
        if (!startGroup.legend) {
            startGroup.legend = {};
            startGroup.legend.treeItem = this.oRoot;
        }
        
        if (this.layerRoot.groups.length > 1) { //Multi-map
            if (this.showMapFolder) {
                for (var i = 0; i < this.layerRoot.groups.length; i++) {
                    this.processMapGroup(this.layerRoot.groups[i], this.oRoot);
                }
            } else {
                for (var i = 0; i < this.layerRoot.groups.length; i++) {
                    var mapRoot = this.layerRoot.groups[i];
                    if (!mapRoot.legend) {
                        mapRoot.legend = {};
                        mapRoot.legend.treeItem = this.oRoot;
                    }
                    for (var j = 0; j < mapRoot.groups.length; j++) {
                        this.processMapGroup(mapRoot.groups[j], this.oRoot);
                    }
                    for (var j = 0; j < mapRoot.layers.length; j++) {
                        this.processMapLayer(mapRoot.layers[j], this.oRoot);
                    }
                }
            }
        } else { //Single-map
            if (!startGroup.legend) {
                startGroup.legend = {};
                startGroup.legend.treeItem = this.oRoot;
            }
            for (var i = 0; i < startGroup.groups.length; i++) {
                this.processMapGroup(startGroup.groups[i], this.oRoot);
            }
            for (var i = 0; i < startGroup.layers.length; i++) {
                this.processMapLayer(startGroup.layers[i], this.oRoot);
            }
        }
        
        this.bIsDrawn = true;
        this.update();
    },

    processMapGroup: function(group, folder) {
        if (group.displayInLegend) {
            /* make a 'namespace' on the group object to store legend-related info */
            group.legend = {};
            var opt = {
                label: group.legendLabel,
                open: group.expandInLegend,
                // contextMenu: this.getContextMenu(),
                checked: group.visible
            };
            var treeItem = new Fusion.Widget.Legend.TreeFolder(opt);
            treeItem.options.data = group;
            group.legend.treeItem = treeItem;
            // treeItem.options.contextMenu.add(
            //     new Jx.Menu.Item({
            //         label: OpenLayers.i18n('collapse'),
            //         onClick: OpenLayers.Function.bind(this.collapseBranch, this, treeItem)
            //     }),
            //     new Jx.Menu.Item({
            //         label: OpenLayers.i18n('expand'),
            //         onClick: OpenLayers.Function.bind(this.expandBranch, this, treeItem)
            //     })
            // );

            folder.add(treeItem);
            var groupInfo = group.oMap.getGroupInfoUrl(group.groupName);
            if (groupInfo) {
                treeItem.setGroupInfo(groupInfo, this.imgGroupInfoIcon);
            }
            for (var i=0; i<group.groups.length; i++) {
                this.processMapGroup(group.groups[i], treeItem);
            }
            for (var i=0; i<group.layers.length; i++) {
                this.processMapLayer(group.layers[i], treeItem);
            }
        }
    },
   
    processMapLayer: function(layer, folder) {
        /* make a 'namespace' on the layer object to store legend-related info */
        layer.legend = {};
        layer.legend.parentItem = folder;
        layer.legend.currentRange = null;
        layer.oMap.registerForEvent(Fusion.Event.LAYER_PROPERTY_CHANGED, OpenLayers.Function.bind(this.layerPropertyChanged, this));
    },
   
    layerPropertyChanged: function(eventID, layer) {
        layer.legend.treeItem.check(layer.isVisible());
    },

    updateTimer: null,
    update: function() {
        if (this.bIsDrawn) {
          if (this.updateTimer) {
            window.clearTimeout(this.updateTimer);
            this.updateTimer = null;
          }
          this.updateTimer = window.setTimeout(OpenLayers.Function.bind(this._update, this), this.updateDelay);
        }
    },
   
    /**
     * update the tree when the map scale changes
     */
    _update: function() {
        this.oTree.freeze();
        this.updateTimer = null;
        var map = this.getMap();
        var currentScale = map.getScale();
        for (var i=0; i<map.layerRoot.groups.length; i++) {
            this.updateGroupLayers(map.layerRoot.groups[i], currentScale);
        }
        //Loop reversed. See addLayerTreeItem() for why we do this
        for (var i=map.layerRoot.layers.length-1; i>=0; i--) {
            this.updateLayer(map.layerRoot.layers[i], currentScale);
        }
        this.oTree.thaw();
    },
   
    /**
     * remove the dom objects representing the legend layers and groups
     */
    clear: function() {
        //console.log('clear legend');
        var map = this.getMap();
        for (var i=0; i<map.layerRoot.groups.length; i++) {
            this.clearGroup(map.layerRoot.groups[i]);
        }
        for (var i=0; i<map.layerRoot.layers.length; i++) {
          if (map.layerRoot.layers[i].legend) {
            map.layerRoot.layers[i].legend.treeItem = null;
            map.layerRoot.layers[i].legend.checkbox = null;
            map.layerRoot.layers[i].legend.currentRange = null;
          }
        }
        this.oRoot.empty();
    },
    
    clearGroup: function(group) {
      for (var i=0; i<group.groups.length; i++) {
        this.clearGroup(group.groups[i]);
      }
      for (var i=0; i<group.layers.length; i++) {
        if (group.layers[i].legend) {
          group.layers[i].legend.treeItem = null;
          group.layers[i].legend.currentRange = null;
        }
      }
    },
    addLayerStyleTreeItems: function(treeItem, items) {
        treeItem.tree.freeze();
        treeItem.add(items);
        treeItem.tree.thaw();
    },
    addLayerTreeItem: function(treeItem, layerTreeItem) {
        //Here's the problem: The layers are being iterated in the correct draw order, but they are
        //being appended *after* groups at any particular level due to the out-of-order tree node rendering
        //setup between group and layer nodes (groups are drawn first before layers). 
        //
        //To do this in a way that preserves draw order, but makes sure thes layer nodes are always 
        //before group nodes at any particular level. We have to do 2 things:
        //
        // 1. Prepend this item instead of appending
        // 2. Ensure the outermost loop that calls updateLayer() (that calls this method), is iterating
        //    in *reverse* order.
        //
        //This allows for layers to be added in correct draw order *before* the group nodes at any
        //particular level.
        //
        //updateLayer() is the only method that calls this method, so the above contract is satisified.
        //If this is no longer the case, please re-read what I just wrote to make sure the two points
        //above are still valid.
        treeItem.add(layerTreeItem, 0);
    },
    updateGroupLayers: function(group, fScale) {
        for (var i=0; i<group.groups.length; i++) {
            this.updateGroupLayers(group.groups[i], fScale);
        }
        //Loop reversed. See addLayerTreeItem() for why we do this
        for (var i=group.layers.length-1 ; i >= 0; i--) {
            this.updateLayer(group.layers[i], fScale);
        }
    },
    updateLayer: function(layer, fScale) {
        /* no need to do anything if we are hiding the layer */
        if (!layer.displayInLegend || !layer.legend) {
            return;
        }
        /* check the layer's current scale range against the previous one
         * if the range hasn't changed, don't do anything
         */
        var range = layer.getScaleRange(fScale);
        if (range == layer.legend.currentRange && layer.legend.treeItem) {
            return;
        }
        
        /* remember the range we are now representing for the next update */
        layer.legend.currentRange = range;
        
        /* if layer is in range */
        if (range != null && range.styles) {
            /* if it has more than one style, we represent it as a folder
             * with classes as items in it
             */
            if (range.styles.length > 1) {
                //tree item needs to be a folder
                if (!layer.legend.treeItem) {
                    layer.legend.treeItem = this.createFolderItem(layer);
                    this.addLayerTreeItem(layer.parentGroup.legend.treeItem, layer.legend.treeItem);
                } else if (layer.legend.treeItem instanceof Fusion.Widget.Legend.TreeItem) {
                    this.clearTreeItem(layer);
                    layer.legend.treeItem = this.createFolderItem(layer);
                    this.addLayerTreeItem(layer.parentGroup.legend.treeItem, layer.legend.treeItem);
                } else {
                    layer.legend.treeItem.empty();
                }
                //This style range has the compression flag set. This would have been set server-side
                //if it contains more than a pre-defined number of style rules (see LoadScaleRanges.php for
                //more information)
                if (range.isCompressed) {
                    //Attach required data for theme expansion later on
                    layer.legend.treeItem.layer = layer;
                    layer.legend.treeItem.range = range;
                    layer.legend.treeItem.scale = fScale;
                    layer.legend.treeItem.hasDecompressedTheme = false;
                    //console.assert(range.styles.length > 2);
                    var children = [];
                    children.push(this.createTreeItem(layer, range.styles[0], fScale, false));
                    children.push(this.createThemeCompressionItem(range.styles.length - 2, layer.legend.treeItem));
                    children.push(this.createTreeItem(layer, range.styles[range.styles.length-1], fScale, false));
                    this.addLayerStyleTreeItems(layer.legend.treeItem, children);
                } else {
                    var children = [];
                    for (var i=0; i<range.styles.length; i++) {
                        children.push(this.createTreeItem(layer, range.styles[i], fScale, false));
                    }
                    this.addLayerStyleTreeItems(layer.legend.treeItem, children);
                }
            /* if there is only one style or no style, we represent it as a tree item */
            } else {
                var style = range.styles[0];
                if (style && !style.legendLabel) {
                    style.legendLabel = layer.legendLabel;
                }
                if (!layer.legend.treeItem) {
                    layer.legend.treeItem = this.createTreeItem(layer, style, fScale, !layer.isBaseMapLayer);
                    this.addLayerTreeItem(layer.parentGroup.legend.treeItem, layer.legend.treeItem);
                } else if (layer.legend.treeItem instanceof Fusion.Widget.Legend.TreeFolder) {
                    this.clearTreeItem(layer);
                    layer.legend.treeItem = this.createTreeItem(layer, style, fScale, !layer.isBaseMapLayer);
                    this.addLayerTreeItem(layer.parentGroup.legend.treeItem, layer.legend.treeItem);
                } else {
                    if (range.styles.length > 0) {
                        var url;
                        if(style.iconOpt && style.iconOpt.url){
                            url = style.iconOpt.url;
                            var img = layer.legend.treeItem.elements.get('jxTreeIcon');
                            var iconX = -1 * style.iconX;
                            var iconY = -1 * style.iconY;
                            img.style.backgroundPosition = iconX + 'px ' + iconY + 'px';
                        }else{
                            url = layer.oMap.getLegendImageURL(fScale, layer, style);
                        }
                        layer.legend.treeItem.setImage(url);
                        layer.legend.treeItem.enable(true);
                    } else {
                        layer.legend.treeItem.enable(false);
                    }
                }
            }
        } else {
            /* the layer is to be displayed but is not visible in the map
             * at the current map scale so disable it and display as a tree
             * item or hide it altogether if necessary;
             */
            if (this.hideInvisibleLayers) {
                if (layer.legend.treeItem) {
                    layer.parentGroup.legend.treeItem.remove(layer.legend.treeItem);
                    layer.legend.treeItem = null;
                }
            } else {
                var newTreeItem = this.createTreeItem(layer, {legendLabel: layer.legendLabel, iconOpt: { url: this.oLegend.outOfRangeIcon } }, null, !layer.isBaseMapLayer);
                if (layer.legend.treeItem) {
                    layer.parentGroup.legend.treeItem.replace(layer.legend.treeItem, newTreeItem);
                    layer.legend.treeItem.finalize();
                } else {
                    this.addLayerTreeItem(layer.parentGroup.legend.treeItem, newTreeItem);
                }
                layer.legend.treeItem = newTreeItem;
            }
        }
        if (layer.legend.treeItem) {
            layer.legend.treeItem.options.data = layer;
            if (!layer.isBaseMapLayer) //Tiled layers don't have a checkbox so there's nothing to check
                layer.legend.treeItem.check(layer.visible);
        }
    },
    getThemeExpandContextMenu: function(node) {
        return new Jx.Menu.Context(this.name).add([
            new Jx.Menu.Item({
                label: OpenLayers.i18n('expandTheme'),
                onClick: OpenLayers.Function.bind(function() { this.expandTheme(node); }, this)
            })]
        );
    },
    expandTheme: function(node) {
        if (node.hasDecompressedTheme !== true && confirm(OpenLayers.i18n('expandCompressedThemeConfirmation'))) {
            var range = node.range;
            var layer = node.layer;
            var fScale = node.scale;
            node.empty();
            //FIXME: JxLib really needs an API to add these in a single batch that doesn't hammer
            //the DOM (if it's even possible)
            for (var i = 0; i < range.styles.length; i++) {
                var item = this.createTreeItem(layer, range.styles[i], fScale, false);
                node.add(item);
            }
            node.hasDecompressedTheme = true;
        }
    },
    createThemeCompressionItem: function(number, node) {
        var opt = {
            label: OpenLayers.i18n('otherThemeItems', { count: number }),
            draw: this.renderItem,
            contextMenu: this.getThemeExpandContextMenu(node),
            image: this.imgBlankIcon
        };
        var item = new Jx.TreeItem(opt);
        return item;
    },
    createFolderItem: function(layer) {
        var opt = {
            label: layer.legendLabel == '' ? '&nbsp;' : layer.legendLabel,
            open: layer.expandInLegend,
            // contextMenu: this.getContextMenu(),
            image: this.imgLayerThemeIcon
        };
        if (layer.metadata) {
          opt.selectable = !layer.metadata.jxUnselectable || (layer.metadata.jxUnselectable && layer.metadata.jxUnselectable != 'true');
        } else {
          opt.selectable = false;
        }
        var folder;
        if (!layer.isBaseMapLayer) {
            folder = new Fusion.Widget.Legend.TreeFolder(opt);
            /* only need to add layer info if it has a check box too */
            var layerInfo = layer.oMap.getLayerInfoUrl(layer.layerName);
            if (layerInfo) {
                folder.setLayerInfo(layerInfo, this.imgLayerInfoIcon);
            }
        }  else {
            opt.selectable = false;
            folder = new Jx.TreeFolder(opt);
        }
        var img = folder.elements.get('jxTreeIcon');
        img.style.backgroundPosition = '0px 0px';
        // folder.options.contextMenu.add([
        //     new Jx.Menu.Item({
        //         label: OpenLayers.i18n('collapse'),
        //         onClick: OpenLayers.Function.bind(this.collapseBranch, this, folder)
        //     }),
        //     new Jx.Menu.Item({
        //         label: OpenLayers.i18n('expand'),
        //         onClick: OpenLayers.Function.bind(this.expandBranch, this, folder)
        //     })]
        // );
       
        return folder;
    },
    
    createTreeItem: function(layer, style, scale, checkbox) {
        var opt = {};
        opt.statusIsDefault = layer.statusDefault;
        var range = layer.legend.currentRange;
        //Set the label. Use style label IFF there are more than one style rule.
        //Otherwise layer's legend label takes precedence
        if (style && range && range.styles.length > 1) {
            opt.label = style.legendLabel == '' ? '&nbsp;' : style.legendLabel;
        } else {
            opt.label = layer.legendLabel == '' ? '&nbsp;' : layer.legendLabel;
        }
        
        if (layer.metadata) {
          opt.selectable = !layer.metadata.jxUnselectable || (layer.metadata.jxUnselectable && layer.metadata.jxUnselectable != 'true');
        } else {
          opt.selectable = false;
        }

        var LAYER_RASTER = 4;
        var LAYER_DWF = 5;

        if (!style) {
            //This could be a DWF or Raster layer
            if (layer.layerTypes.length == 1) {
                if (layer.layerTypes[0] == LAYER_RASTER) {
                    opt.image = this.imgLayerRasterIcon;
                } else if (layer.layerTypes[0] == LAYER_DWF) {
                    opt.image = this.imgLayerDWFIcon;
                } else {
                    opt.image = this.imgDisabledLayerIcon;
                    opt.enabled = false;
                }
            } else {
                opt.image = this.imgDisabledLayerIcon;
                opt.enabled = false;
            }
        } else {
            if(style.iconOpt && style.iconOpt.url){
                opt.image = style.iconOpt.url;
            }else{
                opt.image = layer.oMap.getLegendImageURL(scale, layer, style);
            }
        }
        // MapGuide DWF and Raster layer
        // MapGuide Raster and DWF layer
        if(layer.layerTypes[0] == 4){
            opt.image = this.imgLayerRasterIcon;
            opt.enabled = true;
        } else if(layer.layerTypes[0] == 5){
            opt.image = this.imgLayerDWFIcon;
            opt.enabled = true;
        }
        var item;
        if (!layer.isBaseMapLayer&&checkbox) {
            // opt.contextMenu = this.getContextMenu();
            item = new Fusion.Widget.Legend.TreeItem(opt);
            /* only need to add layer info if it has a check box too */
            var layerInfo = layer.oMap.getLayerInfoUrl(layer.layerName);
            if (layerInfo) {
                item.setLayerInfo(layerInfo, this.imgLayerInfoIcon);
            }
        }  else {
            opt.selectable = false;
            item = new Jx.TreeItem(opt);
        }
        
        var iconX = 0;
        var iconY = 0;
        var img = item.elements.get('jxTreeIcon');
        if (style && style.iconX >= 0 && style.iconY >= 0) {
            /* calculate the size of the image that holds the icon
             * only once and cache the values as it is an expensive operation
             * We use the size to center the class/layer icon as a background
             * image inside the image that holds it so that if the icon is
             * not the same size it is represented in a reasonable way
             */
            if (!this.offsetsCalculated) {
                var parent = img.parentNode;
                var sibling = img.getPrevious();
                var d = new Element('div', {'class':'fusionLegendTreeRoot'});
                img.setStyle('visiblity','hidden');
                img.inject(d);
                //TODO: img.getStyle doesn't seem to work for IE, need another solution here
                var w = 16;//img.getStyle('width').toInt();
                var h = 16;//img.getStyle('height').toInt();
                if (!sibling) {
                    img.inject(parent,'top');
                } else {
                    img.inject(sibling, 'after');
                }
                img.setStyle('visibility','visible');
                this.iconWidth = ((style.iconOpt?style.iconOpt.width:16) - w)/2;
                this.iconHeight = ((style.iconOpt?style.iconOpt.height:16) - h)/2;
                //alert(w+":"+h);
                this.offsetsCalculated = true;
            }
            iconX = -1 * (style.iconX + this.iconWidth);
            iconY = -1 * (style.iconY + this.iconHeight);
            img.style.backgroundPosition = iconX + 'px ' + iconY + 'px';
        }
        
        return item;
    },
    clearTreeItem: function(layer) {
        if (layer.legend.treeItem && layer.legend.treeItem.owner) {
            layer.legend.treeItem.owner.remove(layer.legend.treeItem);
            // layer.legend.treeItem.finalize();
            layer.legend.treeItem.destroy();
            layer.legend.treeItem = null;
        }
    }
});

Fusion.Widget.Legend.TreeItem = new Class({
    Extends: Jx.TreeItem,
    options: {
        template: '<li class="jxTreeContainer jxTreeLeaf"><img class="jxTreeImage" src="'+Jx.aPixel.src+'" alt="" title=""><span class="fusionLegendCheckboxContainer"><input type="checkbox" class="fusionLegendCheckboxInput"></span><a class="fusionLayerInfo" target="_blank"><img class="fusionLayerInfoIcon" src="'+Jx.aPixel.src+'"></a><a class="jxTreeItem fusionCheckboxItem" href="javascript:void(0);"><img class="jxTreeIcon" src="'+Jx.aPixel.src+'" alt="" title=""><span class="jxTreeLabel" alt="" title=""></span></a></li>'
    },
    classes: new Hash({
        domObj: 'jxTreeContainer', 
        domA: 'jxTreeItem', 
        domImg: 'jxTreeImage', 
        domIcon: 'jxTreeIcon',
        domLabel: 'jxTreeLabel',
        checkbox: 'fusionLegendCheckboxInput',
        layerInfo: 'fusionLayerInfo',
        layerInfoIcon: 'fusionLayerInfoIcon'
    }),
    init: function() {
      this.bound.onClick = function(e){
          if (this.options.data) {
              if (e.target.checked && this.options.data.show) {
                  this.options.data.show();
              } else if (!e.target.checked && this.options.data.hide) {
                  this.options.data.hide();
              }
          }
      }.bind(this);
      this.bound.enabled = function() {
          this.checkbox.disabled = false;
      }.bind(this);
      this.bound.disabled = function() {
          this.checkbox.disabled = true;
      }.bind(this);
      this.parent();
    },
    render: function() {
        this.parent();
        this.domLabel.set('alt', this.options.label);
        this.domLabel.set('title', this.options.label);
        if (this.checkbox) {
            if ($defined(this.options.checked)) {
                this.check(this.options.checked);
            }
            this.checkbox.addEvent('click', this.bound.onClick);
            this.addEvents({
              enabled: this.bound.enabled,
              disabled: this.bound.disabled
            });
        }
    },
    cleanup: function() {
      this.removeEvents({
        enabled: this.bound.enabled,
        disabled: this.bound.disabled
      });
      if (this.checkbox) {
        this.checkbox.removeEvent('click', this.bound.onClick);
      }
      this.bound.onClick = null;
      this.bound.enabled = null;
      this.bound.disabled = null;
      this.parent();
    },
    check: function(state) {
        if (this.checkbox) {
            this.checkbox.set('checked', state);
        }
    },
    isChecked: function() {
        return this.checkbox && this.checkbox.checked;
    },
    setLayerInfo: function(url, icon) {
        //change class to make fusionLayerInfo display block
        this.domObj.addClass('fusionShowLayerInfo');
        if (this.layerInfo) {
            this.layerInfo.set('href', url);
        }
        if (this.layerInfoIcon) {
            this.layerInfoIcon.set('src', icon);
        }
    }
});

Fusion.Widget.Legend.TreeFolder = new Class({
    Extends: Jx.TreeFolder,
    options: {
        template: '<li class="jxTreeContainer jxTreeBranch"><img class="jxTreeImage" src="'+Jx.aPixel.src+'" alt="" title=""><span class="fusionLegendCheckboxContainer"><input type="checkbox" class="fusionLegendCheckboxInput"></span><a class="jxTreeItem fusionCheckboxItem" href="javascript:void(0);"><img class="jxTreeIcon" src="'+Jx.aPixel.src+'" alt="" title=""><span class="jxTreeLabel" alt="" title=""></span></a><a class="fusionGroupInfo"><img class="fusionGroupInfoIcon" src="'+Jx.aPixel.src+'"></a><a class="fusionLayerInfo"><img class="fusionLayerInfoIcon" src="'+Jx.aPixel.src+'"></a><ul class="jxTree"></ul></li>'
    },
    classes: new Hash({
        domObj: 'jxTreeContainer', 
        domA: 'jxTreeItem', 
        domImg: 'jxTreeImage', 
        domIcon: 'jxTreeIcon',
        domLabel: 'jxTreeLabel',
        domTree: 'jxTree',
        checkbox: 'fusionLegendCheckboxInput',
        layerInfo: 'fusionLayerInfo',
        layerInfoIcon: 'fusionLayerInfoIcon',
        groupInfo: 'fusionGroupInfo',
        groupInfoIcon: 'fusionGroupInfoIcon'
    }),
    init: function() {
      this.bound.onClick = function(e){
          if (this.options.data) {
              if (e.target.checked && this.options.data.show) {
                  this.options.data.show();
              } else if (!e.target.checked && this.options.data.hide) {
                  this.options.data.hide();
              }
          }
      }.bind(this);
      this.bound.enabled = function() {
          this.checkbox.disabled = false;
      }.bind(this);
      this.bound.disabled = function() {
          this.checkbox.disabled = true;
      }.bind(this);
      this.parent();
    },
    
    render: function() {
        this.parent();
        this.domLabel.set('alt', this.options.label);
        this.domLabel.set('title', this.options.label);
        if (this.checkbox) {
            if ($defined(this.options.checked)) {
                this.check(this.options.checked);
            }
            this.checkbox.addEvent('click', this.bound.onClick);
            this.addEvents({
                enabled: this.bound.enabled,
                disabled: this.bound.disabled
            });
        }
    },
    cleanup: function() {
      this.removeEvents({
        enabled: this.bound.enabled,
        disabled: this.bound.disabled
      });
      if (this.checkbox) {
        this.checkbox.removeEvent('click', this.bound.onClick);
      }
      this.bound.onClick = null;
      this.bound.enabled = null;
      this.bound.disabled = null;
      this.parent();
    },
    check: function(state) {
        if (this.checkbox) {
            this.checkbox.set('checked', state);
        }
    },
    isChecked: function() {
        return this.checkbox && this.checkbox.checked;
    },
    setLayerInfo: function(url, icon) {
        //change class to make fusionLayerInfo display block
        this.domObj.addClass('fusionShowLayerInfo');
        if (this.layerInfo) {
            this.layerInfo.set('href', url);
        }
        if (this.layerInfoIcon) {
            this.layerInfoIcon.set('src', icon);
        }
    },
    setGroupInfo: function(url, icon) {
        //change class to make fusionGroupInfo display block
        this.domObj.addClass('fusionShowGroupInfo');
        if (this.groupInfo) {
            this.groupInfo.set('href', url);
        }
        if (this.groupInfoIcon) {
            this.groupInfoIcon.set('src', icon);
        }
    }
});
/**
 * Fusion.Widget.LinkToView
 *
 * $Id: LinkToView.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.LinkToView
 *
 * A widget that displays a link to the currently displayedd map view.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.LinkToView = OpenLayers.Class(Fusion.Widget,  {
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.baseUrl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?';

        //remove any existing extent param
        var join = '';
        for (var param in Fusion.queryParams) {
          if (typeof Fusion.queryParams[param] == 'string') {
            if (param == 'extent' ||
                param == 'filter' ||
                param == 'spatialfilter' ||
                param == 'variant' ||
                param == 'theme' ||
                param == 'selectlayer' ||
                param == 'showlayers' ||
                param == 'hidelayers' ||
                param == 'showgroups' ||
                param == 'hidegroups' ) {
                continue;
            }
            this.baseUrl += join + param + '=' + Fusion.queryParams[param];
            join = '&';
          }
        }
        this.anchorLabel = json.Label ? json.Label[0] : (this.domObj ? (this.domObj.innerHTML ? this.domObj.innerHTML : 'Link to View') : 'Link to View');

        Fusion.addWidgetStyleSheet(widgetTag.location + 'LinkToView/LinkToView.css');
        this.anchor = document.createElement('input');
        this.anchor.className = 'anchorLinkToView';
        this.anchor.value = this.baseUrl;
        this.anchor.title = json.Tooltip ? json.Tooltip[0] : 'Right-click to copy or bookmark link to current view';
        this.anchor.onfocus = OpenLayers.Function.bind(this.selectAnchorValue, this);
        
        if(this.domObj){
            this.domObj.innerHTML = '';
            this.domObj.appendChild(this.anchor);
        }
        
        this.getMap().oMapOL.events.register("addlayer", this, this.setListener);
        this.enable();                   
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        if (this.uiObj.domObj) {
            this.uiObj.domObj.appendChild(this.anchor);
        } else {
            this.uiObj.appendChild(this.anchor);
        }
    },
    
    selectAnchorValue: function() {
        this.anchor.select();
    },
    
    setListener: function(evt) {
        var layer = evt.layer;
        //register on the OL loadend event to update the link because this event
        //is fired whenever the layers are redrawn
        layer.events.register("loadend", this, this.updateLink);
    },
    
    updateLink: function() {
        var join = (this.baseUrl.indexOf('?')==this.baseUrl.length-1)?'':'&';
        var queryStr = this.getMap().getLinkParams();
        this.anchor.value = this.baseUrl + join + queryStr;
        //this.anchor.select();
    }
});
/**
 * Fusion.Widget.MapMenu
 *
 * $Id: MapMenu.js 2770 2013-08-27 11:40:25Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /****************************************************************************
 * Class: Fusion.Widget.MapMenu
 *
 * A widget that displays a selection of maps that can be loaded into the 
 * application.  The list of maps is configured in the ApplicationDefinition.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.MapMenu = OpenLayers.Class(Fusion.Widget,  {
    uiClass: Jx.Menu,
    domObj: null,
    mapGroupData: null,
    loadOverlaysOnly: false,
    rootFolder: '',
    menus: null,
    initializeWidget: function(widgetTag) {       
        this.extension = this.widgetTag.extension;       
        var widgetLayer = this.getMapLayer();
        this.arch = widgetLayer.arch;
        if (this.arch == 'MapGuide' && this.extension.Folder) {
            this.rootFolder = this.extension.Folder ? this.extension.Folder[0] : 'Library://';
            this.requestURL = 'layers/' + this.arch + '/' + Fusion.getScriptLanguage() +
                          '/MapMenu.' + Fusion.getScriptLanguage();
        } else if (this.arch == 'MapServer' && this.extension.Folder) {
            this.rootFolder = this.extension.Folder ? this.extension.Folder[0] : '/';
            //var s = 'layers/' + this.arch + '/' + Fusion.getScriptLanguage() + '/MapMenu.' + Fusion.getScriptLanguage();
            this.requestURL = this.extension.ListURL ? this.extension.ListURL[0] : '/platform/api/mapsherpa.php';
            this.publishedMapsOnly = this.extension.PublishedMapsOnly ? this.extension.PublishedMapsOnly : false;
            if(this.publishedMapsOnly){
                this.platformRequest = 'listpublishedmaps';
            }
            else
            {
                this.platformRequest = 'listresources';
            }
        }
        this.loadOverlaysOnly = (this.extension.LoadOverlaysOnly && this.extension.LoadOverlaysOnly[0] == 'true') ? true:false;
       
        this.getMapLayer().registerForEvent(Fusion.Event.MAP_SESSION_CREATED, OpenLayers.Function.bind(this.loadMenu, this));
        this.enable();
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        
        this.loadMaxExtent = this.extension.LoadMaxExtent ? 
             (this.extension.LoadMaxExtent[0].toLowerCase() == 'true') : false;
        
        //If no folder is specified for enumeration, build a menu
        //from the mapgroup alone. Folders are only supported with MapGuide.
        //Otherwise, create a hash of mapgroup resourceId to mapGroup data
        //to be used to assign mapgroup extensions to enumerated maps.
        
        var mapGroups = Fusion.applicationDefinition.mapGroups;
        this.mapGroupData = {};
        var buttonSet = new Jx.ButtonSet();
        for (var key in mapGroups) {
            if (mapGroups[key].mapId) {
                var mapGroup = mapGroups[key];
                if (this.extension.Folder) {
                    this.mapGroupData[mapGroup.maps[0].resourceId] = mapGroup; 
                } else {
                    var data = mapGroup;
                    var menuItem = new Jx.Menu.Item({
                        label: mapGroup.mapId,
                        toggle: true,
                        onClick: OpenLayers.Function.bind(this.switchMap, this, data)
                    });
                    buttonSet.add(menuItem);
                    this.uiObj.add(menuItem);
                }
            }
        }
    },
    
    loadMenu: function() {

        //console.log("LOADMENU");
        //debugger;
        
        //get the mapdefinitions as xml if there  is a folder specified
        //in the widget tag. All subfolders will be enumerated.
        //FIXME: this should be platform agnostic, Library:// isn't!
        //FIXME: use JSON rather than XML        
        if (this.arch == 'MapGuide' && this.extension.Folder) {
            var params =  {
              parameters: {'folder': this.rootFolder},
              onComplete: OpenLayers.Function.bind(this.processMapMenu, this)
            };
            Fusion.ajaxRequest(this.requestURL, params);
        } else if (this.arch == 'MapServer' && this.extension.Folder) {
            var map = this.getMapLayer();
            var options =  {
                  parameters: {
                    request: this.platformRequest,
                    type: 'map',
                    session:  map.getSessionID(),
                    depth: -1,
                    folder: this.rootFolder
                  },
                  method: 'GET',
                  onComplete: OpenLayers.Function.bind(this.processMSMapMenu, this)
            };
            var temp = new OpenLayers.Ajax.Request(this.requestURL, options);
        };
    },
    
    processMSMapMenu: function(r) {
        if (r.status == 200) {
            var o = Fusion.parseJSON(r.responseText);
            //var testData = '{"success":true,"errorMessages":[],"values":[{
            //  "sPath":"/ms4w/apps/gmap/cap/HamiltonLowIncome.map",
            //  "sPermissions":"2",
            //  "sResourceId":"/Hamilton/Hamilton Low Income",
            //  "sMapResource":"/Hamilton/hamilton_low_income"},
            //  {"sPath":"/mnt/FGS_ENVIRONS/fgs-cap/apps/platform/data/home/root/Canada1.map","sPermissions":"2","sResourceId":"/Canada/Canada1","sMapResource":"/Canada/Canada"}],"request":"listpublishedmaps","version":1}';
            //eval("o="+testData);
            this.menus = {};
            this.uiObj.empty();
            if (o.values && o.values.resources) {
              var list = o.values.resources;
              var widgetLayer = this.getMapLayer();
              for (var i=0; i<list.length; i++) {
                  var resource = list[i];
                  var mapId = resource.sResourceId;
                  mapId = mapId.replace(this.rootFolder, '');
                  var folders = mapId.split('/');
                  var label = folders.pop();
                  var path = folders.join('/');
                  this.createFolders(path);
                  
                  // check for mapgroup data and if there is none,
                  // create a maptag that will be passed to the map
                  // widget constructor 
                  var data = {maps:[{'resourceId': resource.sResourceId,
                          'singleTile':true,
                          'type': widgetLayer.arch,
                          'sid': widgetLayer.getSessionID(),
                          'layerOptions': {},
                          'id': widgetLayer.id,
                          'extension':{
                            'MapFile': ['platform:/'+resource.sResourceId],
                            'MapMetadata': [widgetLayer.mapMetadataKeys],
                            'LayerMetadata': [widgetLayer.layerMetadataKeys]
                          }
                         }]};
                  //set up needed accessor
                  data.getInitialView = function() {
                      return this.initialView;
                  };
                  if (this.loadOverlaysOnly) {
                    data.maps[0].layerOptions.isBaseLayer = false;
                  }
                  var menuItem = new Jx.Menu.Item({
                      label: label,
                      onClick: OpenLayers.Function.bind(this.switchMap, this, data)
                  });
                  
                  if (path == '') {
                      this.uiObj.add(menuItem);
                  } else {
                      this.menus[path].add(menuItem);
                  }
              }
            }
        }
    },
    
    processMapMenu: function(r) {
        if (r.status == 200) {
            var o = Fusion.parseJSON(r.responseText);
            this.menus = {};
            for (var i=0; i<o.maps.length; i++) {
                var map = o.maps[i];
                var path = map.path.replace(this.rootFolder, '');
                if (path.lastIndexOf('/') > -1) {
                    path = path.slice(0, path.lastIndexOf('/'));
                    this.createFolders(path);
                } else {
                    path = '';
                }
                
                // check for mapgroup data and if there is none,
                // create a maptag that will be passed to the map
                // widget constructor 
                var data = null;
                if (this.mapGroupData[map.path]) {
                    data = this.mapGroupData[map.path];
                } else {
                    data = {maps:[{'resourceId':map.path,
                            'singleTile':true,
                            'type': this.arch,
                            'extension':{'ResourceId': [map.path]}
                           }]};
                    //set up needed accessor
                    data.getInitialView = function() {
                        return this.initialView;
                    };
                }
                var menuItem = new Jx.Menu.Item({
                    label: map.name,
                    onClick: OpenLayers.Function.bind(this.switchMap, this, data)
                });
                
                if (path == '') {
                    this.uiObj.add(menuItem);
                }else {
                    this.menus[path].add(menuItem);
                }
            }
        }
    },
    
    createFolders: function(id) {
        var folders = id.split('/');
        //loop through folders, creating them if they don't exist
        var parent = '';
        var pathSeparator = '';
        for (var i=0; i<folders.length; i++) {
            if (!this.menus[parent + pathSeparator + folders[i]]){
                var menu = new Jx.Menu.SubMenu({label:folders[i]});
                if (parent == '') {
                    this.uiObj.add(menu);
                } else {
                    this.menus[parent].add(menu);
                }
                this.menus[parent + pathSeparator + folders[i]] = menu;
            }
            parent = parent + pathSeparator + folders[i];
            pathSeparator = '/';
        }
    },
    
    //action to perform when the button is clicked
    //activateTool: function() {
    //    this.oMenu.show();
    //},
        
    //change the map, preserving current extents
    switchMap: function(data) {
        data.initialView = null;
        if (!this.loadMaxExtent) {
          var ce = this.getMap().getCurrentExtents();
          var dest = null;
          for (var i=0; i<data.maps.length; ++i) {
            if (data.maps[i].layerOptions && data.maps[i].layerOptions.projection) {
              //This may not be an "EPSG:XXXX" string. It could already be an OpenLayers.Projection
              //so re-use it if that's the case
              var oProj = data.maps[i].layerOptions.projection;
              var projCode = "";
              if (oProj instanceof OpenLayers.Projection)
                dest = oProj;
              else
                dest = new OpenLayers.Projection(oProj);
                
              if (data.maps[i].layerOptions.isBaseLayer) {
                break;
              }
            }
          }
          if (dest) {
            ce = ce.transform(this.oMap.oMapOL.getProjectionObject(), dest);
          } else {
            dest = this.oMap.oMapOL.getProjectionObject();
          }
          data.initialView = {
            minX: ce.left,
            minY: ce.bottom,
            maxX: ce.right,
            maxY: ce.top,
            projection: dest
          };        
        }
        this.getMap().loadMapGroup(data, this.loadOverlaysOnly);
    }
});
/**
 * Fusion.Widget.About
 *
 * $Id: About.js 1656 2008-11-08 21:44:26Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/********************************************************************
* Class: Fusion.Widget.MapMetadata
*
* MapMetadata widget to inject map metadata elements into the interface
*
* Inherits from:
*  - <Fusion.Widget>
* **********************************************************************/

Fusion.Widget.MapMetadata = OpenLayers.Class(Fusion.Widget, {
    
    content: 'text',
    layerName: null,
    
/*
 * Constructor: MapMetadata
 *
 * Parameters:
 *
 * widgetTag - JSON node for this widget from the Application definition
 *
 */
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        if (json.MetadataId) {
          this.metadataId =  json.MetadataId[0];
        } else {
          Fusion.reportFatalError( "MetadataId is a required parameter for mapMetadata widget");
        }
        if (json.Content) {
          this.content = json.Content[0];
        }
        if (json.LayerName) {
          this.layerName = json.LayerName[0];
        }
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.showMetadata, this));
    },

    /**
     * Function: showMetadata
     *
     * puts a piece of map metadata into the interface
     * 
     */
    showMetadata: function() {
      var metadataLayer = this.getMapLayer();
      var metadata = metadataLayer.getMetadata(this.metadataId, this.layerName);
      if (metadata) {
        //this.domObj.appendChild(metadata);
        if (this.content == 'markup') {             
          //load in an HTML fragment
          //this isn't working in IE
          var domObj = $(this.domObj);
          domObj.load(metadata);
        } else if ((this.content == 'hyperlink') && this.domObj.tagName == 'A') {
          this.domObj.href = metadata;
        } else {
          this.domObj.innerHTML = metadata;
        }
      }
    }
});
/**
 * Fusion.Widget.Maptip
 *
 * $Id: Maptip.js 2765 2013-08-09 06:49:50Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Maptip
 *
 * Displays tooltips over the map when the mouse is hovered for some 
 * time.  You must configure tooltips for each layer using Studio
 * or Web Studio by editing the LayerDefinition Settings and
 * specifying an expression for the tooltip.
 *
 *
 * Delay (optional)
 *
 * This is the delay, in milliseconds, that the user must keep the mouse
 * in the same position in order for the maptip to appear.  The default,
 * if not specified, is 350 milliseconds.
 *
 * Layer (optional, multiple)
 *
 * This is the name of a layer from the MapDefinition to get the tooltip
 * from.  If no Layer elements are specified, then all layers will be
 * queried and the top-most one will be displayed.  Multiple Layer tags
 * can be added, allowing tooltips to come from different layers.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.Maptip = OpenLayers.Class(Fusion.Widget, {
    oCurrentPosition: new OpenLayers.Pixel(0,0),
    oMapTipPosition: null,
    nTimer: null,
    delay: null,
    aLayers: null,
    bOverTip: false,
    sWinFeatures: 'menubar=no,location=no,resizable=no,status=no,scrollbars=yes',
    offset: new OpenLayers.Pixel(2,20),
    szTip: '',
    szHref:'',
    szLabel:'',
    aTextFields: null,
    mapTipFired: false,
    bStartMapTips:false,
    mapTipBtn: null,
    label:'',
    
    
    initializeWidget: function(widgetTag) {
        var index = window.location.href.indexOf("?");
    
        this.label = widgetTag.label;
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "MaptipWindow";
        if (json.WinFeatures) {
          this.sWinFeatures = json.WinFeatures[0];
        }
        this.delay = json.Delay ? parseInt(json.Delay[0]) : 350;
        this.nTolerance = json.Tolerance ? parseInt(json.Tolerance[0]) : 2;

        this.aCustomURL =   [];
        this.aTextFields = []; 
        this.aLayers = [];
        this.aLabels = [];
        
        if (json.Maptip) {
            for (var i=0; i<json.Maptip.length; i++) {
                this.aLayers.push(json.Maptip[i].Layer);
                this.aTextFields.push(json.Maptip[i].TextField);
                this.aLabels.push(json.Maptip[i].Label);
                this.aCustomURL.push(json.Maptip[i].CustomURL);
            }
        }

        //prepare the container div for the maptips
        Fusion.addWidgetStyleSheet(widgetTag.location + 'Maptip/Maptip.css');
        if (this.domObj) {
          this.domObj.parentNode.removeChild(this.domObj);
        } else {
          this.domObj = document.createElement('div');
        }
        this.domObj.className = 'maptipContainer';
        this.domObj.style.display = 'none';
        this.domObj.style.top = '0px';
        this.domObj.style.left = '0px';
        
        //create an iframe to stick behind the maptip to prevent clicks being passed through to the map
        this.iframe = document.createElement('iframe');
        this.iframe.className = 'maptipShim';
        this.iframe.scrolling = 'no';
        this.iframe.frameborder = 0;

        this.mouseOverTipFunc = OpenLayers.Function.bind(this.mouseOverTip, this);
        this.mouseOutTipFunc = OpenLayers.Function.bind(this.mouseOutTip, this);
        this.mouseMoveFunc = OpenLayers.Function.bind(this.mouseMove, this);
        this.mouseDownFunc = OpenLayers.Function.bind(this.mouseDown, this);
        this.mouseUpFunc = OpenLayers.Function.bind(this.mouseUp, this);
        this.mouseOutFunc = OpenLayers.Function.bind(this.mouseOut, this);
        this.mapTipReqFinishedFunc = OpenLayers.Function.bind(this._display,this);
        this.mapBusyChangedFunc = this.busyChanged.bind(this);
        this.mapLoaded = this.startMapTips.bind(this);
        
        //Subscribe to digitizer events so we know when not to interfere
        this.getMap().registerForEvent(Fusion.Event.MAP_DIGITIZER_ACTIVATED, OpenLayers.Function.bind(this.onDigitizerActivated, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_DIGITIZER_DEACTIVATED, OpenLayers.Function.bind(this.onDigitizerDeactivated, this));
    },
    
    onDigitizerActivated: function() {
        this.hideMaptip();
        this.bDigitizerActive = true;
    },
    
    onDigitizerDeactivated: function() {
        this.bDigitizerActive = false;
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.uiInstances[this.type].push(this);

        this.mapTipBtn = new Jx.Button({
            id: 'maptipButton',
            image: Fusion.getApplicationURL() + 'images/icons.png',
            imageClass: "maptip",
            label: this.label,
            toggle: true,
            onDown: (function() {
                    var instances = Fusion.Widget.uiInstances[this.type];
                    for (var i=0; i<instances.length; i++) {
                        var instance = instances[i];
                        if (instance.shouldActivateWith(this) &&
                            instance.mapTipBtn && instance.mapTipBtn.setActive) {
                            instance.mapTipBtn.setActive(true);
                        }
                    }
                    if(!Fusion.Widget.Maptip.ActiveInstance)
                    {
                        this.activate();
                        Fusion.Widget.Maptip.ActiveInstance = this;
                    }
                }).bind(this),
            onUp: (function() {
                    var instances = Fusion.Widget.uiInstances[this.type];
                    for (var i=0; i<instances.length; i++) {
                        var instance = instances[i];
                        if (instance.shouldActivateWith(this) &&
                            instance.mapTipBtn && instance.mapTipBtn.setActive) {
                            instance.mapTipBtn.setActive(false);
                        }
                    }
                    if(Fusion.Widget.Maptip.ActiveInstance == this)
                    {
                        this.deactivate();
                        Fusion.Widget.Maptip.ActiveInstance = null;
                    }
                }).bind(this)
        }).addTo(uiObj);
        if (this.widgetTag.tooltip) {
            this.mapTipBtn.setTooltip(this.widgetTag.tooltip);
        }
        if (uiObj.options.active) {
            this.mapTipBtn.setActive(true);
        }

        this.uiObj = uiObj;	
    },
    
    activate : function() {
        this.bStartMapTips = true;
        
        OpenLayers.Event.observe(this.domObj, 'mouseover', this.mouseOverTipFunc);
        OpenLayers.Event.observe(this.domObj, 'mouseout', this.mouseOutTipFunc);
        
        var oDomElem =  this.getMap().getDomObj();
        document.getElementsByTagName('BODY')[0].appendChild(this.domObj);
        
        
        this.getMap().observeEvent('mousemove', this.mouseMoveFunc);
        this.getMap().observeEvent('mousedown', this.mouseDownFunc);
        this.getMap().observeEvent('mouseup', this.mouseUpFunc);
        this.getMap().observeEvent('mouseout', this.mouseOutFunc);

        this.eventListener = false;
        this.getMap().registerForEvent(Fusion.Event.MAP_MAPTIP_REQ_FINISHED,this.mapTipReqFinishedFunc);
        this.getMap().registerForEvent(Fusion.Event.MAP_BUSY_CHANGED, this.mapBusyChangedFunc);
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, this.mapLoaded);
    },
    
    deactivate: function() {
        this.bStartMapTips = false;
        
        OpenLayers.Event.stopObserving(this.domObj, 'mouseover', this.mouseOverTipFunc);
        OpenLayers.Event.stopObserving(this.domObj, 'mouseout', this.mouseOutTipFunc);
        
        this.getMap().stopObserveEvent('mousemove', this.mouseMoveFunc);
        this.getMap().stopObserveEvent('mousedown', this.mouseDownFunc);
        this.getMap().stopObserveEvent('mouseup', this.mouseUpFunc);
        this.getMap().stopObserveEvent('mouseout', this.mouseOutFunc);

        this.eventListener = false;
        this.getMap().deregisterForEvent(Fusion.Event.MAP_MAPTIP_REQ_FINISHED,this.mapTipReqFinishedFunc);
        this.getMap().deregisterForEvent(Fusion.Event.MAP_BUSY_CHANGED, this.mapBusyChangedFunc);
        this.getMap().deregisterForEvent(Fusion.Event.MAP_LOADED, this.mapLoaded);
        
        if(this.domObj != null && this.domObj.parentNode != null) {
            this.domObj.parentNode.removeChild(this.domObj); 
        }
    },
    
    mouseOut: function(e) {
      //console.log('maptip mouseOut:'+this.nTimer+':'+this.nHideTimer);
        if (this.nTimer) {
            window.clearTimeout(this.nTimer);
            if (!this.nHideTimer) {
                /*console.log('mouseOut: set hide timer');*/
                this.nHideTimer = window.setTimeout(OpenLayers.Function.bind(this.hideMaptip, this), 250);
            }
        }
    },

    startMapTips: function(){
        this.bStartMapTips = true;
    },
    
    mouseMove: function(e) {
        if( this.bStartMapTips == true){
            if(!this.eventListener){
                this.eventListener = true;
            }
        //console.log('map tip mouseMove');
            if (this.bOverTip || this.mouseIsDown) {
                return;
            }

            var map = this.getMap();
            this.mapSize = map.getSize();
            this.mapOffset = map._oDomObj.getOffsets();

            var p = map.getEventPosition(e);
            if (p.x == this.oCurrentPosition.x && p.y == this.oCurrentPosition.y ) {
              return;
            }
            this.oCurrentPosition = p;
            this.oMapTipPosition = p;

            if(typeof( this.nTimer) == "number") {
                window.clearTimeout(this.nTimer);
                this.nTimer = null;
            }

            this.nTimer = window.setTimeout(OpenLayers.Function.bind(this.showMaptip, this), this.delay);
            //Event.stop(e);
        } // bStartMapTips 
    },
    
    mouseDown: function() {
        this.mouseIsDown = true;
        this._hide();
    },
    
    mouseUp: function() {
        this.mouseIsDown = false;
    },
    
    _getMapTip: function(oMapTips) {
        var iMgInstances = 0;
        var mapWidget = this.getMap();
        for (var i = mapWidget.aMaps.length - 1; i >= 0; i--) {
            if (mapWidget.aMaps[i].arch == "MapGuide") {
                iMgInstances++;
            }
        }
        if (iMgInstances <= 1) {
            this.getMapLayer().getMapTip(this);
        } else {
            var scale = mapWidget.getScale();
            for (var i = mapWidget.aMaps.length - 1; i >= 0; i--) {
                var mp = mapWidget.aMaps[i];
                if (scale >= mp.minScale && scale <= mp.maxScale && mp.layerRoot.visible) {
                    mp.getMapTip(oMapTips);
                }
            }
        }
    },
    
    showMaptip: function() {
        if (this.bDigitizerActive === true) {
            //console.log("Abort maptip query");
            return;
        }
        this._getMapTip(this);
        this.mapTipFired = true;
    },
    
    _display: function(eventID,oMapTip) {
        if (this.bDigitizerActive === true || typeof(oMapTip) == "undefined" || oMapTip.t == '') {
            //console.log("Abort maptip display");
            return;
        }
        if(this.domObj.style.visibility != 'visible' || oMapTip.t != this.szTip ){
            this.domObj.innerHTML = null;
            var contentDiv = document.createElement('div');
            contentDiv.className = 'maptipContent';
            this.domObj.appendChild(contentDiv);
            var empty = true;
            this.bIsVisible = true;
            
            var tip = oMapTip.t;
            var hyperlink =oMapTip.h;
            var label =oMapTip.l;
            
            this.szTip = tip;
            this.szHref = hyperlink;
            this.label = label;
            
            if(typeof(tip) == "object"){
                for(var i=0;i<tip.length;i++){
                    var t = tip[i];
                    var h = hyperlink[i];
                    var l = label[i];
                    var oLinkDom = this.addMapTipDomObj(h,t);
                    var mapTipContent = document.createElement('DIV');
                    mapTipContent.className = "mapTipContentDIV";
                    // has a label with the maptip
                    if(l != ""){
                        mapTipContent.innerHTML = l+" : ";
                        contentDiv.appendChild(mapTipContent);
                        mapTipContent.appendChild(oLinkDom);
                        empty = false;
                    }
                    else
                    {
                        contentDiv.appendChild(mapTipContent);
                        contentDiv.appendChild(oLinkDom);
                        empty = false;
                    }
                }
            }
            else
            {
                if (tip) {
                    var mapTipContent = document.createElement('DIV');
                    mapTipContent.innerHTML = tip.replace(/\\n/g, "<br>");
                    contentDiv.appendChild(mapTipContent);
                    empty = false;
                }

                if (hyperlink) {
                    var mapTipContent = document.createElement('DIV');
                    hyperlink =  hyperlink.replace(/\\n/g, "<br>");
                    if ( hyperlink.indexOf("href=")>0 ) {
                      mapTipContent.innerHTML = hyperlink
                    } else {
                      var anchor = document.createElement('A');
                      var openLink = OpenLayers.Function.bind(this.openLink, this, hyperlink);
                      anchor.onclick = OpenLayers.Function.bindAsEventListener(openLink, this);
                      anchor.target = "_blank";
                      anchor.href = 'javascript:void(0)';
                      anchor.innerHTML = OpenLayers.i18n('maptipLinkText');
                      mapTipContent.appendChild(anchor);
                    }
                    contentDiv.appendChild(mapTipContent);
                    empty = false;
                }
            }

            
            if (!empty) {
                var size = $(this.domObj).getBorderBoxSize();
                this.oMapTipPosition = this.oMapTipPosition.add(this.mapOffset.x, this.mapOffset.y);
                if (this.oCurrentPosition.x < this.mapSize.w/2) {
                    this.domObj.style.left = (this.oMapTipPosition.x + this.offset.x) + 'px';
                } else {
                    this.domObj.style.left = (this.oMapTipPosition.x - (size.width+this.offset.x)) + 'px';
                }
                if (this.oCurrentPosition.y < this.mapSize.h/2) {
                    this.domObj.style.top = (this.oMapTipPosition.y + this.offset.y) + 'px';
                } else {
                    this.domObj.style.top = (this.oMapTipPosition.y - (size.height+this.offset.y)) + 'px';
                }
                this.domObj.style.visibility = 'hidden';
                this.domObj.style.display = 'block';

                if (!window.opera) {
                    contentDiv.appendChild(this.iframe);
                    var size = $(this.domObj).getContentBoxSize();
                    this.iframe.style.width = size.width + "px";
                    this.iframe.style.height = size.height + "px";
                }

                this.domObj.style.visibility = 'visible';
                //this.hideTimer = window.setTimeout(OpenLayers.Function.bind(this._hide, this),10000);
            } else {
                this.hideMaptip();
            }
        }
        this.mapTipFired = false;
    },

    addMapTipDomObj: function(url,szText){
        if(url == "undefined" || url == typeof("undefined")  || url ==""){
                var linkSpan = document.createElement('SPAN');
                linkSpan.className = "mapTipData";
                linkSpan.innerHTML = szText;
                return linkSpan;
            }
            else
            {
            var a, linkURL;
            var linkSpan = document.createElement('SPAN');
            linkSpan.className = "mapTipData"
            if (url.indexOf('href=') > 0) {   //MGOS allows complete anchor tags as the hyperlink
            linkSpan.innerHTML = url;
            a = linkDiv.firstChild;
            linkURL = a.href;
            } else {
            a = document.createElement('a');
            a.className = "mapTipLink";
            a.innerHTML = szText;
            linkURL = url;
            linkSpan.appendChild(a);
            }
            a.href = 'javascript:void(0)';
            var openLink = OpenLayers.Function.bind(this.openLink, this, linkURL);
            a.onclick = OpenLayers.Function.bindAsEventListener(openLink, this);
            return linkSpan;
            }
    },
    
    hideMaptip: function() {
      //console.log('hideMaptip');
        this.bIsVisible = false;
        this.hideTimer = window.setTimeout(OpenLayers.Function.bind(this._hide, this),10);
    },
    
    _hide: function() {
      //console.log('maptip _hide');
        this.hideTimer = null;
        this.domObj.style.display = 'none';
        this.domObj.style.visibility = '';
        //this.oMapTipPosition = null;
    },
    
    mouseOverTip: function() {
      //console.log('mouseOverTip');
        window.clearTimeout(this.nHideTimer);
        this.nHideTimer = null;
        this.bOverTip = true;
    },
    
    mouseOutTip: function() {
      //console.log('mouseOutTip');
        this.nHideTimer = window.setTimeout(OpenLayers.Function.bind(this.hideMaptip, this), 250);
        this.bOverTip = false;
    },
    
    busyChanged: function() {
        if (this.getMap().isBusy()){
        this.bIsVisible = false;
        this.hideMaptip();
        }
    },

    openLink: function(url, evt) {
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        if ( taskPaneTarget ) {
            taskPaneTarget.setContent(url);
        } else {
            var pageElement = $(this.sTarget);
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                window.open(url, this.sTarget, this.sWinFeatures);
            }
        }
        OpenLayers.Event.stop(evt, true);
        return false;
    }
});
/**
 * Fusion.Widget.Measure
 *
 * $Id: Measure.js 2686 2013-04-05 11:44:39Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /* ********************************************************************
 * Class: Fusion.Widget.Measure
 *
 * The Measure widget allows the user to measure distances or areas on the map
 * in one or more segments. Area is positive if measured clockwise.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Constant.MEASURE_TYPE_DISTANCE = 1;
Fusion.Constant.MEASURE_TYPE_AREA = 2;

Fusion.Event.MEASURE_SEGMENT_UPDATE = Fusion.Event.lastEventId++;
Fusion.Event.MEASURE_CLEAR = Fusion.Event.lastEventId++;
Fusion.Event.MEASURE_COMPLETE = Fusion.Event.lastEventId++;

Fusion.Widget.Measure = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,

    //distance of each segment
    distances: null,
    distanceMarkers: null,
    areaMarker: null,

    /* the units to display distances in */
    units: Fusion.UNKNOWN,

    /* Type of measure: values = disance, area or both, default: both*/
    measureType: null,

    /* Precision of the distance displayed */
    distPrecision: 4,

    /* Precision of the area displayed */
    areaPrecision: 4,

    /* Style for the distance line used for distance draw */
    distanceNormalStyle: null,

    /* Style for the polygon used for area draw */
    fillStyle: null,

    /* set to false for local coordinate systems */   
    geodesic: true,

    /* Style for the polygon line used for area draw */
    areaStyle: null,
    segmentLabels: true,
    
    /* maintain measurements for other widgets to read */
    totalLength: 0,
    totalArea: 0,
    
    initializeWidget: function(widgetTag) {
        this.asCursor = ['crosshair'];
        var json = widgetTag.extension;
        this.units = (json.Units && (json.Units[0] != '')) ?  Fusion.unitFromName(json.Units[0]): this.units;
        this.distPrecision = json.DistancePrecision ? parseInt(json.DistancePrecision[0]) : 4;
        this.areaPrecision = json.AreaPrecision ? parseInt(json.AreaPrecision[0]) : 4;
        if(json.SegmentLabels){
            this.segmentLabels = (json.SegmentLabels[0].toLowerCase() == "true" && json.SegmentLabels[0]) ? true : false;
        }
        if(json.Geodesic){
            this.geodesic = (json.Geodesic[0].toLowerCase == "false") ? false : true;
        }
        this.sTarget = json.Target ? json.Target[0] : "";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/Measure/Measure.php';

        //init measure type
        this.measureType = 0;
        if (json.Type) {
            switch(json.Type[0].toLowerCase()) {
                case 'distance':
                    this.measureType |= Fusion.Constant.MEASURE_TYPE_DISTANCE;
                    break;
                case 'area':
                    this.measureType |= Fusion.Constant.MEASURE_TYPE_AREA;
                    break;
                case 'both':
                    this.measureType |= Fusion.Constant.MEASURE_TYPE_DISTANCE;
                    this.measureType |= Fusion.Constant.MEASURE_TYPE_AREA;
                    break;
                default:
                    break;
            }
        }

        //Here are the canvas style definition
        var fillStyle = json.FillStyle ? json.FillStyle[0] : 'rgba(0,0,255, 0.3)';
        var lineStyleWidth = json.LineStyleWidth ? json.LineStyleWidth[0] : 2;
        var lineStyleColor = json.LineStyleColor ? json.LineStyleColor[0] : 'rgba(0,0,255,0.3)';
        this.distanceMarkers = [];
        this.distances = [];

        this.registerEventID(Fusion.Event.MEASURE_SEGMENT_UPDATE);
        this.registerEventID(Fusion.Event.MEASURE_CLEAR);
        this.registerEventID(Fusion.Event.MEASURE_COMPLETE);

        var mapWidget = this.getMap();
        this.keyHandler = OpenLayers.Function.bind(this.onKeyPress, this);
        Fusion.addWidgetStyleSheet(widgetTag.location + 'Measure/Measure.css');

        mapWidget.registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.setUnits, this, this.units));
        this.registerParameter('Units');

            // style the sketch fancy
        this.sketchSymbolizers = {
                "Point": {
                    pointRadius: 4,
                    graphicName: "square",
                    fillColor: "white",
                    fillOpacity: 1,
                    strokeWidth: 1,
                    strokeOpacity: 1,
                    strokeColor: "#333333"
                },
                "Line": {
                    strokeWidth: 3,
                    strokeOpacity: 1,
                    strokeColor: "#666666"
                },
                "Polygon": {
                    strokeWidth: 2,
                    strokeOpacity: 1,
                    strokeColor: "#666666",
                    fillColor: "white",
                    fillOpacity: 0.3
                }
        };
        var style = new OpenLayers.Style();
        style.addRules([
            new OpenLayers.Rule({symbolizer: this.sketchSymbolizers})
        ]);
        var styleMap = new OpenLayers.StyleMap({"default": style});

        //add in the OL Polygon handler
        this.map = mapWidget.oMapOL;
        var handlerOptions = {
            style: "default", // this forces default render intent
            layerOptions: {styleMap: styleMap}
        };
        var handler = OpenLayers.Handler.Path;
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_AREA) {
            handler = OpenLayers.Handler.Polygon;
        }

        var controlOptions = {
            persist: true,
            handlerOptions: handlerOptions,
            partialDelay: 600,
            callbacks: {
                'modify': OpenLayers.Function.bind(this.measurePartial, this),
                'cancel': OpenLayers.Function.bind(this.resetMeasure, this)
            }
        };
        this.control = new OpenLayers.Control.Measure(handler, controlOptions);
        this.getMap().oMapOL.addControl(this.control);

        this.control.events.on({
            measure: this.measure,
            scope: this
        });
        
        //add in the clear button if required
        if(json.ClearButtonContainer) {
          var clearButtonText = json.ClearButtonText? json.ClearButtonText[0] : 'Clear';
          this.clearButton = new Jx.Button({
            label: clearButtonText,
            onClick: OpenLayers.Function.bind(this.clearMeasure, this)
          }).addTo(json.ClearButtonContainer[0]);
          this.registerForEvent(Fusion.Event.MEASURE_CLEAR, OpenLayers.Function.bind(this.toggleClearButton, this, false));
          this.registerForEvent(Fusion.Event.MEASURE_SEGMENT_UPDATE, OpenLayers.Function.bind(this.toggleClearButton, this, true));
          this.clearButton.setEnabled(false);
        }

        this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, OpenLayers.Function.bind(this.extentsChangedCB, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.extentsChangedCB, this));

    },
    
    clearMeasure: function() {
        this.control.cancel();
    },

    toggleClearButton: function(enabled) {
        this.clearButton.setEnabled(enabled);
    },

    extentsChangedCB: function() {
        var olControl = this.control;
        if(olControl && olControl.active)
        {
            var poly = olControl.handler;
            var geom = poly.getGeometry();
            if(geom)
            {
                this.updateMarkers(geom);
            }
        }
    },

    updateMarkers: function(geom) {
        var v = geom.getVertices();
        for(var i = 0; i < this.distanceMarkers.length; i++)
        {
            if (i + 1 >= v.length)
                break;

            var seg =  new OpenLayers.Geometry.LineString();
            seg.addPoint(v[i].clone());
            seg.addPoint(v[i+1].clone());
            this.updateMarker(this.distanceMarkers[i], seg);
        }
        this.updateDistances(geom);
        this.updateArea(geom);
    },

    shouldActivateWith: function(widget) {
        return (widget instanceof Fusion.Widget.SelectPolygon &&
                widget.widgetUniqueId == this.widgetUniqueId);
        
    },
    

    measure: function(stats) {
        this.hasMeasure = true;
        /* rebuild distance markers */
        var geom = stats.geometry;
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_DISTANCE) {
            var v = geom.getVertices();
            while(this.distanceMarkers.length > v.length-1) {
                this.distanceMarkers.pop().destroy();
            }
            var vFirst = v0 = v[0];
            for (var i=1; i<v.length; i++) {
                v1 = v[i];
                if (this.distanceMarkers[i-1]) {
                    var m = this.distanceMarkers[i-1];
                    m.label = i + ': ';
                } else {
                    m = new Fusion.Widget.Measure.Marker(this.units, this.distPrecision, i + ': ');
                }
                var seg = new OpenLayers.Geometry.LineString();
                seg.addPoint(v0.clone());
                seg.addPoint(v1.clone());
                this.updateMarker(m,seg);
                v0 = v1;
            }
            if (this.measureType & Fusion.Constant.MEASURE_TYPE_AREA) {
                seg = new OpenLayers.Geometry.LineString();
                seg.addPoint(vFirst);
                seg.addPoint(v0);
                this.lastMarker.label = i+ ': ';
                this.updateMarker(this.lastMarker, seg);
            }
        }
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_AREA) {
            this.updateArea(geom);
        }
        this.triggerEvent(Fusion.Event.MEASURE_COMPLETE);
    },

    measurePartial: function(point, sketch) {
        var geom = sketch.geometry;
        var v = geom.getVertices();
        if (this.hasMeasure) {
            if(v.length != 1)
            {
                this.resetMeasure();
                this.hasMeasure = false;
            }
        }
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_DISTANCE) {
            this.updateDistances(geom);
        }
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_AREA) {
            this.updateArea(geom);
        }
        this.triggerEvent(Fusion.Event.MEASURE_SEGMENT_UPDATE);
    },

    updateDistances: function(geom) {
        var v = geom.getVertices();
        var last = v.length - 1;
        if (last > 0) {
            var lastSeg =  new OpenLayers.Geometry.LineString();
            lastSeg.addPoint(v[last - 1].clone());
            lastSeg.addPoint(v[last].clone());
            var marker;
            if (this.distanceMarkers.length < last) {
                marker = new Fusion.Widget.Measure.Marker(this.units, this.distPrecision, last + ': ');
                this.distanceMarkers.push(marker);
            } else {
                marker = this.distanceMarkers[last-1];
            }
            this.updateMarker(marker, lastSeg);
            if ( last > 1 && geom.CLASS_NAME.indexOf('LineString') == -1) {
                lastSeg = new OpenLayers.Geometry.LineString();
                lastSeg.addPoint(v[0].clone());
                lastSeg.addPoint(v[last].clone());
                if (!this.lastMarker) {
                    this.lastMarker = new Fusion.Widget.Measure.Marker(this.units, this.distPrecision, '');
                }
                this.lastMarker.label = v.length + ': ';
                this.updateMarker(this.lastMarker, lastSeg);
            }
        }
    },

    updateArea: function(geom) {
        if (!this.areaMarker) {
            this.areaMarker = new Fusion.Widget.Measure.Marker(this.units, this.areaPrecision, '', true);
        }
        this.updateMarker(this.areaMarker, geom);
    },

    delayUpdate: function(marker, geom) {
        this.delayUpdateTimer = null;
        this.updateMarker(marker, geom);
    },

    updateMarker: function(marker, geom) {
        if (!marker) {
            return;
        }
        var quantity, from, to;
        var v = geom.getVertices();
        var map = this.getMap();
        var proj = map.oMapOL.baseLayer.projection;
        var at = null;
        var pixQuantity = 0;
        if (geom.CLASS_NAME.indexOf('LineString') != -1) {
            from = this.getMap().geoToPix(v[0].x,v[0].y);
            to = this.getMap().geoToPix(v[1].x,v[1].y);
            at = {x: (from.x + to.x) / 2, y: (from.y + to.y) / 2};
            if (this.geodesic) {
              quantity = geom.getGeodesicLength(proj);
            } else {
              quantity = geom.getLength();
            }

            //calculate the length in pixels
            pixQuantity = Math.sqrt((to.x - from.x) * (to.x - from.x) + (to.y - from.y) * (to.y - from.y));

            measureUnits = Fusion.METERS;
            if (measureUnits != this.units) {
              quantity = Fusion.convert(measureUnits, this.units, quantity);
            }
        } else {
            if(geom.getArea() == 0)
                return;

            var cg = geom.getCentroid();
            at = this.getMap().geoToPix(cg.x, cg.y);
            if (this.geodesic) {
              quantity = geom.getGeodesicArea(proj);
            } else {
              quantity = geom.getArea();
            }
            var resolution = this.getMap().getResolution();

            measureUnits = Fusion.METERS;
            if (measureUnits != this.units) {
                var rate = Fusion.convert(measureUnits, this.units, 1);
                quantity = quantity * rate * rate;
                resolution = resolution * rate;
            }
            this.areaMarker.quantity = quantity;

            //calculate the area in square pixels
            pixQuantity = quantity / (resolution*resolution);

        }

        if (pixQuantity > 1) {
            marker.setQuantity(quantity);
            this.positionMarker(marker, at);
        }
    },

    positionMarker: function(marker, at) {
        var oDomElem =  this.getMap().getDomObj();
        if (!marker.domObj.parentNode ||
            marker.domObj.parentNode != oDomElem) {
            oDomElem.appendChild(marker.domObj);
        }
        var size = marker.getSize();
        var t = at.y - size.height/2 ;
        var l = at.x - size.width/2;
        if (!isNaN(t) && !isNaN(l)) {
            marker.domObj.style.top = t + 'px';
            marker.domObj.style.left = l + 'px';
            if(this.segmentLabels === true){
                marker.domObj.style.display = 'block';
            }
        } else {
            if(this.segmentLabels === true){
                marker.domObj.style.display = 'none';
            }
        }
    },

    onKeyPress: function(e) {
        var charCode = (e.charCode ) ? e.charCode : ((e.keyCode) ? e.keyCode : e.which);
        if (charCode == OpenLayers.Event.KEY_ESC) {
            this.deactivate();
            this.activate();
        }
    },

    /**
     * (public) initVars()
     *
     * reset area and/or distance vars
     */
    initVars: function() {
        this.cumulativeDistance = 0;
        this.lastDistance = 0;
        this.cumulativeArea = 0;
        this.lastArea = 0;
        this.aAreaFirstPoint = null;
    },

    activate: function() {
        this.loadDisplayPanel();
        this.startMeasurement();
    },
    
    startMeasurement: function() {
        this.control.activate();
        //We add a stub "stop" link to the MapMessage, then wire it up to stopMeasurement() by fetching the
        //anchor element through its DOM
        var msg = this.getMap().message;
        msg.info(OpenLayers.i18n("measureInProgress") + " <a id='measureMsgDismiss' href='javascript:void(0)'>" + OpenLayers.i18n("stop") + "</a>");
        var link = msg.container.ownerDocument.getElementById("measureMsgDismiss");
        //Wire the anchor click
        link.onclick = OpenLayers.Function.bind(this.stopMeasurement, this);
        
        OpenLayers.Event.observe(document,"keypress",this.keyHandler);
        this.getMap().supressContextMenu(true);
        this.updateButtonStates();
        //This is digitization, so trigger the necessary event
        this.getMap().triggerEvent(Fusion.Event.MAP_DIGITIZER_ACTIVATED);
    },
    
    stopMeasurement: function() {
        OpenLayers.Event.stopObserving(document, 'keypress', this.keyHandler);
        this.control.deactivate();
        this.control.cancel();
        this.getMap().message.clear();
        this.getMap().supressContextMenu(false);
        this.updateButtonStates();
        //This is digitization, so trigger the necessary event
        this.getMap().triggerEvent(Fusion.Event.MAP_DIGITIZER_DEACTIVATED);
    },
    
    updateButtonStates: function() {
        if (this.startButton != null && this.stopButton != null) {
            this.stopButton.disabled = !this.control.active;
            this.startButton.disabled = this.control.active;
        }
    },
    
    setButtons: function(stopBtn, startBtn) {
        this.startButton = startBtn;
        this.stopButton = stopBtn;
        this.updateButtonStates();
    },
    
    initManualControls: function(outputWin) {
        this.startButton = null;
        this.stopButton = null;
        
        var timer;
        var that = this;
        var watch = function() {
            try {
                if (outputWin.domInit) {
                    doc = outputWin.document;
                    clearInterval(timer);
                    outputWin.SetWidget(that); //Hook the widget for auto-deactivation
                }
            } catch (e) {
            }
        };
        timer = setInterval(watch, 200);
    },

    loadDisplayPanel: function() {
        if (this.sTarget) {
            var url = this.sBaseUrl;
            var queryStr = 'locale='+Fusion.locale;
            if (url.indexOf('?') < 0) {
                url += '?';
            } else if (url.slice(-1) != '&') {
                url += '&';
            }
            url += queryStr + '&type='+this.measureType;

            var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
            var outputWin = window;
            if ( taskPaneTarget ) {
                if(!taskPaneTarget.isSameWithLast(url))
                {
                    taskPaneTarget.setContent(url);
                }
                outputWin = taskPaneTarget.iframe.contentWindow;
            } else {
                outputWin = window.open(url, this.sTarget, this.sWinFeatures);
            }
            this.initManualControls(outputWin);
            this.registerForEvent(Fusion.Event.MEASURE_CLEAR, OpenLayers.Function.bind(this.clearDisplay, this, outputWin));
            this.registerForEvent(Fusion.Event.MEASURE_SEGMENT_UPDATE, OpenLayers.Function.bind(this.updateDisplay, this, outputWin));
            this.registerForEvent(Fusion.Event.MEASURE_COMPLETE, OpenLayers.Function.bind(this.updateDisplay, this, outputWin));
        } else {
          if (this.measureType & Fusion.Constant.MEASURE_TYPE_DISTANCE) {
              this.totalDistanceMarker = new Fusion.Widget.Measure.Marker(this.units, this.distPrecision, 'Total:');
              var oDomElem =  this.getMap().getDomObj();
              if (!this.totalDistanceMarker.domObj.parentNode ||
                  this.totalDistanceMarker.domObj.parentNode != oDomElem) {
                  oDomElem.appendChild(this.totalDistanceMarker.domObj);
              }
              this.totalDistanceMarker.domObj.addClass('divMeasureTotal');
              this.totalDistanceMarker.domObj.style.display = 'none';
              this.registerForEvent(Fusion.Event.MEASURE_CLEAR, OpenLayers.Function.bind(this.clearTotalDistance, this));
              this.registerForEvent(Fusion.Event.MEASURE_SEGMENT_UPDATE, OpenLayers.Function.bind(this.updateTotalDistance, this));
              this.registerForEvent(Fusion.Event.MEASURE_COMPLETE, OpenLayers.Function.bind(this.updateTotalDistance, this));
          }
        }
    },

    /**
     * (public) deactivate()
     *
     * deactivate the ruler tool
     */
    deactivate: function() {
        this.stopMeasurement();
    },

    resetMeasure: function() {
        this.initVars();
        for (var i=0; i<this.distanceMarkers.length; i++)  {
            this.distanceMarkers[i].destroy();
        }
        this.distanceMarkers = [];
        if (this.areaMarker) {
            this.areaMarker.destroy();
            this.areaMarker = null;
        }
        if (this.lastMarker) {
            this.lastMarker.destroy();
            this.lastMarker = null;
        }
        this.triggerEvent(Fusion.Event.MEASURE_CLEAR, this);
    },

    remoteMeasureSegment: function(marker, from, to, geom) {
        var mapWidget = this.getMap();
        var widgetLayer = this.getMapLayer();
        var s = 'layers/' + widgetLayer.arch + '/' + Fusion.getScriptLanguage() + "/Measure." + Fusion.getScriptLanguage();
        var fromGeo = mapWidget.pixToGeo(from.x, from.y);
        var toGeo = mapWidget.pixToGeo(to.x, to.y);
        var options = {
            parameters: {
                'session': widgetLayer.getSessionID(),
                'locale': Fusion.locale,
                'mapname': mapWidget.getMapName(),
                'x1': fromGeo.x,
                'y1': fromGeo.y,
                'x2': toGeo.x,
                'y2': toGeo.y
            },
            'onComplete': OpenLayers.Function.bind(this.remoteMeasureCompleted, this, from, to, marker)
        };
        Fusion.ajaxRequest(s, options);
    },

    remoteMeasureCompleted: function(from, to, marker, r) {
        if (r.status == 200) {
            var o = Fusion.parseJSON(r.responseText);
            if (o.distance) {
              /* distance returned is always in meters*/
              //var mapUnits = Fusion.unitFromName(this.getMap().getUnits());
              //if (mapUnits == Fusion.DEGREES || Fusion.DECIMALDEGREES)
              mapUnits = Fusion.METERS;

              if (mapUnits != this.units) {
                o.distance = Fusion.convert(mapUnits, this.units, o.distance);
              }

              marker.setQuantity(o.distance);
              this.positionMarker(marker, from, to);
              this.triggerEvent(Fusion.Event.MEASURE_SEGMENT_UPDATE);
            }
        }
    },

    /*
     * updates the summary display if it is loaded in a window somewhere
     */
    updateDisplay: function(outputWin) {
        var outputDoc = outputWin.document;
        var resolution = this.getMap().getResolution();
        this.clearDisplay(outputWin);
        var units = Fusion.unitAbbr(this.units);
        var value;
        var distPrecision = this.distPrecision;
        var createEntry = function(idx, distance, resolution) {
            if (distance / resolution < 1) {
                return;
            }
            var tr = outputDoc.createElement('tr');
            var td = outputDoc.createElement('td');
            td.innerHTML = OpenLayers.i18n('segment',{'seg':idx});
            tr.appendChild(td);
            td = outputDoc.createElement('td');
            if (distPrecision == 0) {
              value = Math.floor(distance);
            }
            else {
              value = distance.toPrecision(distPrecision);
            }
            td.innerHTML = value + ' ' + units;
            tr.appendChild(td);
            tbody.appendChild(tr);
        }
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_DISTANCE) {
            var tbody = outputDoc.getElementById('segmentTBody');
            var totalDistance = 0;
            if (tbody) {
                for (var i=0; i<this.distanceMarkers.length; i++) {
                    var distance = this.distanceMarkers[i].getQuantity();
                    totalDistance += distance;
                    createEntry(i+1, distance, resolution);
                }
                if (this.lastMarker) {
                    totalDistance += this.lastMarker.getQuantity();
                    createEntry(i+1, this.lastMarker.getQuantity(), resolution);
                }
                var tDist = outputDoc.getElementById('totalDistance');
                if (this.distPrecision == 0) {
                      value = Math.floor(totalDistance);
                }
                else {
                  value = totalDistance.toPrecision(this.distPrecision);
                }
                tDist.innerHTML = value + ' ' + units;
            }
        }
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_AREA) {
            var tArea = outputDoc.getElementById('totalArea');
            if (tArea) {
                value = this.areaMarker.getQuantity();
                if (this.areaPrecision == 0) {
                    value = Math.floor(value);
                } else {
                    value = value.toPrecision(this.areaPrecision);
                }
                tArea.innerHTML = value + ' ' + units + '<sup>2</sup>';
            }
        }
    },

    /*
     * updates the summary display if it is loaded in a window somewhere
     */
    updateTotalDistance: function() {
      	var totalDistance = 0;
        var units = Fusion.unitAbbr(this.units);
        for (var i=0; i<this.distanceMarkers.length; i++) {
            var distance = this.distanceMarkers[i].getQuantity();
            totalDistance += distance;
        }
        if (this.lastMarker) {
            var lastDist = this.lastMarker.getQuantity();
            //only add this in if it's a multi-point geometry
            if (lastDist != totalDistance) {
                totalDistance += lastDist;
            }
        }
        this.totalDistanceMarker.domObj.style.display = 'block';
        this.totalDistanceMarker.setQuantity(totalDistance);
        
        if (this.distPrecision == 0) {
          totalDistance = Math.floor(totalDistance);
        } else {
          totalDistance = totalDistance.toPrecision(this.distPrecision);
        }
        this.totalLength = totalDistance;
        if (this.measureType & Fusion.Constant.MEASURE_TYPE_AREA) {
          var value = this.areaMarker.getQuantity();
          if (this.areaPrecision == 0) {
            value = Math.floor(value);
          } else {
            value = value.toPrecision(this.areaPrecision);
          }
          this.totalArea = value;
        }
    },

  /*
      *clears the summary display if it is loaded in a window somewhere
      */
    clearDisplay: function(outputWin) {
        var outputDoc = outputWin.document;
        var tbody = outputDoc.getElementById('segmentTBody');
        if (tbody) {
          while(tbody.firstChild) {
              tbody.firstChild.marker = null;
              tbody.removeChild(tbody.firstChild);
          }
          var tDist = outputDoc.getElementById('totalDistance');
          tDist.innerHTML = '';
          var tArea = outputDoc.getElementById('totalArea');
          if(tArea){
              tArea.innerHTML = '';
          }
        }
    },

  /*
      *clears the summary display if it is loaded in a window somewhere
      */
    clearTotalDistance: function() {
      this.totalDistanceMarker.domObj.style.display = 'none';
      this.totalLength = 0;
    },

  /*
     * Callback method for the MAP_LOADED event
     * Set the units to whatever is specified in the AppDef, or the mapUnits if not specified
     * Subsequent calls from a ViewOptions widget would override the value specified.
     */
    setUnits: function(units) {
      var map = this.getMap();
      units = (units == Fusion.UNKNOWN)?Fusion.unitFromName(map.getUnits()):units;
      this.setParameter('Units', Fusion.unitName(units));
      if (map.oMapOL.baseLayer.projection.proj && map.oMapOL.baseLayer.projection.proj.localCS) {
        this.geodesic = false;
      }
    },

    setParameter: function(param, value) {
      //console.log('setParameter: ' + param + ' = ' + value);
        if (param == 'Units') {
            this.units = Fusion.unitFromName(value);
            for (var i=0; i<this.distanceMarkers.length; i++) {
                this.distanceMarkers[i].setUnits(this.units);
            }
            if (this.totalDistanceMarker) {
              this.totalDistanceMarker.setUnits(this.units);
            }
        }
    }
});

/*
* A class for handling the 'tooltip' for the distance measurement.  Markers also hold the distance
values and all markers are held in an array in the Measure widget for access.
*/
Fusion.Widget.Measure.Marker = OpenLayers.Class(
{
    calculatingImg: null,
    quantity: 0,
    isArea: false,
    initialize: function(units, precision, label, isArea) {
        this.precision = precision;
        this.label = label ? label:'';
        this.isArea = isArea || false;
        this.domObj = new Element('DIV', {});

       this.domObj.className = 'divMeasureMarker';
        this.calculatingImg = document.createElement('img');
        this.calculatingImg.src = Fusion.getFusionURL() + 'widgets/Measure/MeasurePending.gif';
        this.calculatingImg.width = 19;
        this.calculatingImg.height = 4;
        this.setUnits(units);
        this.setCalculating();
    },

    destroy: function() {
      if (this.domObj.parentNode) {
          this.domObj.parentNode.removeChild(this.domObj);
          this.domObj.style.display = 'none'; //Also hide it because Safari leaves the domObj on the page
      }
    },

    setUnits: function(units) {
        this.unit = units;
        this.unitAbbr = Fusion.unitAbbr(units);
    },

    getQuantity: function() {
        return this.quantity;
    },

    getQuantityLabel: function() {
      var value;
      if (this.precision == 0) {
          value = Math.floor(this.quantity);
      } else {
          value = this.quantity.toPrecision(this.precision);
      }
      var sq = '';
      if (this.isArea) {
          sq = '<sup>2<sup>';
      }
      return this.label + ' ' + value + ' ' + this.unitAbbr + sq;
    },

    setQuantity: function(quantity) {
        if (this.calculatingImg.parentNode) {
            this.calculatingImg.parentNode.removeChild(this.calculatingImg);
        }
        this.quantity = quantity;
        this.domObj.innerHTML = this.getQuantityLabel();
    },

    setCalculating: function() {
        if (!this.calculatingImg.parentNode) {
            this.domObj.innerHTML = '';
            this.domObj.appendChild(this.calculatingImg);
        }
    },

    getSize: function() {
        var size =  $(this.domObj).getBorderBoxSize();
        var imgSize = {width:19, height:4};
        if (size.width < imgSize.width) {
            size.width += imgSize.width;
        }
        if (size.height < imgSize.height) {
            size.height += imgSize.height;
        }
        return size;
    }
});
/**
 * Fusion.Widget.Navigator
 *
 * $Id: Navigator.js 2690 2013-04-24 11:03:56Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Navigator
 *
 * A widget that immplements an in-map navigation control with zoom and pan.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Navigator = OpenLayers.Class(Fusion.Widget, {
    bInternalChange: false,
    zoomInFactor: 4,
    zoomOutFactor: 2,
    panAmount: 50,
    initializeWidget: function(widgetTag) {
        this.activeControls = [];

        var m = document.createElement('map');
        m.name = 'Navigator_ImageMap';
        m.id = 'Navigator_ImageMap';

        var a = document.createElement('area');
        a.shape = 'poly';
        a.alt = OpenLayers.i18n('panEast');
        a.title = OpenLayers.i18n('panEast');
        a.coords = '27,176, 27,177, 40,190, 44,182, 44,159';
        var panEast = OpenLayers.Function.bind(this.pan, this, this.panAmount/100, 0);
        OpenLayers.Event.observe(a, 'mousedown', OpenLayers.Function.bindAsEventListener(panEast, this));
        m.appendChild(a);

        var a = document.createElement('area');
        a.shape = 'poly';
        a.alt = OpenLayers.i18n('panWest');
        a.title = OpenLayers.i18n('panWest');
        a.coords = '24,177, 24,176, 7,159, 7,182, 11,190';
        var panWest = OpenLayers.Function.bind(this.pan, this, -this.panAmount/100, 0);
        OpenLayers.Event.observe(a, 'mousedown', OpenLayers.Function.bindAsEventListener(panWest, this) );
        m.appendChild(a);

        var a = document.createElement('area');
        a.shape = 'poly';
        a.alt = OpenLayers.i18n('panSouth');
        a.title = OpenLayers.i18n('panSouth');
        a.coords = '25,178, 12,191, 21,197, 30,197, 39,191, 26,178';
        var panSouth = OpenLayers.Function.bind(this.pan, this, 0, -this.panAmount/100 );
        OpenLayers.Event.observe(a, 'mousedown', OpenLayers.Function.bindAsEventListener(panSouth, this) );
        m.appendChild(a);

        var a = document.createElement('area');
        a.shape = 'poly';
        a.alt = OpenLayers.i18n('panNorth');
        a.title = OpenLayers.i18n('panNorth');
        a.coords = '26,175, 43,158, 8,158, 25,175';
        var panNorth = OpenLayers.Function.bind(this.pan, this, 0, this.panAmount/100 );
        OpenLayers.Event.observe(a, 'mousedown', OpenLayers.Function.bindAsEventListener(panNorth, this) );
        m.appendChild(a);

        var a = document.createElement('area');
        a.shape = 'circle';
        a.alt = OpenLayers.i18n('zoomOut');
        a.title = OpenLayers.i18n('zoomOut');
        a.coords = '25,142,8';
        var zoomOut = OpenLayers.Function.bind(this.zoom, this, 1/this.zoomOutFactor);
        OpenLayers.Event.observe(a, 'mousedown', OpenLayers.Function.bindAsEventListener(zoomOut, this) );
        m.appendChild(a);

        var a = document.createElement('area');
        a.shape = 'circle';
        a.alt = OpenLayers.i18n('zoomIn');
        a.title = OpenLayers.i18n('zoomIn');
        a.coords = '25,34,8';
        var zoomIn = OpenLayers.Function.bind(this.zoom, this, this.zoomInFactor);
        OpenLayers.Event.observe(a, 'mousedown', OpenLayers.Function.bindAsEventListener(zoomIn, this) );
        m.appendChild(a);

        this.domObj.appendChild(m);

        var sliderBg = document.createElement('img');
        sliderBg.src = Fusion.getFusionURL() + widgetTag.location + 'Navigator/sliderscale.png';
        sliderBg.className = 'png24';
        sliderBg.width = 51;
        sliderBg.height = 201;
        sliderBg.style.position = 'absolute';
        sliderBg.style.left = '0px';
        sliderBg.style.top = '0px';
        sliderBg.useMap = '#Navigator_ImageMap';
        this.domObj.appendChild(sliderBg);

        var handleDiv = document.createElement('div');
        handleDiv.style.position = 'absolute';
        handleDiv.style.top = '6px';
        handleDiv.style.left = '6px';
        handleDiv.style.width = '39px';
        handleDiv.style.height = '16px';
        this.domObj.appendChild(handleDiv);

        var sliderDiv = document.createElement('div');
        sliderDiv.style.position = 'absolute';
        sliderDiv.style.top = '44px';
        sliderDiv.style.left = '0px';
        sliderDiv.style.width = '51px';
        sliderDiv.style.height = '85px';
        this.domObj.appendChild(sliderDiv);

        var sliderHandle = document.createElement('img');
        sliderHandle.src = Fusion.getFusionURL() + widgetTag.location + 'Navigator/slider.png';
        sliderHandle.className = 'png24';
        sliderHandle.width = 29;
        sliderHandle.height = 12;
        sliderHandle.style.position = 'absolute';
        sliderHandle.style.left = '11px';
        sliderHandle.style.top = '49px';
        sliderDiv.appendChild(sliderHandle);

        this.activityIndicator = document.createElement('img');
        this.activityIndicator.src = Fusion.getFusionURL() + widgetTag.location + 'Navigator/spinner.gif';
        this.activityIndicator.width = 18;
        this.activityIndicator.height = 6;
        this.activityIndicator.style.position = 'absolute';
        this.activityIndicator.style.top = '3px';
        this.activityIndicator.style.right = '4px';
        handleDiv.appendChild(this.activityIndicator);

        this.domObj.style.position = 'absolute';
        this.domObj.style.zIndex = 1000;
        this.domObj.style.width = '51px';
        this.domObj.style.height = '204px';
        this.domObj.style.cursor = 'pointer';
        // need to disable active map controls when the mouse is over the navigator
        this.domObj.addEvents({
          mouseenter: OpenLayers.Function.bind(this.mouseEnter,this),
          mouseleave: OpenLayers.Function.bind(this.mouseLeave,this)
        });

        var checkPosition = OpenLayers.Function.bind(this.checkPosition, this);

        //set up the navigator as draggable
        new Drag(this.domObj, {
            handle: handleDiv,
            onComplete: checkPosition,
            preventDefault: true
        });

        this.slider = new Slider(sliderDiv, sliderHandle, {
            mode: 'vertical',
            steps: 81,
            snap: true,
            onComplete: OpenLayers.Function.bind(this.scaleChanged, this)
        });
        // precompute this for efficiency
        this.LN9 = Math.log(9);

        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.updateSlider, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_RESIZED, checkPosition);
        this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, OpenLayers.Function.bind(this.updateSlider, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_BUSY_CHANGED, OpenLayers.Function.bind(this.busyChanged, this));
    },

    scaleChanged: function(value) {
        var map = this.getMap();
        var activeWidget = null;
        if (map.oActiveWidget) {
          activeWidget = map.oActiveWidget;
          map.deactivateWidget(map.oActiveWidget);
        }
        if (!this.bInternalChange) {
            var olMap = this.getMap().oMapOL;
            var baseLayer = olMap.baseLayer;
            if (baseLayer != null) {
                if (baseLayer.singleTile) {
                    var scale = Math.pow(9,value/9);
                    var resolution = OpenLayers.Util.getResolutionFromScale(scale, baseLayer.units);
                    resolution += baseLayer.minResolution;
                    this.bInternalChange = true;
                    olMap.zoomTo(olMap.getZoomForResolution(resolution));
                    this.bInternalChange = false;
                } else {
                    var zoom = olMap.baseLayer.resolutions.length - value - 1;
                    this.bInternalChange = true;
                    olMap.zoomTo(zoom);
                    this.bInternalChange = false;
                }
            }
        }

        if (activeWidget) {
          map.activateWidget(activeWidget);
        }
        return false;
    },

    checkPosition: function() {
        var nav = this.domObj;
        var pDim = $(nav.parentNode).getContentBoxSize();
        var nLeft, nTop;
        nLeft = parseInt(nav.style.left);
        nTop = parseInt(nav.style.top);
        if (nLeft + nav.getWidth() > pDim.width) {
            nLeft = pDim.width - nav.getWidth();
            nav.style.left = nLeft + 'px';
        }
        if (nTop + nav.getHeight() > pDim.height) {
            nTop = pDim.height - nav.getHeight();
            nav.style.top = nTop + 'px';
        }
        if (nLeft < 0) {
            nav.style.left = '0px';
        }
        if (nTop < 0) {
            nav.style.top = '0px';
        }
    },

    updateSlider: function() {
        var map = this.getMap().oMapOL;
        baseLayer = map.baseLayer
        if (baseLayer.singleTile) {
            this.slider.steps = 81;
            var resolution = map.getResolution() - baseLayer.minResolution;
            var scale = OpenLayers.Util.getScaleFromResolution(resolution, baseLayer.units);
            var position = 9*Math.log(scale)/this.LN9;
            this.bInternalChange = true;
            this.slider.set(position);
            this.bInternalChange = false;
        } else {
            this.slider.steps = map.baseLayer.resolutions.length - 1;
            var position = map.baseLayer.resolutions.length -1 - map.getZoom();
            this.bInternalChange = true;
            this.slider.set(position);
            this.bInternalChange = false;
        }
    },

    pan: function(x,y,e) {
        //console.log('pan by : ' + x + ', ' + y);
        var map = this.getMap();
        var center = map.getCurrentCenter();
        var res = map.oMapOL.getResolution();
        var size = map.oMapOL.getSize();
        map.zoom(center.x + (x * size.w * res), center.y + (y * size.h * res), 1);
        //new Event(e).stop();
        OpenLayers.Event.stop(e);
        return false;
    },

    zoom: function(factor, e) {
        //console.log('zoom by factor: ' + factor);
        var map = this.getMap();
        var center = map.getCurrentCenter();
        map.zoom(center.x, center.y, factor);
        OpenLayers.Event.stop(e);
        return false;
    },

    mouseEnter: function() {
        var mapWidget = this.getMap();
        var mapOL = mapWidget.oMapOL;
        for (var i=0; i<mapOL.controls.length; ++i) {
          var control = mapOL.controls[i];
          if (control.active) {
            control.deactivate();
            this.activeControls.push(control);
          }
        }
        for (var i=0; i<mapWidget.handlers.length; ++i) {
          var handler = mapWidget.handlers[i];
          if (handler.active) {
            if(handler.CLASS_NAME == "OpenLayers.Handler.Box" && handler.zoomBox != undefined){
              handler.removeBox();
            }
            handler.deactivate();
            this.activeControls.push(handler);
          }
        }
    },

    mouseLeave: function() {
        var mapOL = this.getMap().oMapOL;
        while (this.activeControls.length>0) {
          var control = this.activeControls.pop();
          control.activate();
        }
    },

    busyChanged: function() {
        this.activityIndicator.style.visibility = this.getMap().isBusy() ? 'visible' : 'hidden';
    }


});
/**
 * Fusion.Widget.OverviewMap
 *
 * $Id: OverviewMap.js 2881 2014-09-27 07:07:46Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.OverviewMap
 *
 * A widget that displays an overview map showing the current view of the
 * primary map.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.OverviewMap = OpenLayers.Class(Fusion.Widget, {
    oSize: null,
    nMinRatio: 4, // Default value
    nMaxRatio: 32, // Default value
    bDisplayed: false,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        if (json.MinRatio) {
            this.nMinRatio = json.MinRatio[0];
        }
        if (json.MaxRatio) {
            this.nMaxRatio = json.MaxRatio[0];
        }

        var mapTag = null;

        this.sMapGroupId = json.MapId;

        // Set the size to the size of the DOM element if available
        if (this.domObj) {
            this.domObj.style.overflow = 'hidden';
            var jxl = this.domObj.retrieve('jxLayout');
            if (!jxl) {
                jxl = new Jx.Layout(this.domObj);
            }
            jxl.addEvent('sizeChange', OpenLayers.Function.bind(this.sizeChanged, this));
        }

        this.oMapOptions = {};  //TODO: allow setting some mapOptions in AppDef

        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.mapWidgetLoaded, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_BASE_LAYER_CHANGED, OpenLayers.Function.bind(this.mapBaseLayerChanged, this));
    },

    mapWidgetLoaded: function()
    {
        var mapTag = null;
        if (this.sMapGroupId)
        {
            // Use the specified map in the overview
            var mapGroup = Fusion.applicationDefinition.getMapGroup(this.sMapGroupId);
            mapTag = mapGroup.maps[0];    //TODO: always use the baselayer Map in the group?
        }
        else
        {
            // Use the same map as displayed in the main map widget in the overview
            var mainMap = this.getMap();
            mapTag = mainMap.mapGroup.maps[0];    //TODO: always use the baselayer Map in the group?
        }

        if (Fusion.Layers[mapTag.type])
        {
            // Create a Fusion layer of the specified type
            this.mapObject = new Fusion.Layers[mapTag.type](this.getMap(), mapTag, false);
        }
        else
        {
            // Create a generic Fusion layer (as used by Bing, Google, Yahoo etc.)
            this.mapObject = new Fusion.Layers.Generic(this, mapTag, false);
        }

        // Set up the binding so the display initializes when the map configuration has loaded
        this.mapObject.registerForEvent(Fusion.Event.LAYER_LOADED, OpenLayers.Function.bind(this.loadOverview, this));
    },

    loadOverview: function()
    {
        if (this.control) {
          this.control.destroy();
        }

        var layer = this.mapObject.oLayerOL;
        if(layer != null)
        {
            var size = $(this.domObj).getContentBoxSize();
            this.oSize = new OpenLayers.Size(size.width, size.height);
            layer.isBaseLayer = true;
            layer.ratio = 1.0;
            if (layer.singleTile) {
              this.oMapOptions.numZoomLevels = 3;  //TODO: make this configurable?
            }

            ovLayers = [layer]; // layers in Overview Map
            
            // the last layer on the bottom
            if(this.mapObject.oLayersOLTile && this.mapObject.oLayersOLTile.length > 1) {
                for(var i=this.mapObject.oLayersOLTile.length-2; i>=0; i--) {
                    // Workaround to make multiple baselayers display. 
                    // Openlayers only supports single baselayer.
                    this.mapObject.oLayersOLTile[i].isBaseLayer = false;
                    
                    ovLayers.push(this.mapObject.oLayersOLTile[i]);
                }
            }
            
            // Check if we have any external layers, if so add them too.
            var bHasOtherLayers = false;
            var otherLayers = this.getMap().getAllMaps();
            for (var i = 0; i < otherLayers.length; i++) {
                if (otherLayers[i].arch != "MapGuide") {
                    var olLayer = otherLayers[i].oLayerOL.clone();
                    ovLayers.push(olLayer);
                    bHasOtherLayers = true;
                }
            }
            // If we found external layers, the MapGuide one is no longer a base layer but an overlay
            if (bHasOtherLayers) {
                layer.isBaseLayer = false;
            }
            
            var options = {
              div: this.domObj,
              size: this.oSize,
              minRatio: this.nMinRatio,
              maxRatio: this.nMaxRatio,
              mapOptions: this.oMapOptions,
              layers: ovLayers
            };

            this.control = new OpenLayers.Control.OverviewMap(options);
            if (size.width == 0 || size.height == 0)
            {
                return;   //don't try to load if the container is not visible
            }
            else
            {
                this.getMap().oMapOL.addControl(this.control);
                this.bDisplayed = true;
            }
            //console.log('OverviewMap mapLoaded');
        }
    },
    
    mapBaseLayerChanged: function(evtId, newBaseLayer) {
        //OverviewMap control not created yet. Come back later
        if (!this.control) {
            return;
        }
        var newOLLayer = newBaseLayer.oLayerOL;
        //Update ov map's base layer to match
        var layers = this.control.ovmap.layers;
        var newOvLayer = null;
        for (var i = 0; i < layers.length; i++) {
            layers[i].isBaseLayer = false;
            if (layers[i].name == newOLLayer.name) {
                newOvLayer = layers[i];
            }
        }
        if (newOvLayer != null) {
            this.control.ovmap.setBaseLayer(newOvLayer);
        }
    },

    sizeChanged: function() {
        var size = $(this.domObj).getContentBoxSize();
        this.oSize = new OpenLayers.Size(size.width, size.height);
        if (size.width == 0 || size.height == 0) {
          return;   //don't try to load if the container is not visible
        }
        if (!this.bDisplayed && this.control) {
          this.getMap().oMapOL.addControl(this.control);
          this.bDisplayed = true;
        }
        if (this.control) {
            this.control.size = new OpenLayers.Size(size.width, size.height);
            this.control.mapDiv.style.width = this.oSize.w + 'px';
            this.control.mapDiv.style.height = this.oSize.h + 'px';
            this.control.ovmap.updateSize();
            this.control.update();
        }
    }

});

/**
 * Fusion.Widget.Pan
 *
 * $Id: Pan.js 2686 2013-04-05 11:44:39Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Pan
 *
 * A widget that allows for naviagtion by panning
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Pan = OpenLayers.Class(Fusion.Widget, {
    isExclusive: true,
    uiClass: Jx.Button,
    initializeWidget: function(widgetTag) {
        this.control = new OpenLayers.Control.DragPan();
        this.getMap().oMapOL.addControl(this.control);
        this.control.handler.keyMask = 0;
        var index = window.location.href.indexOf("?");
        var mainpath = window.location.href.substring(0,index);
        index = mainpath.lastIndexOf("/");
        mainpath = mainpath.substring(0,index+1);
        var grabpath= "url(" + mainpath + "images/grab.cur" + "), move";
        var grabbingpath = "url(" + mainpath + "images/grabbing.cur" + "), move";
        
        this.cursorNormal = [grabpath, 'grab', '-moz-grab', 'move'];
        this.cursorDrag = [grabbingpath, 'grabbing', '-moz-grabbing', 'move'];
        
        //We're interested in these events, because this widget could get in the way
        this.getMap().registerForEvent(Fusion.Event.MAP_DIGITIZER_ACTIVATED, OpenLayers.Function.bind(this.onDigitizerActivated, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_DIGITIZER_DEACTIVATED, OpenLayers.Function.bind(this.onDigitizerDeactivated, this));
    },
    
    onDigitizerActivated: function() {
        this.bRestoreActiveState = this.isActive;
        //TODO: What we really want is to do is filter out the valid mouse buttons for a drag. So we can
        //still pan while digitizing via the middle mouse button.
        //This is an OpenLayers-level limitation (the drag handler is hardcoded to left mouse button)
        this.deactivate();
    },
    
    onDigitizerDeactivated: function() {
        if (this.bRestoreActiveState === true) {
            this.activate();
            this.bRestoreActiveState = false;
        }
    },

    activate : function() {
        //TODO: Why Fusion.Widget has no active state flag? Probably should have one
        this.isActive = true;
        this.control.activate();
        this.getMap().setCursor(this.cursorNormal);
    },
    
    deactivate: function() {
        this.isActive = false;
        /*console.log('Pan.deactivate');*/
        this.control.deactivate();
        this.getMap().setCursor('auto');
    }
});
/**
 * Fusion.Widget.PanOnClick
 *
 * $Id: PanOnClick.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.PanOnClick
 *
 * Pans the map a fixed amount in a particular direction
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.PanOnClick = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    fPercent: null,
    nDeltaX: null,
    nDeltaY: null,
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        var percent = json.Percentage ? json.Percentage[0] : 75;
        this.fPercent = parseFloat(percent)/100;
        
        var direction = json.Direction ? json.Direction[0] : '';
        switch (direction) {
            case 'north':
                this.nDeltaX = 0;
                this.nDeltaY = 1;
                break;
            case 'south':
                this.nDeltaX = 0;
                this.nDeltaY = -1;
                break;
            case 'east':
                this.nDeltaX = 1;
                this.nDeltaY = 0;
                break;
            case 'west':
                this.nDeltaX = -1;
                this.nDeltaY = 0;
                break;
            default:
                this.nDeltaX = 0;
                this.nDeltaY = 0;
        }
        
    },

    /**
     * called when the button is clicked by the Fusion.Widget widget
     */
    activate: function() {
        var extents = this.getMap().getCurrentExtents();
        var center = this.getMap().getCurrentCenter();
        var fX, fY;
        fX = center.x + this.nDeltaX * (extents[2] - extents[0]) * this.fPercent;
        fY = center.y + this.nDeltaY * (extents[3] - extents[1]) * this.fPercent;
        this.getMap().zoom(fX, fY, 1);
    }
});/**
 * Fusion.Widget.PanQuery
 *
 * $Id: PanQuery.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.PanQuery
 *
 * A widget that combines pan and query functionality.  If the mouse is moved
 * before being released, a pan is performedd, otherwise a query is executed.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

//Fusion.require('widgets/Pan.js');

Fusion.Widget.PanQuery = OpenLayers.Class(Fusion.Widget, {
    isExclusive: true,
    uiClass: Jx.Button,
    selectionType: 'INTERSECTS',
    nTolerance: 3,
    bActiveOnly: false,
    initializeWidget: function(widgetTag) {
        this.control = new OpenLayers.Control.DragPan();
        this.getMap().oMapOL.addControl(this.control);
        //TODO figure out how to set the mouseup via handlerOptions
        this.control.handler.up = OpenLayers.Function.bind(this.mouseUp, this);
        
        var json = widgetTag.extension;
        
        this.nTolerance = json.Tolerance ? Math.abs(parseInt(json.Tolerance)) : 3;
        this.bComputeMetadata = (json.ComputeMetadata &&
                           (json.ComputeMetadata[0] == 'true' ||
                            json.ComputeMetadata[0] == '1')) ? true : false;
        

        var activeOnly = json.QueryActiveLayer ? json.QueryActiveLayer[0] : 'false';
        this.bActiveOnly = (activeOnly == 'true' || activeOnly == '1') ? true : false;
        
        this.cursorNormal = ['auto'];
        this.cursorDrag = ["url('images/grabbing.cur'),move", 'grabbing', '-moz-grabbing', 'move'];
    },

    /**
     * (private) gPan.MouseUp(e)
     *
     * handle mouseup events on the mapObj
     *
     * @param e Event the event that happened on the mapObj
     */
    mouseUp: function(e) {
        //this.getMap().setCursor(this.cursorNormal);
        var handler = this.control.handler;
        
        var p = {x:Event.pointerX(e), y:Event.pointerY(e)};    

        var dx = handler.start.x - handler.last.x;
        var dy = handler.start.y - handler.last.y;
        
        if (Math.abs(dx) < this.nTolerance && Math.abs(dy) < this.nTolerance) {
            //execute query
            var pos = this.getMap().pixToGeo(handler.last.x, handler.last.y);
            var options = {};
            var dfGeoTolerance = this.getMap().pixToGeoMeasure(this.nTolerance);
            var minx = pos.x-dfGeoTolerance; 
            var miny = pos.y-dfGeoTolerance; 
            var maxx = pos.x+dfGeoTolerance; 
            var maxy = pos.y+dfGeoTolerance;
            options.geometry = 'POLYGON(('+ minx + ' ' + miny + ', ' + maxx + ' ' + miny + ', ' + maxx + ' ' + maxy + ', ' + minx + ' ' + maxy + ', ' + minx + ' ' + miny + '))';
            options.selectionType = "INTERSECTS";
            options.computed = this.bComputeMetadata;

            if (this.bActiveOnly) {
                var layer = this.getMap().getActiveLayer();
                if (layer) {
                    options.layers = layer.layerName;
                } else {
                    return;
                }
            }

            if (e.shiftKey) {
                options.extendSelection = true;
            }

            this.getMap().query(options);
        }
        Event.stop(e);
    },    
    activate : function() {
        this.control.activate();
        this.getMap().setCursor(this.cursorNormal);
    },
    
    deactivate: function() {
        /*console.log('Pan.deactivate');*/
        this.control.deactivate();
        this.getMap().setCursor('auto');
    },
    
    setParameter : function(param, value) {
        if (param == "Tolerance" && value > 0) {
            this.nTolerance = value;
        }
        if (param == 'SelectionType') {
            this.selectionType = value;
        }
    }
});/**
 * Fusion.Widget.Print
 *
 * $Id: Print.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Print
 *
 * Print the current map.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Print = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        var showPrintUI = json.ShowPrintUI ? json.ShowPrintUI[0] : 'false';
        this.showPrintUI = (showPrintUI.toLowerCase() == 'true' || showPrintUI == '1');
        
        var showTitle = json.ShowTitle ? json.ShowTitle[0] : 'false';
        this.showTitle = (showTitle.toLowerCase() == 'true' || showTitle == '1');

        this.pageTitle = json.PageTitle ? json.PageTitle[0] : '';
        
        this.resultsLayer = json.ResultsLayer ? json.ResultsLayer[0] : null;

        var showLegend = json.ShowLegend ? json.ShowLegend[0] : 'false';
        this.showLegend = (showLegend.toLowerCase() == 'true' || showLegend == '1');
        
        var showNorthArrow =json.ShowNorthArrow ? json.ShowNorthArrow[0] : 'false';
        this.showNorthArrow = (showNorthArrow.toLowerCase() == 'true' || showNorthArrow == '1');
        
        this.imageBaseUrl = json.ImageBaseUrl ? json.ImageBaseUrl[0] : null;
        
        this.dialogContentURL = Fusion.getFusionURL() + widgetTag.location + 'Print/Print.html';
        this.printablePageURL = Fusion.getFusionURL() + widgetTag.location + 'Print/printablepage.php';
        Fusion.addWidgetStyleSheet(widgetTag.location + 'Print/Print.css');
        
        /*
         * TODO: this is bad, why did we do this?
         this.getMap().registerForEvent(Fusion.Event.SELECTION_COMPLETE, OpenLayers.Function.bind(this.getSelection, this));
         */
        
    },
    /**
     * load an interface that builds a printable version of
     * the current map view
     */
    activate: function() {
        if (this.showPrintUI) {
            this.openPrintUI();
        } else {
            this.openPrintable();
        }
    },
    
    openPrintUI: function() {
        if (!this.dialog) {

            var toolbar = new Jx.Toolbar({position: 'bottom'});
            var o = {
                label: OpenLayers.i18n('printTitle'),
                id: 'printablePage',
                content : '<div class="PrintDialogForm">' +
                              '<fieldset class="PrintDialogOptions">' +
                                  '<legend>' + OpenLayers.i18n("printOptions") +'</legend>' +
                                    '<label class="block" for="dialogPrintShowtitle"><input name="dialogPrintShowtitle" id="dialogPrintShowtitle" type="checkbox" value="showtitle" checked>' +
                                         OpenLayers.i18n("printShowTitle") +'</label>' +
                                    '<label class="block" for="dialogPrintTitle">' + OpenLayers.i18n("printTitleText") + '</label>' +
                                    '<input name="dialogPrintTitle" id="dialogPrintTitle" type="text" class="inputText" />' +
                                    '<label class="block" for="dialogPrintShowlegend"><input name="dialogPrintShowlegend" id="dialogPrintShowlegend" type="checkbox" value="showlegend" checked>' +
                                         OpenLayers.i18n("printShowLegend") + '</label>' +
                                    '<label class="block" for="dialogPrintShowNorthArrow"><input name="dialogPrintShowNorthArrow" id="dialogPrintShowNorthArrow" type="checkbox" value="shownortharrow" checked>' +
                                         OpenLayers.i18n("printShowNorthArrow") + '</label>' +
                              '</fieldset>' +
                            '</div>',
                width: 350,
                height: 250,
                resize: true,
                toolbars: [toolbar]
            };
            var d = new Jx.Dialog(o);
			d.addEvent("contentLoaded", this.contentLoaded.bind(this));
            toolbar.add(
                new Jx.Button({
                    label: OpenLayers.i18n('printGenerate'),
                    onClick: OpenLayers.Function.bind(this.generate, this)
                }),
                new Jx.Button({
                    label: OpenLayers.i18n('printCancel'),
                    onClick: function() {
                        d.close();
                    }
                })
            );
            this.dialog = d;
            
        }
        this.dialog.show();
    },
    
    setParameter: function(param, value) {
        switch (param) {
            case 'Print_ShowTitle':
            this.showTitle = value;
            break;
            case 'Print_Title':
            this.pageTitle = value;
            break;
            case 'Print_ShowLegend':
            this.showLegend = value;
            break;
            case 'Print_ShowNorthArrow':
            this.showNorthArrow = value;
            break;
        }
    },
    
    contentLoaded: function(dialog) {
        dialog.content.getElementById('dialogPrintShowtitle').checked = this.showTitle;
        dialog.content.getElementById('dialogPrintTitle').value = this.pageTitle;
        dialog.content.getElementById('dialogPrintTitle').disabled = !this.showTitle;
        dialog.content.getElementById('dialogPrintShowlegend').checked = this.showLegend;
        dialog.content.getElementById('dialogPrintShowNorthArrow').checked = this.showNorthArrow;
        
         OpenLayers.Event.observe(dialog.content.getElementById('dialogPrintShowtitle'), 'click', OpenLayers.Function.bind(this.controlTitle, this));
    },
    
    controlTitle: function() {
        this.dialog.content.getElementById('dialogPrintTitle').disabled = !this.dialog.content.getElementById('dialogPrintShowtitle').checked;
        
    },
    
    generate: function() {
        this.showTitle = this.dialog.content.getElementById('dialogPrintShowtitle').checked;
        this.pageTitle = this.dialog.content.getElementById('dialogPrintTitle').value;
        this.showLegend = this.dialog.content.getElementById('dialogPrintShowlegend').checked;
        this.showNorthArrow = this.dialog.content.getElementById('dialogPrintShowNorthArrow').checked;
        this.openPrintable();
    },
    
    openPrintable: function() {
        var mainMap = this.getMap();
        var url = this.printablePageURL+'?';
        var extents = mainMap.getCurrentExtents();
        var centerX = (extents.left + extents.right)/ 2;
        var centerY = (extents.top + extents.bottom)/ 2;
        var dpi = mainMap._nDpi;
        var scale = mainMap.getScale();
        var widgetLayer = this.getMapLayer();
        url = url + 'MAPNAME=' + widgetLayer.getMapName();
        url = url + '&SESSION=' + widgetLayer.getSessionID();
        url = url + '&CENTERX='+centerX;
        url = url + '&CENTERY='+centerY;
        url = url + '&DPI='+dpi;
        url = url + '&SCALE='+scale;
        url = url + '&ISTITLE=' + (this.showTitle != '' ? '1' : '0');
        url = url + '&ISLEGEND=' + (this.showLegend ? '1' : '0');
        url = url + '&ISARROW=' + (this.showNorthArrow ? '1' : '0');
        if (this.pageTitle != '') {
            url = url + '&TITLE='+this.pageTitle;
        }
        
        window.open(url, 'printablepage', '');
        
    }
});
/**
 * Fusion.Widget.Query
 *
 * $Id: Query.js 1523 2008-09-11 19:30:43Z pagameba $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.Query
 *
 * The Query widget prompts the user for some inputs and then executes
 * a query on the map.
 *
 * If the Target property points to TaskPane widget, the task will be listed in
 * the menu list of the TaskPane and loaded there.
 * Otherwise if the target is an existing IFrame in the page it will be loaded
 * there, otherwise it will open a new window with that name.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.Query = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,
    sFeatures : 'menubar=no,location=no,resizable=no,status=no',

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "QueryWindow";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/Query/querymain.php';
    },

    activate: function() {
        var url = this.sBaseUrl;
        //add in other parameters to the url here

        var widgetLayer = this.getMapLayer();
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        var pageElement = $(this.sTarget);

        var params = [];
        params.push('LOCALE='+Fusion.locale);
        params.push('SESSION='+widgetLayer.getSessionID());
        params.push('MAPNAME='+widgetLayer.getMapName());
        if (taskPaneTarget || pageElement) {
          params.push('POPUP=false');
        } else {
          params.push('POPUP=true');
        }

        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
        } else {
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                window.open(url, this.sTarget, this.sWinFeatures);
            }
        }
    }
});
/**
 * Fusion.Widget.QuickPlot
 * Copyright (C) 2010 Autodesk, Inc. All rights reserved.
 */

Fusion.require("widgets/QuickPlot/MapCapturer.js");

/*****************************************************************************
 * Class: Fusion.Widget.QuickPlot
 * This widget provides a quick way to print a certain region of map in a good quality
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/
Fusion.Widget.QuickPlot = OpenLayers.Class(Fusion.Widget, 
{
    isExclusive: false,
    uiClass: Jx.Button,
    sFeatures : 'width=350,height=650,menubar=no,location=no,resizable=no,status=no',
    options : {},
    //The legal disclaimer text to display in the preview and final printout
    disclaimer: "",
    //The default DPI to use, if specified will hide the DPI field on the QuickPlot UI and override
    //whatever DPI value is being used for plotting
    defaultDpi: null,
    //A custom paper size list. If specified, will override the default list in the QuickPlot UI
    paperList: null,
    //A custom scale list. If specified, will override the default list in the QuickPlot UI
    scaleList: null,
    //Indicates whether to show the coordinate labels in the QuickPlot preview dialog
    showCoordinatesInPreview: true,
    //Indicates whether to show the sub title in the QuickPlot UI and preview dialog
    showSubTitle: true,
    //Indicates whether cookies will be used to persist QuickPlot UI options
    persistPlotOptions: false,
    //Indicates whether the legend checkbox in the Quick Plot UI is initially checked
    showLegend: true,
    //Indicates whether the north arrow checkbox in the Quick Plot UI is initially checked
    showNorthArrow: false,
    //Indicates whether the coordinates checkbox in the Quick Plot UI is initially checked
    showCoordinates: false,
    //Indicates whether the scale bar checkbox in the Quick Plot UI is initially checked
    showScaleBar: false,
    //Indicates whether the disclaimer checkbox is visible. This can be set to false to remove the ability for the user to toggle this element.
    allowDisclaimerToggle: true,
    //Indicates whether the disclaimer checkbox in the Quick Plot UI is initially checked
    showLegalDisclaimer: true,
    
    initializeWidget: function(widgetTag) 
    {
        this.mapCapturer = new OpenLayers.Control.MapCapturer(this.getMap());
        this.getMap().oMapOL.addControl(this.mapCapturer);
        
        var json = widgetTag.extension;
        
        this.sTarget  = json.Target ? json.Target[0] : "PrintPanelWindow";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/QuickPlot/QuickPlotPanel.php';
        
        if (json.DefaultDpi) {
            this.defaultDpi = parseInt(json.DefaultDpi[0]);
        }
        
        /******************************************************
         *
         *<DefaultMargin>25.4,12.7,12.7,12.7</DefaultMargin>
         *top,buttom,left,right
        ******************************************************/
        if(json.DefaultMargin){      
            try{
                this.margin = {};
                var marginArray = json.DefaultMargin[0].split(",");
                this.margin.top = parseFloat(marginArray[0]);
                this.margin.buttom = parseFloat(marginArray[1]);
                this.margin.left = parseFloat(marginArray[2]);
                this.margin.right = parseFloat(marginArray[3]);
            }catch(ex){
                this.margin = null;
            }
        }
        
        /*
        <PaperListEntry>
          <Name>Letter</Name>
          <Value>279.4,215.9</Value>
        </PaperListEntry>
        <PaperListEntry>
          <Name>A4</Name>
          <Value>297.0,210.0</Value>
        </PaperListEntry>
        */
        
        this.paperList = [];
        if (json.PaperListEntry) {
            for (var i=0; i<json.PaperListEntry.length; i++)
            {
                var p = json.PaperListEntry[i];
                var name = p.Name[0];
                var size = p.Value[0];
                this.paperList.push({ name: name, size: size });
            }
        }
        
        /*
        <ScaleListEntry>
          <Name>1:2500</Name>
          <Value>2500</Value>
        </ScaleListEntry>
        <ScaleListEntry>
          <Name>1:5000</Name>
          <Value>5000</Value>
        </ScaleListEntry>
        */
        
        this.scaleList = [];
        if (json.ScaleListEntry) {
            for (var i=0; i<json.ScaleListEntry.length; i++)
            {
                var p = json.ScaleListEntry[i];
                var name = p.Name[0];
                var scale = p.Value[0];
                this.scaleList.push({ name: name, scale: scale });
            }
        }
        
        if (json.ShowSubTitle) {
            this.showSubTitle = (json.ShowSubTitle[0] == 'true');
        }
        
        if (json.ShowCoordinateLabels) {
            this.showCoordinatesInPreview = (json.ShowCoordinateLabels[0] == 'true');
        }
        
        if (json.AllowDisclaimerToggle) {
            this.allowDisclaimerToggle = (json.AllowDisclaimerToggle[0] == 'true');
        }
        
        if (json.ShowLegend) {
            this.showLegend = (json.ShowLegend[0] == 'true');
        }
        
        if (json.ShowNorthArrow) {
            this.showNorthArrow = (json.ShowNorthArrow[0] == 'true');
        }
        
        if (json.ShowCoordinates) {
            this.showCoordinates = (json.ShowCoordinates[0] == 'true');
        }
        
        if (json.ShowScaleBar) {
            this.showScaleBar = (json.ShowScaleBar[0] == 'true');
        }
        
        if (json.ShowLegalDisclaimer) {
            this.showLegalDisclaimer = (json.ShowLegalDisclaimer[0] == 'true');
        }
        
        if (json.RememberPlotOptions) {
            this.persistPlotOptions = (json.RememberPlotOptions[0] == 'true');
        }
        
        if (json.Disclaimer) {
            this.disclaimer = json.Disclaimer[0];
        }
        
        this.additionalParameters = [];
        if (json.AdditionalParameter) 
        {
            for (var i=0; i<json.AdditionalParameter.length; i++) 
            {
                var p = json.AdditionalParameter[i];
                var k = p.Key[0];
                var v = p.Value[0];
                this.additionalParameters.push(k+'='+encodeURIComponent(v));
            }
        }
    },

    activate: function() 
    {
        var url = this.sBaseUrl;
        var widgetLayer    = this.getMapLayer();
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        var pageElement    = $(this.sTarget);

        var params = [];
        params.push('locale='+Fusion.locale);
        params.push('session='+widgetLayer.getSessionID());
        params.push('mapname='+widgetLayer.getMapName());
        
        if (taskPaneTarget || pageElement) 
        {
          params.push('popup=false');
        } 
        else 
        {
          params.push('popup=true');
        }

        params = params.concat(this.additionalParameters);

        if (url.indexOf('?') < 0) 
        {
            url += '?';
        } 
        else if (url.slice(-1) != '&') 
        {
            url += '&';
        }
        
        url += params.join('&');
        
        if (taskPaneTarget) 
        {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
        } 
        else 
        {
            if (pageElement) 
            {
                pageElement.src = url;
            } 
            else 
            {
                var win = window.open(url, this.sTarget, this.sFeatures);
                win.focus();
            }
        }
        
        // Expand taskpane automatically if it is the target window
        if (typeof (panelman) != "undefined")
        {
            var panel = null;
            for (var i = 0; i < panelman.panels.length; ++i)
            {
                panel = panelman.panels[i];
                if (panel.options.contentId == this.sTarget)
                {
                    panelman.maximizePanel(panel);
                    return;
                }
            }
        }
    }
});/**
 * Copyright (C) 2010 Autodesk, Inc. All rights reserved.
 */
 
/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Handler/Drag.js
 * @requires OpenLayers/Handler/Feature.js
 */

/**
 * Class: OpenLayers.Control.MapCapturer
 * The MapCapturer control shows a box on the map to illustrate the area that will be captured.
 * Normally the capture information will be used to print the map. If the MapCapturer is configured not
 * show the capture box, then the current viewport will be used as the capture area.
 * 
 * The user can move or rotate the capture box to change the area he'd like to capture.
 * And the user can hold SHIFT key to snapp to a certain angle when he is rotating the capture box
 *
 * Inherits From:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.MapCapturer = OpenLayers.Class(OpenLayers.Control, {
    /**
     * Property: layer
     * {<OpenLayers.Layer.Vector>}
     */
    layer: null,
    
    /**
     * Property: feature. The active feature which is repsonding to mouse action
     * {<OpenLayers.Feature.Vector>}
     */
    feature: null,

    /**
     * Property: captureBox. It's the feature represnting the capture area
     * {<OpenLayers.Feature.Vector>}
     */
    captureBox: null,
    
    /**
     * Property: rotateHandle. It's the feature representing a rotate handle
     * (<OpenLayers.Feature.Vector)
     */
    rotateHandle: null,
    
    /**
     * Property: rotateHandleStart. It's the feature representing the center point of capture box.
     * And it's also the start point of the rotate handle.
     * (<OpenLayers.Feature.Vector>)
     */
    rotateHandleStart: null,
    
    /**
     * Property: rotateHandleEnd. It's the feature responding to the rotate command.
     * (<OpenLayers.Feature.Vector>)
     */
    rotateHandleEnd: null,

    /**
     * Property: snappingLine. It's the feature indicating a certain angle to snap to. It shows up in the rotate process
     * (<OpenLayers.Feature.Vector>)
     */
    snappingLine: null,
    
    /**
     * Property: rotation. The rotation of the capture box
     * {float}
     */	
    rotation: 0.0,
    
    /**
     * Property: rotateHandleLength. Defines the length of the rotate handle 
     * in pixel. default is 30 pixels
     * {int}
     */	
    rotateHandleLength: 30,
    
    /**
     * Property: captureBoxStyle. Style for the capture box
     * {<OpenLayers.Style>}
     */	
    captureBoxStyle: null,
    
    /**
     * Property: rotateHandleStyle. Style for the rotate handle
     * (<OpenLayers.Style>)
     */
    rotateHandleStyle: null,
    
    /**
     * Property: rotateHandlePointStyle. Style for the snap handle's start and end points
     * (<OpenLayers.Style>)
     */
    rotateHandlePointStyle: null,
    
    /**
     * Property: snappingLineStyle. Style for the snapping line
     * (<OpenLayers.Style>)
     */
    snappingLineStyle: null,

    /**
     * Indicates if the capture box is enabled. An enabled capture box will show up on the map
     * {bool}
     */	
    enabled: false,
    
    /**
     * Property: warningMessage. The warning message when the capture box cannot display completely in current zoom level
     * (string)
     */
    warningMessage: OpenLayers.i18n("quickPlotResolutionWarning"),
    
    /**
     * Property: rotateSnappingStep. Defines the rotate snapping angle step. In degrees
     * (int)
     */
    rotateSnappingStep: 45,
    
    /**
     * Property: rotateSnappingTolerance. Defines the rotate snapping tolerance. In degress
     */
    rotateSnappingTolerance: 2,
    
    /**
     * Property: lastPixel. The last position the mouse action was responded
     * {<OpenLayers.Pixel>}
     */
    lastPixel: null,

    /**
     * Constructor: OpenLayers.Control.DragFeature
     * Create a new control to drag features.
     *
     * Parameters:
     * map - The widget map which encapsulates an OpenLayers.Map
     *
     * options - {Object} Optional object whose properties will be set on the control.
     */
    initialize: function(map, options) 
    {
        OpenLayers.Control.prototype.initialize.apply(this, [options]);
        
        // Initialize the styles
        this.captureBoxStyle = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style["default"]);
        this.captureBoxStyle.fillOpacity    = 0.8;
        this.captureBoxStyle.fillColor      = "#476387";
        this.captureBoxStyle.strokeColor    = "#39506F";
        
        this.rotateHandleStyle              = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style["default"]);
        this.rotateHandleStyle.strokeColor  = "#39506F";
        this.rotateHandlePointStyle             = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style["default"]);
        this.rotateHandlePointStyle.strokeColor = "#39506F";
        this.rotateHandlePointStyle.fillOpacity = 1.0;
        this.rotateHandlePointStyle.fillColor   = "#476387";
        this.rotateHandlePointStyle.pointRadius = 4;
        
        this.snappingLineStyle = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style["default"]);
        this.snappingLineStyle.strokeOpacity   = 0.8;
        this.snappingLineStyle.strokeColor     = "black";
        this.snappingLineStyle.strokeDashstyle = "dot";

        
        // The cursor styles
        this.cursorMove   = "move";
        this.cursorRotate = "url(../../../Widgets/QuickPlot/rotate.cur), default";
        
        // The widget map
        this.wMap = map;
        
        // Create a new layer to show the capture box
        this.layer  = new OpenLayers.Layer.Vector("Map Capturer Layer");
        map.oMapOL.addLayer(this.layer);
        
        // Create the features
        this.captureBox        = new OpenLayers.Feature.Vector(null, null, this.captureBoxStyle);
        this.rotateHandle      = new OpenLayers.Feature.Vector(null, null, this.rotateHandleStyle);
        this.rotateHandleStart = new OpenLayers.Feature.Vector(null, null, this.rotateHandlePointStyle);
        this.rotateHandleEnd   = new OpenLayers.Feature.Vector(null, null, this.rotateHandlePointStyle);
        this.snappingLine      = new OpenLayers.Feature.Vector(null, null, this.snappingLineStyle);
        
        // Add the features into the layer
        this.layer.addFeatures([this.captureBox, this.rotateHandle, this.rotateHandleStart, this.rotateHandleEnd, this.snappingLine]);

        this.handlers = 
        {
            drag: new OpenLayers.Handler.Drag(
                this, OpenLayers.Util.extend({
                    down: this.downFeature,
                    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}
            )
        };

        // List to the mouse move event
        this.handlers.drag.move = this.mouseMove.bind(this);
        
        // Listen to MAP_EXTENTS_CHANGED event to refresh the outer box because the 'delta' is fixed regardless current scale
        var oMap = this.wMap.oMapOL;
        oMap.events.register("zoomend", this, this.drawFeatures.bind(this));
    },
    
    /**
     * 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.feature.deactivate();
        this.handlers.drag.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 enables the drag control if the event target is the outer polygon,
     *		and the rotate control will be activated if the event target is the inner polygon
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The selected feature.
     */
    overFeature: function(feature) 
    {
        // The feature that the cursor stops on
        this.stopFeature = feature;
        
        if(!this.handlers.drag.dragging) 
        {
            this.feature = feature;
            this.handlers.drag.activate();
            this.over = true;
            
            this.setCursor(); 
        } 
        else 
        {
            if(this.captureBox.id == feature.id || this.rotateHandleEnd.id == feature.id) 
            {
                this.over = true;
            } 
            else 
            {
                this.over = false;
            }
        }
    },
    
    /**
     * Method: isDragging
     * Check if it's currently in dragging to move mode
     */
    isDragging: function() 
    {
        if (this.feature && this.captureBox)
        {
            return this.feature.id == this.captureBox.id || this.feature.id == this.rotateHandleStart.id;
        }
        
        return false;
    },
    
    /**
     * Method: isRotating
     * Check if it's currently in rotatiing mode
     */
    isRotating: function() 
    {
        return this.feature && this.rotateHandleEnd && this.feature.id == this.rotateHandleEnd.id;
    },

    /**
     * Method: downFeature
     * Called when the drag handler detects a mouse-down.
     *
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
     */
    downFeature: function(pixel) 
    {
        this.setCursor();
        this.lastPixel = pixel;
    },
    
    /**
     * Method: setCursor
     * Set the cursor according to current action: moving or rotating
     */
    setCursor: function() 
    {
        if (this.isDragging())
        {
            this.wMap.setCursor(this.cursorMove);
        }
        else if (this.isRotating())
        {
            this.wMap.setCursor(this.cursorRotate);
        }
    },
    /**
     * Method: moveFeature
     * Called when the drag handler detects a mouse-move.  Also calls the
     *     optional onDrag method.
     */
    mouseMove: function(evt) 
    {
        if (this.isDragging()) 
        {
            this.moveFeature(evt);
        }
        else if (this.isRotating()) 
        {
            this.rotateFeature(evt);
        }
    },
    
    /**
     * Method: moveFeature
     *  Move the feature according to the mouse-move
     */
    moveFeature: function(evt) 
    {
        pixel     = evt.xy;
        var res   = this.wMap.getResolution();
        var delta = {x:res * (pixel.x - this.lastPixel.x), y:res * (this.lastPixel.y - pixel.y)};
        
        var features = this.layer.features;
        for (var i = 0; i < features.length; ++i)
        {
            // Don't touch the snapping line
            if (features[i].geometry != null && features[i].id != this.snappingLine.id)
            {
                features[i].geometry.move(delta.x, delta.y);
                this.layer.drawFeature(features[i]);
            }
        }
        
        this.lastPixel = pixel;
    },
    
    /**
     * Method: moveFeature
     * Called when the drag handler detects a mouse-move.  Also calls the
     *     optional onDrag method.
     */
    rotateFeature: function(evt)
    {
        pixel = evt.xy;
        var centroid   = this.captureBox.geometry.getCentroid();
        var origin     = this.map.getViewPortPxFromLonLat(new OpenLayers.LonLat(centroid.x, centroid.y));
        var angle      = this.calculateAngle(pixel, origin);
        var rotation   = this.rotation + angle;

        if (evt.shiftKey)
        {
            var a = rotation % this.rotateSnappingStep;
            var b = Math.round(rotation / this.rotateSnappingStep);
            var targetRotation = rotation;
            
            if (Math.abs(a) <= this.rotateSnappingTolerance || Math.abs(a)>= this.rotateSnappingStep - this.rotateSnappingTolerance)
            {
                targetRotation = b * this.rotateSnappingStep;
                
                // Draw the snapping guide
                if (this.snappingLine.geometry == null)
                {
                    this.drawSnappingHint(targetRotation);
                }
            }
            else
            {
                this.clearSnappingHint();
            }
            
            angle = targetRotation - this.rotation;
        }
        else
        {
            this.clearSnappingHint();
        }
        
        this.rotation += angle;
        
        var features = this.layer.features;
        for (var i = 0; i < features.length; ++i)
        {
            // Don't touch the snapping line here because the snapping line is controlled only by drawSnappingHint
            if (features[i].geometry != null && features[i].id != this.snappingLine.id)
            {
                features[i].geometry.rotate(angle, centroid);
                this.layer.drawFeature(features[i]);
            }
        }

        this.lastPixel = angle == 0 ? this.lastPixel : pixel;
    },
    
    /**
     * Method: drawSnappingHint
     * Draw the snapping line
     */
    drawSnappingHint: function(angle)
    {
        var viewSize = this.wMap.getCurrentExtents().getSize();
        var length   = Math.sqrt(Math.pow(viewSize.w, 2) + Math.pow(viewSize.h, 2));
        var origin   = this.captureBox.geometry.getCentroid();
        
        var points   = [];
        points.push(new OpenLayers.Geometry.Point(origin.x - length, origin.y));
        points.push(new OpenLayers.Geometry.Point(origin.x + length, origin.y));
        var hint     = new OpenLayers.Geometry.LineString(points);
        
        hint.rotate(angle + 90, origin);
        
        this.snappingLine.geometry = hint;
        this.layer.drawFeature(this.snappingLine);
    },
    
    /**
     * Method: clearSnappingHint
     * Clear the snapping line
     */
    clearSnappingHint: function()
    {
        if (this.snappingLine != null && this.snappingLine.geometry != null)
        {
            this.layer.eraseFeatures([this.snappingLine]);
            this.snappingLine.geometry = null;
        }
    },
    
    /**
     * Method: calculateAngle
     * Calculates the rotate angle, in degree and counterclockwise
     */
    calculateAngle: function(pixel, origin)
    {
        var angle1 = Math.atan2(pixel.y - origin.y, pixel.x - origin.x);
        var angle2 = Math.atan2(this.lastPixel.y - origin.y, this.lastPixel.x - origin.x);
        return (angle2 - angle1) * 180 / Math.PI;
    },

    /**
     * Method: upFeature
     * Called when the drag handler detects a mouse-up.
     * 
     * Parameters:
     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
     */
    upFeature: function(pixel) 
    {
        if(!this.over) 
        {
            this.handlers.drag.deactivate();
        }
        else
        {
            // Set the last-stop feature to be the active one
            this.feature = this.stopFeature;
            // Set the correct cursor
            this.setCursor();
        }
        
        this.clearSnappingHint();
    },

    /**
     * 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();
            this.wMap.setCursor("auto");            
            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);
    },

    /**
     * Method: setRegion
     * Set the size of the capture box. Then the box will be redrawn on the map
     *
     * Parameters: 
     * paperSize - {<OpenLayers.Size>} The paper size, in mm
     * scaleDenominator - The denominator of target scale
     */
    setSize: function(paperSize, scaleDenominator) 
    {
        this.paperSize        = paperSize;
        this.scaleDenominator = scaleDenominator;
        this.drawFeatures();
        // Active the handler
        this.activate();
    },
    
    /**
     * Method: drawFeatures
     * Draw the capture box, rotate handle etc. on the map
     */
    drawFeatures: function()
    {
        // The paper size is not set yet, so don't draw the features
        if (!this.paperSize)
        {
            return;
        }
        
        // Clear the previous drawn features
        this.clearFeatures();
                
        this.createCaptureBox();
        this.createRotateHandle();
        
        // Draw the box only when control is enabled and the resolution is valid
        if (this.enabled) 
        {
            if (this.validateResolution())
            {
                this.layer.features.each(function(item){
                    item.layer.drawFeature(item);
                });
                if(this.wMap.message){
                   this.wMap.message.hide();
                }
            }
            else
            {
                this.wMap.message.warn(this.warningMessage);
            }
        }
    },
    
    /**
     * Method: clearFeatures:
     * Clear the capture box, rotate handle etc. from the map
     */
    clearFeatures: function()
    {
        // Clear the previous drawn features
        var features = this.layer.features;
        var toErase  = [];
        var feature  = null;
        
        for (var i = 0; i < features.length; ++i)
        {
            feature = features[i];
            if (feature.geometry != null)
            {
                toErase.push(feature);
            }
        }
        
        this.layer.eraseFeatures(toErase);
    },
    
    /**
     * Method: createCaptureBox
     * Create the capture box feature
     */
    createCaptureBox: function()
    {
        var origin   = null;
        var rotation = 0;
        
        if (this.captureBox.geometry != null)
        {
            origin   = this.captureBox.geometry.getCentroid();
            rotation = this.rotation;
        }
        else
        {
            origin   = this.wMap.getCurrentCenter();
        }
        
        var factor = this.scaleDenominator / (this.wMap.getMetersPerUnit() * 1000 * 2);
        
        //TODO: If legend is enabled, should draw another box on the LHS of the box to indicate the region that would be cropped
        //out by the legend
        
        var pointList = [];
        pointList.push(new OpenLayers.Geometry.Point(origin.x - this.paperSize.w * factor, origin.y - this.paperSize.h * factor));
        pointList.push(new OpenLayers.Geometry.Point(origin.x + this.paperSize.w * factor, origin.y - this.paperSize.h * factor));
        pointList.push(new OpenLayers.Geometry.Point(origin.x + this.paperSize.w * factor, origin.y + this.paperSize.h * factor));
        pointList.push(new OpenLayers.Geometry.Point(origin.x - this.paperSize.w * factor, origin.y + this.paperSize.h * factor));
        pointList.push(pointList[0]);
        
        var box = new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(pointList));
        
        if (rotation != 0)
        {
            box.rotate(rotation, box.getCentroid());
        }
        
        this.captureBox.geometry = box;
    },

    /**
     * Method: createRotateHandle
     * Create the rotate handle feature
     */
    createRotateHandle: function()
    {
        var box        = this.captureBox.geometry.clone();
        var startPoint = box.getCentroid();
        
        if (this.rotation != 0)
        {
            box.rotate(-this.rotation, startPoint);
        }
        
        var length = this.rotateHandleLength * this.wMap.getResolution();
        
        var endPoint   = new OpenLayers.Geometry.Point(startPoint.x, startPoint.y + box.getBounds().getHeight() / 2 + length);
        var line       = new OpenLayers.Geometry.LineString([startPoint.clone(), endPoint.clone()]);
        
        if (this.rotation != 0)
        {
            endPoint.rotate(this.rotation, startPoint);
            line.rotate(this.rotation, startPoint);
        }
        
        this.rotateHandle.geometry      = line;
        this.rotateHandleStart.geometry = startPoint;
        this.rotateHandleEnd.geometry   = endPoint;
    },
    
    /**
     * Method: validateResolution
     * Check if the capture box could display completely on current screen view port.
     * If it cannot, then an warning message will show up to warn the user. Then the user could zoom out to get
     * the box back
     * 
     * Returns:
     * (Boolean) True if the box can display completely, otherwise false.
     *
     */
    validateResolution: function() 
    {
        if (this.captureBox)
        {
            var screenSize = this.map.getExtent().getSize();
            var boxSize    = this.captureBox.geometry.getBounds().getSize();
            
            if (boxSize.w < screenSize.w || boxSize.h < screenSize.h)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    },
    
    /**
     * Method: getCaptureBox
     * Get the capture area.
     */
    getCaptureBox: function()
    {
        var geometry;
        // The capture box is not enabled, so just use the current viewport as capture box
        if (!this.enabled)
        {
            geometry = this.wMap.getCurrentExtents().toGeometry(); 
        }
        else
        {
            geometry = this.captureBox.geometry;
        }
        
        var info     = "";
        var vertices = geometry.getVertices();
        var vertex   = null;
        
        for (var i = 0; i < vertices.length; ++i)
        {
            vertex = vertices[i];
            info += vertex.x + "," + vertex.y + ",";
        }
        
        // Remove the last ","
        info = info.substr(0, info.length - 1);
        
        return {geometry:geometry, params:info};
    },
    
    /**
     * Method: getNormalizedCapture
     * Get the normalized capture box
     */
    getNormalizedCapture: function()
    {
        var info   = "";
        var geometry = null;
        if (!this.enabled)
        {
            geometry = this.wMap.getCurrentExtents().toGeometry();
            // Calculate the scale denominator
            
        }
        else
        {
            geometry = this.captureBox.geometry.clone();
            geometry.rotate(-this.rotation, geometry.getCentroid());
        }
        
        var vertices = geometry.getVertices();
        var vertex   = null;
        
        for (var i = 0; i < vertices.length; ++i)
        {
            vertex = vertices[i];
            info += vertex.x + "," + vertex.y + ",";
        }
        
        // Remove the last ","
        info = info.substr(0, info.length - 1);
        
        return {geometry:geometry, params:info};
    },
    
    /**
     * Method: getCaptureRotation
     * Get the capture rotation
     */
    getCaptureRotation: function()
    {
        if (this.enabled)
        {
            return this.rotation;
        }
        else
        {
            return 0.0;
        }
    },
    
    /**
     * Method: enable
     * Enable the capture box to display it on the map
     */
    enable: function()
    {
        // Reset everything first
        this.disable();
        this.enabled = true;
    },
    
    /**
     * Method: disable
     * Disable the capture box. Then the capture box will not show up on the Map,
     * 		and the current view port is treated as the capture area
     */
    disable: function()
    {
        this.enabled  = false;
        if(this.wMap.message){
           this.wMap.message.hide();
        }
        this.clearFeatures();
        this.rotation = 0;
        
        var features = this.layer.features;
        for (var i = 0; i < features.length; ++i)
        {
            features[i].geometry = null;
        }
    },
    
    /**
     * Property: CLASS_NAME. The class name
     */
    CLASS_NAME: "OpenLayers.Control.MapCapturer"
});

/**
 * Copyright (C) 2010 Autodesk, Inc. All rights reserved.
 */

function panelUnloaded()
{
    var mapCapturer = getParent().Fusion.getWidgetsByType("QuickPlot")[0].mapCapturer;
    mapCapturer.disable();
}

function panelLoaded()
{
    var widget = getParent().Fusion.getWidgetsByType("QuickPlot")[0];
    
    //set the legal notice from the quick plot widget info
    var disclaimer = widget.disclaimer;
    document.getElementById("legalNotice").value = disclaimer;
    
    //If there's no disclaimer, then this checkbox is useless, so hide it if that's the case
    if (!disclaimer || disclaimer == "" || !widget.allowDisclaimerToggle)
        document.getElementById("DisclaimerCtrl").style.display = "none";
    
    //Apply initial UI state    
    document.getElementById("ShowLegendCheckBox").checked = widget.showLegend;
    document.getElementById("ShowNorthArrowCheckBox").checked = widget.showNorthArrow;
    document.getElementById("ShowCoordinatesCheckBox").checked = widget.showCoordinates;
    document.getElementById("ShowScaleBarCheckBox").checked = widget.showScaleBar;
    document.getElementById("ShowDisclaimerCheckBox").checked = widget.showLegalDisclaimer;
    
    //set the paper list from the quick plot widget info
    if (widget.paperList.length > 0)
    {
        var paperList = document.getElementById("PaperList");
        paperList.options.length = 0;
        
        var i;
        for (i = 0; i < widget.paperList.length; i++) {
            var elOpt = document.createElement("option");
            var name = getParent().Fusion.trim(widget.paperList[i].name);
            
            //users may set the page size as 279.4,215.9 which make the height before width
            //we should always set width before height
            //and also make sure the paper size name is right, otherwise will result in error
            var sizeArray = widget.paperList[i].size.split(",");
            var width = parseFloat(sizeArray[0]);
            var height = parseFloat(sizeArray[1]);
            
            var paperSizeFormatString = (width < height)? (width + "," + height + "," + name) : (height + "," + width + "," + name);
            
            elOpt.text = name;
            elOpt.value = paperSizeFormatString;
            try {
                paperList.add(elOpt, null);
            }catch (ex) {
                paperList.add(elOpt); //IE
            }
        }
    }
    
    //set the scale list from the quick plot widget info
    if (widget.scaleList.length > 0)
    {
        var scaleList = document.getElementById("ScalingList");
        scaleList.options.length = 0;
        
        for (i = 0; i < widget.scaleList.length; i++) {
            var scaleOpt = document.createElement("option");
            scaleOpt.text = widget.scaleList[i].name;
            scaleOpt.value = widget.scaleList[i].scale;
            try {
                scaleList.add(scaleOpt, null);
            }catch (ex) {
                scaleList.add(scaleOpt); //IE
            }
        }
    }
    
    //set the margin from the quick plot widget info
    if(!!widget.margin)
    {
        var margin = widget.margin;
        document.getElementById("margin").value = margin.top + "," + margin.buttom + "," +margin.left + "," + margin.right ; 
    }

    restoreUI();
}

function restoreUI()
{
    setAdvancedOptionsUI(false);
    
    var widget = getParent().Fusion.getWidgetsByType("QuickPlot")[0];
    if (widget.persistPlotOptions)
    {
        // Read the last used options
        var lastPaperSize   = getParent().Cookie.read("QuickPlotLastUsedPaperSize");
        var lastScale       = getParent().Cookie.read("QuickPlotLastUsedScaling");
        var lastDPI         = getParent().Cookie.read("QuickPlotLastUsedDPI");
        var lastOrientation = getParent().Cookie.read("QuickPlotLastUsedOrientation");

        if (lastPaperSize != null){
            document.getElementById("PaperList").value = lastPaperSize;
        }
        
        if (lastScale != null){
            document.getElementById("ScalingList").value = lastScale;
        }
        
        if (lastDPI != null){
            document.getElementById("DPIList").value = lastDPI;
        }
        
        if (lastOrientation != null){
            document.getElementById("OrientationList").value = lastOrientation;
        }
    }
    
    if (widget.defaultDpi){
        document.getElementById("DPICtrl").style.display = "none";
        document.getElementById("DPILabel").style.display = "none";
    }else{
        document.getElementById("DPICtrl").style.display = "block";
        document.getElementById("DPILabel").style.display = "block";
    }
    
    if (widget.showSubTitle){
        document.getElementById("SubTitleCtrl").style.display = "block";
        document.getElementById("SubTitleLabel").style.display = "block";
    }else{
        document.getElementById("SubTitleCtrl").style.display = "none";
        document.getElementById("SubTitleLabel").style.display = "none";
    }
}

function setAdvancedOptionsUI(enabled)
{
    document.getElementById("ScalingList").disabled     = !enabled;
    document.getElementById("DPIList").disabled         = !enabled;
    
    var mapCapturer = getParent().Fusion.getWidgetsByType("QuickPlot")[0].mapCapturer;
    
    if (enabled){
        mapCapturer.enable();
        drawCaptureBox();
    }else{
        mapCapturer.disable();
    }
}

function generatePlot()
{
    var widget = getParent().Fusion.getWidgetsByType("QuickPlot")[0];
    if(!!widget){
        var map = widget.getMapLayer();
        var mapCapturer = widget.mapCapturer; 
    }
  
    //params
    if(map){
        var sessionID = map.getSessionID();
        var mapName = map.getMapName();
    }
    
    if(mapCapturer){
        var capture = mapCapturer.getCaptureBox();
        if(capture){
            var box = capture.params;
        }
        
        var rotation = mapCapturer.rotation;
        var normalizedCapture = mapCapturer.getNormalizedCapture();

        if(!!normalizedCapture){
            var normalized_box = normalizedCapture.params;//normalized_box            
        }
    }
    //get params and assign the value of hidden input
    var printSize = getPrintSize();
    getPrintDpi();
    getScale();
    getPaperType();
    getOrientation();

    document.getElementById("sessionId").value = sessionID;
    document.getElementById("mapName").value = mapName;
    document.getElementById("normalizedBox").value = normalized_box;
    document.getElementById("printSize").value = printSize.w + "," + printSize.h;
    document.getElementById("rotation").value = rotation;
    document.getElementById("box").value = box;
    					     
    if (widget.persistPlotOptions) {
        // Save the advanced options to a cookie
        var cookieDuration = 365;
        getParent().Cookie.write("QuickPlotLastUsedPaperSize", document.getElementById("PaperList").value, {duration:cookieDuration});
        getParent().Cookie.write("QuickPlotLastUsedScaling", document.getElementById("ScalingList").value, {duration:cookieDuration});
        getParent().Cookie.write("QuickPlotLastUsedDPI", document.getElementById("DPIList").value, {duration:cookieDuration});
        getParent().Cookie.write("QuickPlotLastUsedOrientation", document.getElementById("OrientationList").value, {duration:cookieDuration});
    }
    
    //update param
    var form = document.getElementById("Form1");
    form.target = "_blank";//windowName;
    
    //use a random print id to avoid the browser cache problem
    var vNum = Math.random() * 1000;
    var randomId = "printId=" + vNum;
    
    var actiontarget;
    if ( form.action.indexOf("?") != -1)//form.action include "?"
    {
        actiontarget = form.action.slice(0,form.action.indexOf("?"));
    }
    else
    {
        actiontarget = form.action;
    }
    form.action = actiontarget + "?" + randomId;
    form.submit();
}

function advancedOptionsOn()
{
    var o = document.getElementById("AdvancedOptionsCheckBox");
    if (o && o.checked){
        return true;
    }
    return false;
}

function getMargin()
{
    var widget = getParent().Fusion.getWidgetsByType("QuickPlot")[0];
    var margin;
    
    if(!!widget.margin){
         margin = widget.margin;
    }else{
        //the default margin
        margin = {top: 25.4, buttom: 12.7, left: 12.7, right: 12.7};
    }
    return margin;
}

function getOrientation()
{
    document.getElementById("orientation").value = document.getElementById("OrientationList").value;   
    return document.getElementById("OrientationList").value;   
}

function getPaperType()
{
    var value = document.getElementById("PaperList").value.split(",");
    return value[2];
}

function getPrintSize()
{
    var value = document.getElementById("PaperList").value.split(",");
    var size;
    var orientation = getOrientation();
    if (orientation  === "P" ){
        size = {w: parseFloat(value[0]), h: parseFloat(value[1])};
    }else if (orientation === "L"){
        size = {w: parseFloat(value[1]), h: parseFloat(value[0])};
    }
    
    if (!advancedOptionsOn())
    {
        // Calculate the paper size to make sure it has a same ratio with the viweport
        var map        = getParent().Fusion.getWidgetById("Map");
        var paperRatio = size.w / size.h;
        var viewSize   = map.getSize();
        var vs;
        if (orientation === "P") {
            vs = {
                w: viewSize.h,
                h: viewSize.w
            };
        }
        if (orientation === "L") {
            vs = {
                w: viewSize.w,
                h: viewSize.h
            };
        }
        var viewRatio  = vs.w / vs.h;

        if (paperRatio > viewRatio){
            size.w     = size.h * viewRatio;
        }else{
            size.h     = size.w / viewRatio;
        } 
    }
    
    var margins = getMargin();
    size.h = size.h - margins.top - margins.buttom;
    size.w = size.w - margins.left - margins.right;
    
    document.getElementById("paperSize").value = document.getElementById("PaperList").value;
    return size;
}

function getScale()
{
    var scale = 0;
    if (advancedOptionsOn()){
        scale = document.getElementById("ScalingList").value;
    }else{
        var map = getParent().Fusion.getWidgetById("Map");
        scale = map.getScale();
    }

    scale = parseInt(scale);
    // Set the value to a hidden field so that it could be sent by POST method
    // We cannot rely on the ScalingList.value because we have to handle also the viewport print 
    document.getElementById("scaleDenominator").value = scale;
    
    return scale;
}

function getPrintDpi()
{
    var widget = getParent().Fusion.getWidgetsByType("QuickPlot")[0];
    if (widget.defaultDpi){
        document.getElementById("dpi").value = widget.defaultDpi;
        return widget.defaultDpi;
    }else{
        document.getElementById("dpi").value = document.getElementById("DPIList").value;
        return document.getElementById("DPIList").value;
    }
}

function drawCaptureBox()
{
    var mapCapturer = getParent().Fusion.getWidgetsByType("QuickPlot")[0].mapCapturer;
    mapCapturer.setSize( getPrintSize() , getScale() );
}
/**
 * Fusion.Widget.Redline
 *
 * $Id: Redline.js 1736 2009-01-14 15:42:24Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

// This event could be emitted by the Redline widget
Fusion.Event.REDLINE_FEATURE_ADDED = Fusion.Event.lastEventId++;

/* ********************************************************************
* Class: Fusion.Widget.Redline
*
* Allows the user to create a temporary OpenLayers Vector layer and
* draw POINT, LINE and POLYGON features on that layer.
*
* Inherits from:
*  - <Fusion.Widget>
**********************************************************************/
Fusion.Widget.Redline = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,

    // Fusion map widget
    mapWidget: null,

    // a reference to a redline taskPane
    taskPane: null,

    // Indicates whether to autogenerate redline layer names or to prompt the user for one.
    autogenerateLayerNames: true,

    // Indicates whether to use the MapMessage component to display digitization prompts
    mapMessagePrompt: true,
    
    // Indicates the default redline data store to create
    defaultDataStoreFormat: null,
    
    // Indicates the default geometry types the user can record
    defaultRedlineGeometryTypes: 0,
    
    // If we have a redline data store and geom format specified, auto-create the data store and go to the
    // edit redline page 
    bCreateOnStartup: false,
    
    // Determines whether redline features will be rendered with basic or advanced stylization. Advanced Stylization
    // allows for always visible labels for line/area redline features.
    bUseAdvancedStylization: true,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.mapWidget = Fusion.getWidgetById('Map');

        if (json.AutogenerateLayerNames)
            this.autogenerateLayerNames = (json.AutogenerateLayerNames[0] == "true");

        if (json.UseMapMessagePrompt)
            this.mapMessagePrompt = (json.UseMapMessagePrompt[0] == "true");
        
        if (json.StylizationType)
            this.bUseAdvancedStylization = (json.StylizationType[0] == "advanced");
        
        if (json.DataStoreFormat && json.RedlineGeometryFormat) {
            if (json.DataStoreFormat[0] == "SDF" ||
                json.DataStoreFormat[0] == "SHP" ||
                json.DataStoreFormat[0] == "SQLite") {
                
                var geomTypes = parseInt(json.RedlineGeometryFormat[0]);
                if (json.DataStoreFormat[0] == "SHP") {
                    //Only accept if geometry type is singular
                    if (geomTypes == 1 || geomTypes == 2 || geomTypes == 4) {
                        this.defaultDataStoreFormat = json.DataStoreFormat[0];
                        this.defaultRedlineGeometryType = geomTypes;
                        if (json.AutoCreateOnStartup)
                            this.bCreateOnStartup = (json.AutoCreateOnStartup[0] == "true");
                    }
                } else {
                    this.defaultDataStoreFormat = json.DataStoreFormat[0];
                    this.defaultRedlineGeometryType = geomTypes;
                    if (json.AutoCreateOnStartup)
                        this.bCreateOnStartup = (json.AutoCreateOnStartup[0] == "true");
                }
            }
        }

        // register Redline specific events
        this.registerEventID(Fusion.Event.REDLINE_FEATURE_ADDED);

        this.sTarget = json.Target ? json.Target[0] : "";
        if (this.sTarget)
            this.taskPane = new Fusion.Widget.Redline.DefaultTaskPane(this, widgetTag.location);
    },

    getSessionID: function() {
        return this.getMapLayer().getSessionID();
    },

    getMapName: function() {
        return this.getMapLayer().getMapName();
    },

    // activate the redline widget
    activate: function() {
        if (this.taskPane) {
            this.taskPane.loadDisplayPanel();
        }
    },

    // deactivate the redline widget
    deactivate: function() {
        if (this.taskPane) {
            this.taskPane.abortActiveDigitization();
        }
        if (this.mapMessagePrompt)
            this.mapWidget.message.clear(); //Clear digization prompts
    }
});


Fusion.Widget.Redline.DefaultTaskPane = OpenLayers.Class(
{
    // a reference to the redline widget
    widget: null,

    // the the task pane windows
    taskPaneWin: null,

    // the panel url
    panelUrl:  'widgets/Redline/markupmain.php',

    initialize: function(widget,widgetLocation) {
        this.widget = widget;
        this.widget.registerForEvent(Fusion.Event.REDLINE_FEATURE_ADDED, OpenLayers.Function.bind(this.featureAdded, this));
    },

    loadDisplayPanel: function() {
        var url = Fusion.getFusionURL() + this.panelUrl;
        var params = [];

        // Add any additional params here
        params.push('LOCALE='+Fusion.locale);
        params.push('MAPNAME='+this.widget.getMapName());
        params.push('SESSION='+this.widget.getSessionID());
        
        if (this.widget.defaultDataStoreFormat != null && this.widget.defaultRedlineGeometryType > 0) {
            params.push('REDLINEFORMAT=' + this.widget.defaultDataStoreFormat);
            params.push('REDLINEGEOMTYPE=' + this.widget.defaultRedlineGeometryType);
            params.push('AUTOCREATE=' + (this.widget.bCreateOnStartup ? "1" : "0"));
        }
        params.push("REDLINESTYLIZATION=" + (this.widget.bUseAdvancedStylization ? "ADVANCED" : "BASIC"));

        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');

        var taskPaneTarget = Fusion.getWidgetById(this.widget.sTarget);
        var outputWin = window;

        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
            outputWin = taskPaneTarget.iframe.contentWindow;
        } else {
            outputWin = window.open(url, this.widget.sTarget, this.widget.sWinFeatures);
        }
        //outputWin.parent = window;
        this.taskPaneWin = outputWin;
    },

    abortActiveDigitization: function() {
        //This function exists if MapGuideViewerApi.js was included in
        if (this.taskPaneWin.ClearDigitization)
            this.taskPaneWin.ClearDigitization(true);
    }
});
/**
 * Fusion.Widget.RefreshMap
 *
 * $Id: RefreshMap.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.RefreshMap
 *
 * Refreshes the current map view without changing zoom or center.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.RefreshMap = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    activate: function() {
        this.getMap().redraw();
    }
});
/**
 * Fusion.Widget.SaveMap
 *
 * $Id: SaveMap.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SaveMap
 *
 * Save the current map image on the client's computer
 *
 * usage:
 * DWF format support requires a structure like this in the application
 * definition's widget tag extension:
 *    <Extension>
 *      <Format></Format>
 *      <ResourceId></ResourceId>
 *      <Scale></Scale>
 *    </Extension>
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.SaveMap = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    iframe : null,
    printLayout : null,
    printScale : null,
    imageWidth : null,
    imageHeight : null,
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.format = (json.Format && json.Format[0] != '')?
                       json.Format[0] : 'png';

        //for DWF, parse printLayouts and build menu
        if (this.format == 'DWF' && json.PrintLayout.length) {

            var layouts = json.PrintLayout;
            for (var i = 0; i < layouts.length; i++) {
                var layout = layouts[i];
                var opt = {
                    label: layout.Name[0]
                };
                var data = {rid:layout.ResourceId[0]};
                if (layout.PageHeight) {
                    data.pageHeight = layout.PageHeight[0];
                };
                if (layout.PageWidth) {
                    data.pageWidth = layout.PageWidth[0];
                };
                if (layout.Margins) {
                    data.margins = [layout.Margins[0].Top[0],
                                    layout.Margins[0].Left[0],
                                    layout.Margins[0].Right[0],
                                    layout.Margins[0].Bottom[0]];
                };
                var menuItem = null;
                if (layout.Scale) {
                    //create entries for weblayout specified scales
                    menuItem = new Jx.Menu.SubMenu(opt);
                    data.scales = [];
                    for (var j=0; j < layout.Scale.length; j++) {
                        data.scales.push(layout.Scale[j]);
                        menuItem.add(
                            new Jx.Menu.Item({
                                label:layout.Scale[j],
                                onClick: OpenLayers.Function.bind(this.setLayout, this, data, j)
                            })
                        );
                    }
                    //add an entry for current scale
                    menuItem.add(
                        new Jx.Menu.Item({
                            label:'Current Scale',
                            onClick: OpenLayers.Function.bind(this.setLayout, this, data)
                        })
                    );
                } else {
                    //if there are no scales, the layout is used with current scale
                    opt.onClick = OpenLayers.Function.bind(this.setLayout, this, data);
                    menuItem = new Jx.Menu.Item(opt);
                };
                this.oMenu.add(menuItem);
            }
        } else if(this.format.toLowerCase() == 'dwf') {
            var layout = json;
            if(layout.ResourceId) {
                this.printLayout = layout.ResourceId[0];
            };
            if (layout.PageHeight) {
                this.pageHeight = layout.PageHeight[0];
            };
            if (layout.PageWidth) {
                this.pageWidth = layout.PageWidth[0];
            };
            if (layout.Margins) {
                this.margins = [layout.Margins[0].Top[0],
                                layout.Margins[0].Left[0],
                                layout.Margins[0].Right[0],
                                layout.Margins[0].Bottom[0]];
            };
            if (layout.Scale && layout.Scale != '0') {
                this.printScale = layout.Scale[0];
            };

        } else {
            if (json.Width && json.Width[0] != '') {
                this.imageWidth = json.Width[0];
            }
            if (json.Height && json.Height[0] != '') {
                this.imageHeight = json.Height[0];
            }
        }

        this.enable = Fusion.Widget.SaveMap.prototype.enable;
    },

    enable: function() {
        Fusion.Widget.prototype.enable.apply(this, []);
    },

    setLayout: function(data) {
        this.printLayout = data.rid;
        this.pageHeight = data.pageHeight;
        this.pageWidth = data.pageWidth;
        this.pageMargins = data.margins;
        //when the selected item has a scale, the index into the scales array
        //is passed, otherwise value is reset and current scale is used.
        if (arguments.length == 3){
            this.printScale = parseFloat(data.scales[arguments[1]]);
        } else {
            this.printScale = null;
        }

        this.activateTool();
    },

    /**
     * called when the button is clicked by the Fusion.Widget widget
     * prompts user to save the map.
     */
    activate: function() {
        if (!this.iframe) {
            this.iframe = document.createElement('iframe');
            this.iframe.id = 'w';
            this.iframe.style.visibility = 'hidden';
            document.body.appendChild(this.iframe);
        }
        var szLayout = '';
        var szScale = '';
        var szPageHeight = '';
        var szPageWidth = '';
        var szPageMargins = '';
        if (this.format.toLowerCase() == 'dwf') {
            if (this.printLayout) {
                szLayout = '&layout=' + this.printLayout;
            //} else {
            //    alert('DWF Save is not properly configured.');
            //    return;
            }
            if (this.printScale) {
                szScale = '&scale=' + this.printScale;
            }
            if (this.pageHeight) {
                szPageHeight = '&pageheight=' + this.pageHeight;
            }
            if (this.pageWidth) {
                szPageWidth = '&pagewidth=' + this.pageWidth;
            }
            if (this.pageMargins) {
                szPageMargins = '&margins=' + this.pageMargins.join(',');
            }
        }
        var szHeight = '';
        if (this.imageHeight) {
            szHeight = '&height=' + this.imageHeight;
        }
        var szWidth = '';
        if (this.imageWidth) {
            szWidth = '&width=' + this.imageWidth;
        }
        var m = this.getMapLayer();
        if(navigator.appVersion.match(/\bMSIE\b/)) {
            var url = Fusion.fusionURL + 'layers/' + m.arch + '/' + Fusion.getScriptLanguage() + "/SaveMapFrame." + Fusion.getScriptLanguage() + '?session='+m.getSessionID() + '&mapname=' + m.getMapName() + '&format=' + this.format.toUpperCase() + szLayout + szScale + szWidth + szHeight + szPageHeight + szPageWidth + szPageMargins;
            w = open(url, "Save", 'menubar=no,height=200,width=300');
        } else {
            var s = Fusion.fusionURL + 'layers/' + m.arch + '/' + Fusion.getScriptLanguage() + "/SaveMap." + Fusion.getScriptLanguage() + '?session='+m.getSessionID() + '&mapname=' + m.getMapName() + '&format=' + this.format.toUpperCase() + szLayout + szScale + szWidth + szHeight + szPageHeight + szPageWidth + szPageMargins;

            this.iframe.src = s;
        }
    }
});
/**
 * Fusion.Widget.SaveMapImage
 *
 * $Id: About.js 1656 2008-11-08 21:44:26Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/********************************************************************
* Class: Fusion.Widget.SaveMapImage
*
* SaveMapImage widget to display a pop-up window with an image of the map
* that can be saved to the users disk
*
* Inherits from:
*  - <Fusion.Widget>
* **********************************************************************/

Fusion.Widget.SaveMapImage = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    
/*
 * Constructor: SaveMapImage
 *
 * Parameters:
 *
 * widgetTag - JSON node for this widget from the Application definition
 *
 */
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        //this.enable();
    },

    /**
     * Function: execute
     *
     * opens a pop-up window with the about information when invoked
     * 
     */
    activate: function() {
      var map = this.oMap.oMapOL;
      var imageUrl = null;
      if (map.baseLayer.singleTile) {
        var imageUrl = map.baseLayer.grid[0][0].url;
      } //TODO: how to get tiled and overlay images??
      
      if (imageUrl) {
        window.open(imageUrl,'','');
      }
    }
});
/**
 * Fusion.Widget.SaveSession
 *
 * $Id: SaveSession.js 455 2011-05-17 17:24:43Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.SaveSession
 *
 * A Widget that saves the session folder and returns an unique id 
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 ****************************************************************************/
Fusion.Event.SAVE_SESSION_FINISHED = Fusion.Event.lastEventId++;

Fusion.Widget.SaveSession = OpenLayers.Class(Fusion.Widget, {
    oSessionName: null,
    oInputURL: null,
    oSaveSessionLink: null,
    oSaveSessionShare: null,
    oEmail: null,
    oFacebook: null,
    oTwitter: null,
    oDialog: null,
    bDrawn: false,
    
    initializeWidget: function(widgetTag){
        var arch = this.getMapLayer().arch;
        var lang = Fusion.getScriptLanguage();
        this.script = 'layers/' + arch + '/' + lang + '/SaveSession.' + lang;

        this.getMap().registerEventID(Fusion.Event.SAVE_SESSION_FINISHED);
        this.oDialog  = new Jx.Dialog({
            id: 'saveSessionDialog',
            label: 'Save Session',
            modal: false,
            resize: true,
            width: 500,
            height: 150
        });
        
    },
    
    activate: function(){
        this.oDialog.open();
        this.drawSaving();
        this.save();
    },
    
    drawSaving: function() {   
      this.oDialog.content.empty();
      var info = document.createElement('div');
      info.innerHTML = "Saving session, please wait...";
      info.addClass('sheetTip');
      this.oDialog.content.adopt(info);
    },
    
    drawForm: function() {
      this.oDialog.content.empty();
      
      this.form = new Jx.Form({
        name: 'testForm',
        formClass: 'jxFormBlock'
      }).addTo(this.oDialog.content);
      
      this.oInputURL = new Jx.Field.Text({
        id: 'sessionUrl',
        name: 'sessionUrl',
        label: 'Session URL',
        width: 400
      }).addTo(this.form);
      
      var info = document.createElement('div');
      info.innerHTML = "Copy and paste the URL above to restore this session";
      info.addClass('sheetTip');
      this.oDialog.content.adopt(info);
      
    },
    
    save: function(){
        var that = this;
        var mapWidget = this.getMap();
        var mapLayer = this.getMapLayer();

        var aMapExtents  = [];
        aMapExtents[0] = parseFloat(mapWidget._oCurrentExtents.left);
        aMapExtents[1] = parseFloat(mapWidget._oCurrentExtents.bottom);
        aMapExtents[2] = parseFloat(mapWidget._oCurrentExtents.right);
        aMapExtents[3] = parseFloat(mapWidget._oCurrentExtents.top);


        var params = {
                parameters: OpenLayers.Util.getParameterString({
                    command: 'save',
                    extents: aMapExtents,
                    layers: mapLayer.aVisibleLayers,
                    session: mapLayer.getSessionID(),
                    mapname: mapLayer.getMapName()
                }),
                onComplete: function(xhr) {
                    var o = Fusion.parseJSON(xhr.responseText);
                    that.saveCallBack(o);
                }
        };
        if (mapLayer.hasSelection()) {
            params.parameters += "&queryfile=" + mapLayer._sQueryfile;
        }
        Fusion.ajaxRequest(this.script,params);
    },
    
    saveCallBack: function(oKey){
        if(oKey.error){
            alert(oKey.error);
        }
        if(oKey.sessionKey){
            this.drawForm();
            var restoreURI = new URI(window.location.href);
            restoreURI.setData({restoreState:oKey.sessionKey},true,'query');

            this.oInputURL.setValue(restoreURI.toString());
            /*
            this.oFacebook.href = "http://www.facebook.com/share.php?u="+escape(this.oInputURL.value)+"&t="+escape(this.oSessionName.value);
            this.oFacebook.target = "_blank";
            this.oTwitter.href = "http://twitter.com/home?status="+escape(this.oSessionName.value)+" :"+escape(this.oInputURL.value);
            this.oTwitter.target = "_blank";
            this.oEmail.href="mailto:?subject="+this.oSessionName.value+"&body="+escape("Here is a link to a map that I found interesting ")+this.oInputURL.value;
            
            //mailto:you@yourdomain.com?subject=Your Subject&body=Message for the body.
            this.showDiv(this.oSaveSessionShare);
            */
            //this.showDiv(this.oSaveSessionLink);
            this.oInputURL.field.focus();
            this.oInputURL.field.select();
        }
    }
    
});
/**
 * Fusion.Widget.Scalebar
 *
 * $Id: Scalebar.js 2675 2013-03-25 03:32:46Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Scalebar
 *
 * A dynamically generated cartographic scalebar 
 *
 * uses JavaScript Scale Bar for MapServer 
 * (http://mapserver.commenspace.org/tools/scalebar/
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


if (typeof(ScaleBarTool)=='undefined') {
    Fusion.require('widgets/scalebar/scalebartool.js');
}

Fusion.Widget.Scalebar = OpenLayers.Class(Fusion.Widget, {
    style: 'thin',
    displaySystem: 'metric',
    minWidth: 100,
    maxWidth: 200,
    divisions: 2,
    subdivisions: 2,
    showMinorMeasures: true,
    abbreviateLabel: true,
    singleLine: false,
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.style = json.Style ? json.Style[0].toLowerCase() : this.style;
        if (this.style != 'fancy' && 
            this.style != 'fat' && 
            this.style != 'thin' && 
            this.style != 'thinner') {
            this.style = 'thin';
        }
        
        this.displaySystem = json.DisplaySystem ? json.DisplaySystem[0] : this.displaySystem;
        this.minWidth = json.MinWidth ? json.MinWidth[0] : this.minWidth;
        this.maxWidth = json.MaxWidth ? json.MaxWidth[0] : this.maxWidth;
        this.divisions = json.Divisions ? json.Divisions[0] : this.divisions;
        this.subdivisions = json.SubDivisions ? json.SubDivisions[0] : this.subdivisions;
        this.showMinorMeasures = (json.ShowMinorMeasures && json.ShowMinorMeasures[0]) == 'false' ? false : true;
        this.abbreviateLabel = (json.AbbreviateLabel && json.AbbreviateLabel[0]) == 'true' ? true : false;
        this.singleLine = (json.SingleLine && json.SingleLine[0]) == 'true' ? true : false;
        
        
        if (document.styleSheets) {
            if (document.styleSheets[0]) {
                var url = Fusion.getFusionURL() + 'widgets/scalebar/scalebar-'+this.style+'.css';
                //console.log(url);
                if (document.styleSheets[0].addImport) {
                    document.styleSheets[0].addImport(url);
                } else {
                    document.styleSheets[0].insertRule('@import url('+url+');',0);
                }
            }
        }

        this.oScaleBar = new ScaleBarTool(1);
        this.oScaleBar.displaySystem = this.displaySystem;
        this.oScaleBar.minWidth = this.minWidth;
        this.oScaleBar.maxWidth = this.maxWidth;
        this.oScaleBar.divisions = this.divisions;
        this.oScaleBar.subdivisions = this.subdivisions;
        this.oScaleBar.showMinorMeasures = this.showMinorMeasures;
        this.oScaleBar.abbreviateLabel = this.abbreviateLabel;
        this.oScaleBar.singleLine = this.singleLine;
        
        //FireFox gives the following error when just calling place
        //but putting it in a timeout seems to fix the problem.  When
        //debugging using firebug, the problem doesn't occur.
        //this.oScaleBar.place(widgetTag.name);
        //A parameter or an operation is not supported by the underlying object"  code: "15
        window.setTimeout(OpenLayers.Function.bind(this.oScaleBar.place, this.oScaleBar, widgetTag.name), 1);

        this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, OpenLayers.Function.bind(this.extentsChangedCB, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.extentsChangedCB, this));
    },

    extentsChangedCB : function() {
        this.oScaleBar.update(this.getMap().getScale());
    }
});
/**
 * Fusion.Widget.ScalebarDual
 *
 * $Id: ScalebarDual.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.ScalebarDual
 *
 * A dynamically generated cartographic scalebar that looks like the Google scalebar
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.ScalebarDual = OpenLayers.Class(Fusion.Widget, {
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        var maxWidth = json.MaxWidth ? parseInt(json.MaxWidth[0]) : 300;
        var topInUnits = json.TopInUnits ? json.TopInUnits[0] : 'ft';
        var topOutUnits = json.TopOutUnits ? json.TopOutUnits[0] : 'mi';
        var bottomInUnits = json.BottomInUnits ? json.BottomInUnits[0] : 'm';
        var bottomOutUnits = json.BottomOutUnits ? json.BottomOutUnits[0] : 'km';
        var options = {   //set these from widgetTag extension
            maxWidth:  maxWidth,
            topInUnits: topInUnits,
            topOutUnits: topOutUnits,
            bottomInUnits: bottomInUnits,
            bottomOutUnits: bottomOutUnits
        };
        if (this.domObj) {
            options.div = this.domObj;
        }
        this.addControl(new OpenLayers.Control.ScaleLine(options));
    }
});
/**
 * Fusion.Widget.Search
 *
 * $Id: Search.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Search
 *
 * A widget that displays a pre-configured search form to the user and then
 * runs the search.  Searches are done on the attributes of specifiedd layers.
 *
 * uses JavaScript Scale Bar for MapServer
 * (http://mapserver.commenspace.org/tools/scalebar/
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Search = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    sFeatures : 'menubar=no,location=no,status=no,scrollbars=yes',

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "SearchWindow";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/Search/SearchPrompt.php';
        this.prompt = json.Prompt ? json.Prompt[0] : "";
        this.layer = json.Layer ? json.Layer[0] : "";
        this.filter = json.Filter ? json.Filter[0] : "";
        this.limit = json.MatchLimit ? json.MatchLimit[0] : 100;
        this.resultColumns = json.ResultColumns ? json.ResultColumns[0].Column : [];
        this.title = json.Title ? json.Title[0] : widgetTag.label;
    },

    activate: function() {
        var url = this.sBaseUrl;
        //add in other parameters to the url here

        var widgetLayer = this.getMapLayer();
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        var pageElement = $(this.sTarget);

        var params = [];
        params.push('locale='+Fusion.locale);
        params.push('session='+widgetLayer.getSessionID());
        params.push('mapname='+widgetLayer.getMapName());
        if (taskPaneTarget || pageElement) {
          params.push('popup=false');
        } else {
          params.push('popup=true');
        }
        params.push('title='+this.title);
        params.push('prompt='+this.prompt);
        params.push('target='+this.sTarget);
        params.push('filter='+this.filter);
        params.push('layer='+this.layer);
        params.push('limit='+this.limit);
        var names = [];
        var props = [];
        for (var i=0; i<this.resultColumns.length; ++i) {
          names.push(this.resultColumns[i].Name);
          props.push(this.resultColumns[i].Property);
        }
        params.push('properties='+props.join(","));
        params.push('propNames='+names.join(","));

        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        if ( taskPaneTarget ) {
            taskPaneTarget.setContent(url);
        } else {
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                window.open(url, this.sTarget, this.sFeatures);
            }
        }
    }
});
/**
 * Fusion.Widget.Select
 *
 * $Id: Select.js 2807 2013-11-13 05:27:56Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Select
 *
 * perform a selection on map features
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.Select = OpenLayers.Class(Fusion.Widget, {
    isExclusive: true,
    uiClass: Jx.Button,
    selectionType: 'INTERSECTS',
    nTolerance: 3,     //default pixel tolerance for a point click
    bActiveOnly: false, //only select feature(s) on the active layer?
    maxFeatures: 0,     //default of 0 selects all features (i.e. no maximum)
    pointClickSingleSelect: true, //default of true causes a point click always to select only a single feature

    initializeWidget: function(widgetTag) {
        this.asCursor = ['auto'];

        this.enable = Fusion.Widget.Select.prototype.enable;

        var json = widgetTag.extension;

        this.selectionType = json.SelectionType ? json.SelectionType[0] : 'INTERSECTS';

        if (json.Tolerance && (parseInt(json.Tolerance[0]) > 0)) {
            this.nTolerance = parseInt(json.Tolerance[0]);
        }

        if (json.MaxFeatures) {
            this.maxFeatures = parseInt(json.MaxFeatures[0]);
        }

        if(json.PointClickSingleSelect) {
            this.pointClickSingleSelect = (json.PointClickSingleSelect[0] != 'false');
        }

        this.bActiveOnly = (json.QueryActiveLayer &&
                           (json.QueryActiveLayer[0] == 'true' ||
                            json.QueryActiveLayer[0] == '1')) ? true : false;

        this.bComputeMetadata = (json.ComputeMetadata &&
                           (json.ComputeMetadata[0] == 'true' ||
                            json.ComputeMetadata[0] == '1')) ? true : false;

        if (this.bActiveOnly) {
            this.getMap().registerForEvent(Fusion.Event.MAP_ACTIVE_LAYER_CHANGED, OpenLayers.Function.bind(this.enable, this));
        }

        var mapWidget = this.getMap();
        this.map = mapWidget.oMapOL;
        this.handler = new OpenLayers.Handler.Box(this,{done: this.execute});
        //this.shiftHandler = new OpenLayers.Handler.Box(this,{done: this.extend},
        //                                {keyMask:OpenLayers.Handler.MOD_SHIFT});
        mapWidget.handlers.push(this.handler);
        //mapWidget.handlers.push(this.shiftHandler);

    },

    shouldActivateWith: function(widget) {
        return (widget instanceof Fusion.Widget.SelectPolygon &&
                widget.widgetUniqueId == this.widgetUniqueId);
        
    },
    
    enable: function() {
        if (this.bActiveOnly) {
            var layer = this.getMap().getActiveLayer();
            if (layer && layer.selectable) {
                Fusion.Widget.prototype.enable.apply(this, []);
            } else {
                this.disable();
            }
        } else {
            Fusion.Widget.prototype.enable.apply(this,[]);
        }
    },

    /**
       * activate the widget (listen to mouse events and change cursor)
       * This function should be defined for all functions that register
       * as a widget in the map
       */
    activate: function() {
        this.handler.activate();
        //this.shiftHandler.activate();
        this.getMap().setCursor(this.asCursor);
    },

    /**
       * deactivate the widget (listen to mouse events and change cursor)
       * This function should be defined for all functions that register
       * as a widget in the map
       **/
    deactivate: function() {
        this.handler.deactivate();
        //this.shiftHandler.deactivate();
        this.getMap().setCursor('auto');
    },

    /**
       *  set the extants of the map based on the pixel coordinates
       * passed
       *
       * Parameters:
        *   position will be either an instance of OpenLayers.Bounds when the mouse has
        *   moved, or an OpenLayers.Pixel for click without dragging on the map
        **/
    execute : function(position, extend) {
        if (this.getMap().isDigitizing) {
            //console.log("Currently digitizing. Doing nothing");
            return;
        }
    
        //ctrl click is used to launch a URL defined on the feature. See ClickCTRL widget
        if (this.keyModifiers & OpenLayers.Handler.MOD_CTRL) {
          //return;
        }

        var nRight, nTop;
        var nLeft = position.left;
        var nBottom = position.bottom;
        var maxFeaturesToSelect = this.maxFeatures;

        if (position instanceof OpenLayers.Bounds) {
          nRight = position.right;
          nTop = position.top;
        } else { // it's a pixel
          nRight = nLeft = position.x;
          nTop = nBottom = position.y;
          if(this.pointClickSingleSelect) {
              maxFeaturesToSelect = 1;
          }
        }

        var sMin = this.getMap().pixToGeo(nLeft,nBottom);
        var sMax = this.getMap().pixToGeo(nRight,nTop);
        var nXDelta = Math.abs(nLeft-nRight);
        var nYDelta = Math.abs(nBottom- nTop);

        var options = {};
        if (nXDelta <=this.nTolerance && nYDelta <=this.nTolerance) {
            var dfGeoTolerance = this.getMap().pixToGeoMeasure(this.nTolerance);
            sMin.x = sMin.x-dfGeoTolerance;
            sMin.y = sMin.y-dfGeoTolerance;
            sMax.x = sMax.x+dfGeoTolerance;
            sMax.y = sMax.y+dfGeoTolerance;
        }

        options.geometry = 'POLYGON(('+ sMin.x + ' ' +  sMin.y + ', ' +  sMax.x + ' ' +  sMin.y + ', ' + sMax.x + ' ' +  sMax.y + ', ' + sMin.x + ' ' +  sMax.y + ', ' + sMin.x + ' ' +  sMin.y + '))';
        options.selectionType = this.selectionType;
        options.maxFeatures = maxFeaturesToSelect;
        options.computed = this.bComputeMetadata;

        if (this.bActiveOnly) {
            var layer = this.getMap().getActiveLayer();
            if (layer) {
                options.layers = layer.layerName;
            } else {
                return;
            }
        }

        if (this.handler.dragHandler.evt.shiftKey) {
            options.extendSelection = true;
        }

        this.getMap().query(options);
    },

    /**
        * handler for extending the selection when the shift key is pressed
        *
        * Parameters:
        * evt - the OpenLayers.Event object that is being responded to
        */
    extend: function(position) {
        this.execute(position, true);
    },

    /**
        * calculate the keyboard modifier mask for this event
        *
        * Parameters:
        * evt - the OpenLayers.Event object that is being responded to
        */
    setModifiers: function(evt) {
        this.keyModifiers =
            (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
            (evt.ctrlKey  ? OpenLayers.Handler.MOD_CTRL  : 0) |
            (evt.altKey   ? OpenLayers.Handler.MOD_ALT   : 0);
    },

    /**
        * clears the keyboard modifier mask for this event
        *
        * Parameters:
        * evt - the OpenLayers.Event object that is being responded to
        */
    clearModifiers: function(evt) {
      this.keyModifiers = 0;
    },

    /**
        * allows run-time setting of widget parameters
        *
        * Parameters:
        * param - the widget parameter name to set; for the Select widget these may be:
        *               'Tolerance' and 'SelectionType'
        * value - the value to sue for the parameter
        */
    setParameter : function(param, value) {
        if (param == "Tolerance" && value > 0) {
            this.nTolerance = value;
        }
        if (param == 'SelectionType') {
            this.selectionType = value;
        }
    }
});
/**
 * Fusion.Widget.SelectRadius
 *
 * $Id: SelectRadius.js 1816 2009-03-11 20:33:51Z pagameba $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SelectRadius
 *
 * perform a selection by radius from a point
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/
Fusion.Event.RADIUS_WIDGET_ACTIVATED = Fusion.Event.lastEventId++;

Fusion.Widget.SelectAttribute = OpenLayers.Class(Fusion.Widget, {
    isExclusive: true,
    uiClass: Jx.Button,
    drawn: false,
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        if (json.Container) {
          this.container = $(json.Container[0]);
        }
        this.workArea = document.createElement('div');
        this.container.appendChild(this.workArea);
        
        //eventually there will be multiple rows for AND/OR ops on attributes
        this.attrRow = document.createElement('div');
        this.attrRow.className = 'selectAttrInputs';
        this.workArea.appendChild(this.attrRow);
        
        var d = document.createElement('div');
        d.innerHTML = "layer to query:";
        this.layerList = document.createElement('select');
        this.layerList.className = 'layerSelector';
        d.appendChild(this.layerList);
        this.attrRow.appendChild(d);

        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.listLayers, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_RELOADED, OpenLayers.Function.bind(this.listLayers, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_ACTIVE_LAYER_CHANGED, OpenLayers.Function.bind(this.setAttributeOptions, this));
    },
    
    /**
     * populate th list of seletable layers when the map loads
     */
    listLayers: function() {
        //this.layerList.empty();
        this.layerList.innerHTML = '';
        var map = this.getMapLayer(); 
        this.layerList.options[this.layerList.options.length] = new Option('--select--','');
        for (var i=0; i<map.aLayers.length; ++i) {
            var layer = map.aLayers[i];
            if (layer.selectable) {
              this.layerList.options[this.layerList.options.length] = new Option(layer.legendLabel,layer.layerName);
            }
        }
        this.layerList.onchange = OpenLayers.Function.bind(this.activateLayer, this);
        
        var sl = Fusion.getScriptLanguage();
        var queryScript = 'layers/' + map.arch + '/' + sl  + '/GetAttributes.' + sl;
        var params = {
            'mapname': map._sMapname,
            'session': map.getSessionID()
            //'layers': layers,
        };
        var ajaxOptions = {
            onSuccess: OpenLayers.Function.bind(this.setAttributes, this),
            method: 'GET',
            parameters: params
        };
        Fusion.ajaxRequest(queryScript, ajaxOptions);
        
    },
    
    setAttributes: function(xhr) {
      if (xhr.status < 400) {
          this.attrs = Fusion.parseJSON(xhr.responseText);
      }
    },
    
    setAttributeOptions: function(event) {
      if (this.drawn) {
        //this.propsList.empty();
        this.propsList.innerHTML = '';
        var layer = this.layerList[this.layerList.selectedIndex].value;
        var attrs = this.attrs[layer].propertyvalues;
        var props = this.attrs[layer].propertynames;
        for (var i=0; i<attrs.length; ++i) {
            this.propsList.options[this.propsList.options.length] = new Option(attrs[i],props[i]);
        }
      }
    },
    
    activateLayer: function(event) {
      if (this.drawn) {
        var layer = this.layerList[this.layerList.selectedIndex].value;
        var map = this.getMapLayer();
        var mapWidget = this.getMap();
        for (var i=0; i<map.aLayers.length; ++i) {
          if (map.aLayers[i].layerName == layer) {
            mapWidget.setActiveLayer(map.aLayers[i]);
            break;
          }
        }
      }
    },
    
    /**
     * activate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     */
    activate: function() {
        this.container.style.display = 'block';
        
        if (!this.drawn) {
          this.drawn = true;
          this.propsList = document.createElement('select');
          this.propsList.className = 'propsSelector';
          this.attrRow.appendChild(this.propsList);
          
          this.operatorList = document.createElement('select');
          this.operatorList.className = 'operatorSelector';
          this.operatorList.options[this.operatorList.options.length] = new Option("=","eq",true);
          this.operatorList.options[this.operatorList.options.length] = new Option("like","like");
          this.operatorList.options[this.operatorList.options.length] = new Option(">","gt");
          this.operatorList.options[this.operatorList.options.length] = new Option("<","lt");
          this.operatorList.options[this.operatorList.options.length] = new Option("<=","le");
          this.operatorList.options[this.operatorList.options.length] = new Option(">=","ge");
          this.operatorList.options[this.operatorList.options.length] = new Option("!=","ne");
          this.attrRow.appendChild(this.operatorList);
          
          this.attrValue = document.createElement('input');
          this.attrValue.className = 'propsValue';
          this.attrRow.appendChild(this.attrValue);
          
          new Jx.Button({
              label: 'Query',
              onClick: OpenLayers.Function.bind(this.execute, this)
          }).addTo(this.attrRow);
        }
    },

    /**
     * deactivate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     **/
    deactivate: function() {
        this.container.style.display = 'none';
        //this.workArea.style.display = 'none';
    },
    
    /**
     *  set the extants of the map based on the pixel coordinates
     * passed
     * 
     * @param center
     * @param radius
     **/
    execute: function() {
        var propertyName = this.propsList[this.propsList.selectedIndex].value;
        var operator = this.operatorList[this.operatorList.selectedIndex].value;
        var propertyValue = this.attrValue.value;
        var numValue = parseFloat(propertyValue);
        var filter = null;
        if ( isNaN(numValue) ) {
          //we have a string
          if (operator == 'like') {
            filter = "/.*"+propertyValue+".*/gi";
          } else {
            filter = "(["+propertyName+"] "+operator+" '"+propertyValue+"')";
          }
        } else {
          //we have a number
            filter = "(["+propertyName+"] "+operator+" "+propertyValue+")";
        }
        var options = {
          layers: this.layerList[this.layerList.selectedIndex].value,
          filterItem: propertyName,
          filter: filter
        };
        
        this.getMap().query(options);
    }
});
/**
 * Fusion.Widget.SelectPolygon
 *
 * $Id: SelectPolygon.js 2807 2013-11-13 05:27:56Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SelectPolygon
 *
 * perform a selection using a polygon
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.SelectPolygon = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,
    bActiveOnly: false, //only select feature(s) on the active layer?
    selectionType: 'INTERSECTS',
    nTolerance: 3, //default pixel tolernace for a point click
    
    initializeWidget: function(widgetTag) {
        this.asCursor = ['auto'];

        var json = widgetTag.extension;
        
        this.selectionType = json.SelectionType ? json.SelectionType[0] : 'INTERSECTS';
        if (json.Tolerance && (parseInt(json.Tolerance[0]) > 0)) {
            nTolerance = parseInt(json.Tolerance[0]);
        }
        
        this.bActiveOnly = (json.QueryActiveLayer &&
                           (json.QueryActiveLayer[0] == 'true' ||
                            json.QueryActiveLayer[0] == '1')) ? true : false;

        this.bComputeMetadata = (json.ComputeMetadata &&
                           (json.ComputeMetadata[0] == 'true' ||
                            json.ComputeMetadata[0] == '1')) ? true : false;
        
        //add in the OL Polygon handler
        var mapWidget = this.getMap();
        this.map = mapWidget.oMapOL;
        this.handlerOptions = {};
        this.handler = new OpenLayers.Handler.Polygon(this, {done: this.execute}, this.handlerOptions);
        mapWidget.handlers.push(this.handler);
        
        this.keyHandler = OpenLayers.Function.bind(this.onKeyPress, this);
    },
    
    onKeyPress: function(e) {
        var charCode = (e.charCode ) ? e.charCode : ((e.keyCode) ? e.keyCode : e.which);
        if (charCode == OpenLayers.Event.KEY_ESC) {
            this.deactivate();
        }
    },
    
    shouldActivateWith: function(widget) {
        return (widget instanceof Fusion.Widget.SelectPolygon &&
                widget.widgetUniqueId == this.widgetUniqueId);
    },
    
    /**
     * activate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     */
    activate: function() {
        OpenLayers.Event.observe(document,"keypress",this.keyHandler);
        this.handler.activate();
        var map = this.getMap();
        var msg = map.message;
        msg.info(OpenLayers.i18n("selectPolygonPrompt") + ' <a id="abortSelectPolygonLink" href="javascript:void(0)">' + OpenLayers.i18n("stop") + '</a>');
        var link = msg.container.ownerDocument.getElementById("abortSelectPolygonLink");
        //Wire the anchor click
        link.onclick = OpenLayers.Function.bind(this.deactivate, this);
        map.setCursor(this.asCursor);
        map.supressContextMenu(true);
        map.isDigitizing = true;
        map.triggerEvent(Fusion.Event.MAP_DIGITIZER_ACTIVATED);
    },

    /**
     * deactivate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     **/
    deactivate: function() {
        OpenLayers.Event.stopObserving(document,"keypress",this.keyHandler);
        this.handler.deactivate();
        var map = this.getMap();
        map.message.clear();
        map.setCursor('auto');
        map.supressContextMenu(false);
        map.isDigitizing = false;
        map.triggerEvent(Fusion.Event.MAP_DIGITIZER_DEACTIVATED);
    },
    
    /**
     *  
     **/
    execute: function(geom) {
        var options = {};
        var points = geom.getVertices();
        if (points.length < 3) {
            return;
        }
        options.geometry = geom.toString();
        options.selectionType = this.selectionType;
        options.computed = this.bComputeMetadata;
        
        if (this.handler.evt.ctrlKey) {
            options.extendSelection = true;
        }

        if (this.bActiveOnly) {
            var layer = this.getMap().getActiveLayer();
            if (layer) {
                options.layers = layer.layerName;
            } else {
                return;
            }
        }
        
        this.getMap().query(options);
        this.deactivate();
        return false;
    },
    
    setParameter: function(param, value) {
        if (param == "Tolerance" && value > 0) {
            this.nTolerance = value;
        }
        if (param == 'SelectionType') {
            this.selectionType = value;
        }
    }
});
/**
 * Fusion.Widget.SelectRadius
 *
 * $Id: SelectRadius.js 2807 2013-11-13 05:27:56Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SelectRadius
 *
 * perform a selection by radius from a point
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/
Fusion.Event.RADIUS_WIDGET_ACTIVATED = Fusion.Event.lastEventId++;

Fusion.Widget.SelectRadius = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,
    selectionType: 'INTERSECTS',
    bActiveOnly: false, //only select feature(s) on the active layer?
    nTolerance: 3, //default pixel tolernace for a point click
    defaultRadius: 20, //this is now in pixels
    
    initializeWidget: function(widgetTag) {
        this.asCursor = ['auto'];

        var json = widgetTag.extension;
        this.selectionType = json.SelectionType ? json.SelectionType[0] : 'INTERSECTS';

        if (json.Tolerance && (parseInt(json.Tolerance[0]) > 0)) {
            nTolerance = parseInt(json.Tolerance[0]);
        }

        this.defaultRadius = json.DefaultRadius ? parseInt(json.DefaultRadius[0]) : this.defaultRadius;
        this.bComputeMetadata = (json.ComputeMetadata &&
                           (json.ComputeMetadata[0] == 'true' ||
                            json.ComputeMetadata[0] == '1')) ? true : false;
        
        this.bActiveOnly = (json.QueryActiveLayer &&
                           (json.QueryActiveLayer[0] == 'true' ||
                            json.QueryActiveLayer[0] == '1')) ? true : false;
        
        var container = json.RadiusTooltipContainer ? json.RadiusTooltipContainer[0] : '';
        if (container != '') {
            this.radiusTip = $(container);
        }
        
        if (this.radiusTip) {
            this.radiusTipType = json.RadiusTooltipType ?
                                 json.RadiusTooltipType[0].toLowerCase() : 'dynamic';
            if (this.radiusTipType == 'dynamic') {
                var oDomElem =  this.getMap().getDomObj();
                oDomElem.appendChild(this.radiusTip);
                this.radiusTip.style.position = 'absolute';
                this.radiusTip.style.display = 'none';
                this.radiusTip.style.top = '0px';
                this.radiusTip.style.left = '0px';
                this.radiusTip.style.zIndex = 101;
            }
        }
        
        this.registerEventID(Fusion.Event.RADIUS_WIDGET_ACTIVATED);
        
        //add in the OL Polygon handler
        var mapWidget = this.getMap();
        this.map = mapWidget.oMapOL;
        this.handlerOptions = {sides: 40};
        this.handler = new OpenLayers.Handler.RegularPolygon(this, {
            interval: 100,
            done: this.execute,
            down: this.mouseDown,
            move: this.mouseMove
            }, this.handlerOptions);
        mapWidget.handlers.push(this.handler);
        
        this.keyHandler = OpenLayers.Function.bind(this.onKeyPress, this);
    },
    
    setRadius: function(r) {
        this.defaultRadius = r;
    },
    
    getRadius: function() {
        if (this.handler.active) {
            return this.handler.radius;
        } else {
            return this.defaultRadius;
        }
    },
    
    onKeyPress: function(e) {
        var charCode = (e.charCode ) ? e.charCode : ((e.keyCode) ? e.keyCode : e.which);
        if (charCode == OpenLayers.Event.KEY_ESC) {
            this.deactivate();
        }
    },
    
    /**
     * activate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     */
    activate: function() {
        OpenLayers.Event.observe(document,"keypress",this.keyHandler);
        var radius = this.getMap().pixToGeoMeasure(this.defaultRadius);
        this.handler.setOptions({radius: radius});
        this.handler.activate();
        var map = this.getMap();
        map.setCursor(this.asCursor);
        /*map units for tool tip*/
        this.units = map.units;
        var msg = map.message;
        msg.info(OpenLayers.i18n("selectRadiusPrompt") + ' <a id="abortSelectRadiusLink" href="javascript:void(0)">' + OpenLayers.i18n("stop") + '</a>');
        var link = msg.container.ownerDocument.getElementById("abortSelectRadiusLink");
        //Wire the anchor click
        link.onclick = OpenLayers.Function.bind(this.deactivate, this);
        map.supressContextMenu(true);
        this.triggerEvent(Fusion.Event.RADIUS_WIDGET_ACTIVATED, true);
        map.isDigitizing = true;
        map.triggerEvent(Fusion.Event.MAP_DIGITIZER_ACTIVATED);
    },

    /**
     * deactivate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     **/
    deactivate: function() {
        OpenLayers.Event.stopObserving(document,"keypress",this.keyHandler);
        this.handler.deactivate();
        var map = this.getMap();
        map.message.clear();
        map.setCursor('auto');
        map.supressContextMenu(false);
        /*icon button*/
        this.triggerEvent(Fusion.Event.RADIUS_WIDGET_ACTIVATED, false);
        map.isDigitizing = false;
        map.triggerEvent(Fusion.Event.MAP_DIGITIZER_DEACTIVATED);
    },
    
    /**
     * (public) mouseDown(e)
     *
     * handle the mouse down event
     *
     * @param e Event the event that happened on the mapObj
     */
    mouseDown: function(geom) {
        var evt = this.handler.evt;
        if (OpenLayers.Event.isLeftClick(evt)) {
            this.handler.fixedRadius = false;

            var p = this.getMap().getEventPosition(evt);
            var point = this.getMap().pixToGeo(p.x, p.y);
            var radius = this.getMap().pixToGeoMeasure(this.handler.radius);

            if (this.radiusTip && this.radiusTipType == 'dynamic') {
                this.radiusTip.style.display = 'block';
                var size = $(this.radiusTip).getBorderBoxSize();
                this.radiusTip.style.top = (p.y - size.height*2) + 'px';
                this.radiusTip.style.left = p.x + 'px';
                if (this.units == 'm' || this.units == 'ft') {
                    radius = Math.round(radius * 100)/100;
                }
                this.radiusTip.innerHTML = radius + this.units;
            }
        }
    },

    /**
     * (public) mouseMove(e)
     *
     * handle the mouse move event
     *
     * @param e Event the event that happened on the mapObj
     */
    mouseMove: function(geom) {
        
        var evt = this.handler.evt;
        if (OpenLayers.Event.isLeftClick(evt)) {
            var map = this.getMap();
            var p = map.getEventPosition(evt);
            var point = map.pixToGeo(p.x, p.y);
            //var center = this.circle.center;

            var radius = this.getMap().pixToGeoMeasure(this.handler.radius);//Math.sqrt(Math.pow(center.x-point.x,2) + Math.pow(center.y-point.y,2));

            if (this.radiusTip && this.radiusTipType == 'dynamic') {
                this.radiusTip.style.display = 'block';
                var size = $(this.radiusTip).getBorderBoxSize();
                this.radiusTip.style.top = (p.y - size.height*2) + 'px';
                this.radiusTip.style.left = p.x + 'px';
                if (this.units == 'm' || this.units == 'ft') {
                    radius = Math.round(radius * 100)/100;
                }
                this.radiusTip.innerHTML = radius + this.units;
            }
        }
    },
    
    /**
     *  set the extants of the map based on the pixel coordinates
     * passed
     * 
     * @param center
     * @param radius
     **/
    execute: function(geom) {
        if (this.radiusTip && this.radiusTipType == 'dynamic') {
            this.radiusTip.style.display = 'none';
            this.radiusTip.innerHTML = '';
        }
        if (this.handler.start == this.handler.last) {
            this.handler.clear();
        }

        var options = {};
        options.geometry = geom.toString();
        options.selectionType = this.selectionType;
        options.computed = this.bComputeMetadata;

        if (this.bActiveOnly) {
            var layer = this.getMap().getActiveLayer();
            if (layer) {
                options.layers = layer.layerName;
            } else {
                return;
            }
        }
        
        if (this.handler.evt.shiftKey) {
            options.extendSelection = true;
        }
        
        this.getMap().query(options);
        this.deactivate();
    },
    
    setParameter : function(param, value) {
        if (param == "Tolerance" && value > 0) {
            this.nTolerance = value;
        }
        if (param == 'SelectionType') {
            this.selectionType = value;
        }
    }
});
/**
 * Fusion.Widget.SelectRadiusValue
 *
 * $Id: SelectRadiusValue.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SelectRadiusValue
 *
 * A widget to allow the user to specify the radius to use for a 
 * SelectRadius widget.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.SelectRadiusValue = OpenLayers.Class(Fusion.Widget, {
    radiusWidgetName: null,
    label: '',
    className: '',
    domLabel: null,
    initializeWidget: function(widgetTag) {
        /* parse widget properties */
        var json = widgetTag.extension;
        
        this.radiusWidgetName = json.RadiusName ? json.RadiusName[0] : null;
        this.label = json.Label ? json.Label[0] : '';
        this.className = json.ClassName ? json.ClassName[0] : '';
        
        Fusion.registerForEvent(Fusion.Event.FUSION_INITIALIZED, OpenLayers.Function.bind(this.mapLoaded, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, OpenLayers.Function.bind(this.mapExtentsChanged, this));
    },
    
    draw: function() {
        /* put in the label */
        var units = this.getMap().units;
        this.domLabel = document.createElement('span');
        this.domLabel.className = this.className;
        this.domLabel.innerHTML = this.label + '(' + units + ')';
        
        /* put in the input */
        this.input = document.createElement('input');
        this.input.type = 'text';
        this.domLabel.appendChild(this.input);
        
        /* put into page */
        this.domObj.appendChild(this.domLabel);
        OpenLayers.Event.observe(this.input, 'blur', OpenLayers.Function.bind(this.onBlur, this));
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        if (this.uiObj.domObj) {
            this.domObj = this.uiObj.domObj;
        } else {
            this.domObj = this.uiObj;
        }
    },
    
    mapLoaded: function() {
        this.draw();
        this.input.disabled = true;
        var widgets = Fusion.getWidgetsByType('SelectRadius');
        for (var i=0; i<widgets.length; i++) {
            if (widgets[i].widgetTag.name == this.radiusWidgetName) {
                this.widget = widgets[i];
                this.widget.registerForEvent(Fusion.Event.RADIUS_WIDGET_ACTIVATED, this.dependantEnable.bind(this));
                break;
            }
        }
        this.updateFromWidgetValue();
    },
    
    dependantEnable: function(eventId, active) {
        if (this.widget) {
            if (active) {
                this.input.disabled = false;
            } else {
                this.input.disabled = true;
            }
        }
    },
    
    mapExtentsChanged: function() {
        this.updateWidgetValue();
    },
    
    onBlur: function() {
        this.updateWidgetValue();
    },
    
    updateWidgetValue: function() {
        if (this.widget) {
            var radius = this.input.getValue();
            this.widget.setRadius(radius);
        }
    },
    
    updateFromWidgetValue: function() {
        if (this.widget) {
            this.input.value = this.widget.getRadius();
        }
    }
});
/**
 * Fusion.Widget.SelectWithin
 *
 * $Id: SelectWithin.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SelectWithin
 *
 * A widget to perform a selection within a currently selected set of features.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/


Fusion.Widget.SelectWithin = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    sFeatures : 'menubar=no,location=no,resizable=no,status=no',

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "SelectWithinWindow";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/SelectWithin/SelectWithinPanel.php';
        
        this.bSelectionOnly = (json.DisableIfSelectionEmpty &&
                           (json.DisableIfSelectionEmpty[0] == 'true' ||
                            json.DisableIfSelectionEmpty[0] == '1')) ? true : false;
                            
        this.additionalParameters = [];
        if (json.AdditionalParameter) {
            for (var i=0; i<json.AdditionalParameter.length; i++) {
                var p = json.AdditionalParameter[i];
                var k = p.Key[0];
                var v = p.Value[0];
                this.additionalParameters.push(k+'='+encodeURIComponent(v));
            }
        }
        
        this.enable = Fusion.Widget.SelectWithin.prototype.enable;
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.enable, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.enable, this));
        this.disable();
    },

    enable: function() {
        var map = this.getMap();
        if (this.bSelectionOnly || !map) {
            if (map && map.hasSelection()) {
                if (this.action) {
                    this.action.setEnabled(true);
                } else {
                    Fusion.Widget.prototype.enable.apply(this,[]);
                }
            } else {
                if (this.action) {
                    this.action.setEnabled(false);
                } else {
                    this.disable();
                }
            }
        } else {
            if (this.action) {
                this.action.setEnabled(true);
            } else {
                Fusion.Widget.prototype.enable.apply(this,[]);
            }
        }
    },
    
    activate: function() {
        var url = this.sBaseUrl;
        //add in other parameters to the url here
        
        var widgetLayer = this.getMapLayer();
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        var pageElement = $(this.sTarget);

        var params = [];
        params.push('locale='+Fusion.locale);
        params.push('session='+widgetLayer.getSessionID());
        params.push('mapname='+widgetLayer.getMapName());
        if (taskPaneTarget || pageElement) {
          params.push('popup=false');
        } else {
          params.push('popup=true');
        }
        params = params.concat(this.additionalParameters);

        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
        } else {
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                window.open(url, this.sTarget, this.sWinFeatures);
            }
        }
    }
});
/**
 * Fusion.Widget.SelectionInfo
 *
 * $Id: SelectionInfo.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SelectionInfo
 *
 * Displays the number of features and number of layers in the current
 * selection.
 *
 * Template (string, optional) 
 *
 * The format of the output string.  Use {layers} and {features} as 
 * placeholders for the number of layers and features in the current
 * selection.
 *
 * You can embed HTML in the template, but you must escape any characters
 * that result in illegal HTML.  This would include:
 *
 * < is &lt;
 * > is &gt;
 * & is &amp;
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.SelectionInfo = OpenLayers.Class(Fusion.Widget, {
    defaultTemplate: 'selectionInfo',
    domSpan: null,
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        this.emptyText = json.EmptyText ? json.EmptyText[0] : this.domObj.innerHTML;
        this.template = json.Template ? json.Template[0] : null;
        
        this.domSpan = document.createElement('span');
        this.domSpan.className = 'spanSelectionInfo';
        this.domSpan.innerHTML = OpenLayers.i18n(this.emptyText);
        this.emptyText = json.EmptyText ? json.EmptyText[0] : 
            (this.domObj ? this.domObj.innerHTML : null);
        if (this.domObj) {
            this.domObj.innerHTML = '';
            this.domObj.appendChild(this.domSpan);
        } 

        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.update, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.update, this));
        
        Fusion.addWidgetStyleSheet(widgetTag.location + '/SelectionInfo/SelectionInfo.css');
        
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        if (this.uiObj.domObj) {
            this.uiObj.domObj.appendChild(this.domSpan);
        } else {
            this.uiObj.appendChild(this.domSpan);
        }
    },
    
    update: function() {
        var olMap = this.getMap();
        var aMaps = olMap.getAllMaps();
        var nLayers = 0;
        var nFeatures = 0;
        for (var i=0; i<aMaps.length; ++i) { 
          var map = aMaps[i];
          if (map.hasSelection()) {
              var layers = map.getSelectedLayers();
              nLayers += layers.length;
              nFeatures += map.getSelectedFeatureCount();
          }
        }
        if (nFeatures > 0) {
            if (this.template) {
              this.domSpan.innerHTML = this.template.replace('{0}',nFeatures).replace('{1}',nLayers);
            } else {
              this.domSpan.innerHTML = OpenLayers.i18n(this.defaultTemplate,{'features':nFeatures,'layers':nLayers});
            }
        } else {
            this.domSpan.innerHTML = OpenLayers.i18n(this.emptyText);
        }
    }
});
/**
 * Fusion.Widget.SelectionPanel
 *
 * $Id: SelectionPanel.js 2683 2013-03-28 10:07:16Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.SelectionPanel
 *
 * A widget to display information about the currently selected set of features.
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.SelectionPanel = OpenLayers.Class(Fusion.Widget, {
    /**
     * Property: previousIcon
     * {String} The default image for Previous page button.
     */
    previousIcon: 'images/icon_back.gif',

    /**
     * Property: nextIcon
     * {String} The default image for Previous page button.
     */    
    nextIcon: 'images/icon_forward.gif',
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        if (json.PreviousImageUrl) this.previousIcon = json.PreviousImageUrl;
        if (json.NextImageUrl) this.nextIcon = json.NextImageUrl;
        this.iResultsPerPage = json.ResultsPerPage ? json.ResultsPerPage[0] : 0;
        this.iResultsPerPage = parseInt(this.iResultsPerPage);
        if (isNaN(this.iResultsPerPage) || (this.iResultsPerPage < 0))
            this.iResultsPerPage = 0;

        if (json.SelectionRenderer)
        {
            var renderer = eval(json.SelectionRenderer[0]);
            if (renderer && renderer.prototype.CLASS_NAME && renderer.prototype.CLASS_NAME == "Fusion.Widget.SelectionPanel.SelectionRenderer") {
                this.renderer = new renderer(this);
            } else if (typeof renderer == "function") {
                var renderFunction = renderer;
                this.renderer = new Fusion.Widget.SelectionPanel.SelectionRenderer(this);
                this.renderer.updateSelection = function() {
                    this.getMap().getSelection(
                        OpenLayers.Function.bind(renderFunction));
                };
                this.renderer.clearSelection = false;
            } else {
                this.renderer = new Fusion.Widget.SelectionPanel.SelectionRendererDefault(this);
            }
        } else {
            this.renderer = new Fusion.Widget.SelectionPanel.SelectionRendererDefault(this);
        }
        this.iResultsPerPage = null; // handled by the renderer
        
        if (this.renderer.updateSelection) {
            this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON,
                                           OpenLayers.Function.bind(this.renderer.updateSelection, this.renderer));
        }
        
        if (this.renderer.clearSelection) {
            this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF,
                                           OpenLayers.Function.bind(this.renderer.clearSelection, this.renderer));
        }
    }
});

/* Class: Fusion.Widget.SelectionPanel.SelectionRenderer
 * This is a class designed to help users to create their own renderer
 * for customize display results. 
 */
Fusion.Widget.SelectionPanel.SelectionRenderer = OpenLayers.Class(
{
    /**
     * Property: oSelectionPanel
     * {<Fusion.Widget.SelectionPanel>} The parent widget that uses
     *                                  the renderer.
     */
    oSelection: null,

    /**
     * Property: aiCurrentIndex
     * {Array(int)} The index of the current position for pagination.
     *
     */
    aiCurrentIndex: null,
    
    /**
     * Property: iResultsPerPage
     * {int} The number of results per page for pagination.
     */
    iResultsPerPage: 0,

    /* Constructor: Fusion.Widget.SelectionPanel.SelectionRenderer
     * Constructor for a new <Fusion.Widget.SelectionPanel.SelectionRenderer> instance.
     * 
     * Parameters:
     * selectionPanel - {<Fusion.Widget.SelectionPanel>} The parent widget that uses 
     *                                                   the renderer.
     */   
    initialize: function(selectionPanel) {
        this.oSelectionPanel = selectionPanel;
        this.iResultsPerPage = selectionPanel.iResultsPerPage;
        this.aiCurrentIndex = new Array();
    },
    
    /**
     * Method: updateSelectionObject
     * Helper method to update the aiCurrentIndex array for pagination.
     *
     */
    updatePageIndexes: function() {
        var nLayers = this.oSelection.getNumLayers();
        for (var i=0; i<nLayers; i++) {
            this.aiCurrentIndex[this.oSelection.getLayer(i).getName()] = 0;
        }
    },

    /**
     * Method: getNextPage
     * Get the next batches of features. Wrapper of the getPage() method.
     * This method calcul the startIndex/endIndex of the next batch.
     *
     * Parameters:
     * selectionLayer - {<Fusion.SelectionObject.Layer>} The layer that contains 
     *                                                   the set of features.
     *
     * Returns:
     * {Array(Array)} An array of all features with their properties.
     */
    getNextPage: function(selectionLayer) {
        if (selectionLayer && (this.iResultsPerPage != 0)) {
            var layerName = selectionLayer.getName();
            if (this.aiCurrentIndex[layerName] >= selectionLayer.getNumElements()) {
                this.aiCurrentIndex[layerName] = this.aiCurrentIndex[layerName] - this.iResultsPerPage;
            }
            var iTotalElement = selectionLayer.getNumElements();
            var startIndex = this.aiCurrentIndex[layerName];
            var endIndex = startIndex + this.iResultsPerPage;
            if (endIndex >= iTotalElement) {
                endIndex = iTotalElement;
            }
      
            if (startIndex < 0) {
                startIndex = 0;
            }
            this.aiCurrentIndex[layerName] = endIndex;
            
            // if the last page doesn't contains "iResultsPerPage" elements. Fix the current index for the next getPreviousPage() call.
            var diff = (endIndex - startIndex); 
            if ( diff != this.iResultsPerPage) {
                this.aiCurrentIndex[layerName] = this.aiCurrentIndex[layerName] + (this.iResultsPerPage - diff);
            }
            
            return this.getPage(selectionLayer, startIndex, endIndex);
        }
        return this.getPage(selectionLayer);
    },

    /**
     * Method: getPreviousPage
     * Get the previous batches of features. Wrapper of the getPage() method.
     * This method calcul the startIndex/endIndex of the previous batch.
     *
     * Parameters:
     * selectionLayer - {<Fusion.SelectionObject.Layer>} The layer that contains 
     *                                                   the set of features.
     *
     * Returns:
     * {Array(Array)} An array of all features with their properties.
     */
    getPreviousPage: function(selectionLayer) {
        var layerName = selectionLayer.getName();
        if (selectionLayer && (this.aiCurrentIndex[layerName] != 0) && (this.iResultsPerPage != 0)) {
            
            var iTotalElement = selectionLayer.getNumElements();
            var startIndex = this.aiCurrentIndex[layerName] - (this.iResultsPerPage * 2);
            var endIndex = this.aiCurrentIndex[layerName]  - this.iResultsPerPage;
            if (startIndex < 0) {
                startIndex = 0;
                endIndex = (iTotalElement < this.iResultsPerPage) ? iTotalElement : this.iResultsPerPage;
            }
            
            this.aiCurrentIndex[layerName] = endIndex;
            return this.getPage(selectionLayer, startIndex, endIndex);
        }
        return this.getPage(selectionLayer);
    },

    /**
     * Method: getMap
     * Helper method to obtains the map.
     *
     * Returns:
     * {<Fusion.Maps>} The map that uses the SelectionPanel Widget.
     */
    getMap: function() {
        return this.oSelectionPanel.getMap();
    },

    /**
     * Method: getPage
     * Get a batches of features in a selection.
     *
     * Parameters:
     * selectionLayer - {<Fusion.SelectionObject.Layer>} The layer that contains 
     *                                                   the set of features.
     * startIndex - {int} The index of the first element.
     * endIndex   - {int} The index of the last element.
     *
     * Returns:
     * {Array(Array)} An array of all features with their properties.
     */
    getPage: function(selectionLayer, startIndex, endIndex) {
        var page = false;
        if (selectionLayer) {
            page = new Array();
            startIndex = startIndex ? startIndex : 0;
            endIndex = endIndex ? endIndex : selectionLayer.getNumElements();
            var propNames = selectionLayer.getPropertyNames();
            var index =0;
            for (var i=startIndex; i<endIndex; i++, index++) {
                page[index] = new Array();
                for (var j=0; j<propNames.length; j++) {
                    page[index][j] = selectionLayer.getElementValue(i, j);
                }
            }
        }
        return page;
    },

    /**
     * Method: updateSelection
     * Abstract method that handle the event: Fusion.Event.MAP_SELECTION_ON. This method
     *     should be implemented by all concrete class.
     */
    updateSelection: function() {},
    
    /**
     * Method: clearSelection
     * Abstract method that handle the event: Fusion.Event.MAP_SELECTION_OFF. This method
     *     should be implemented by all concrete class.
     */
    clearSelection: function() {},

    CLASS_NAME: "Fusion.Widget.SelectionPanel.SelectionRenderer"
});

/* Class: Fusion.Widget.SelectionPanel.SelectionRendererDefault
 * This class provide a default behavior for the selection panel.
 * 
 */
Fusion.Widget.SelectionPanel.SelectionRendererDefault = OpenLayers.Class(Fusion.Widget.SelectionPanel.SelectionRenderer,
{
    initialize : function(selectionPanel) {
        Fusion.Widget.SelectionPanel.SelectionRenderer.prototype.initialize.apply(this, [selectionPanel]);

        var d = document.createElement('div');

        this.toolbar = document.createElement('div');
        this.toolbar.className = 'selectionPanelToolbar';

        this.layerList = document.createElement('select');
        this.layerList.className = 'layerSelector';
        this.toolbar.appendChild(this.layerList);
        OpenLayers.Event.observe(this.layerList, 'change',
                      OpenLayers.Function.bind(this.renderSelectionFeatures, this));

        this.featureList = document.createElement('select');
        this.featureList.className = 'featureSelector';
        this.toolbar.appendChild(this.featureList);
        OpenLayers.Event.observe(this.featureList, 'change',
                      OpenLayers.Function.bind(this.renderFeature, this));

        this.featureDiv = document.createElement('div');
        this.featureDiv.className = 'selectionPanelContent';
        this.clearSelection();

        d.appendChild(this.toolbar);
        d.appendChild(this.featureDiv);
        
        Fusion.addWidgetStyleSheet(this.oSelectionPanel.getLocation() + 'SelectionPanel/SelectionPanel.css');
        this.oSelectionPanel.domObj.appendChild(d);
    },

    updateSelection: function() {
        this.getMap().getSelection(
            OpenLayers.Function.bind(this.renderSelectionLayers, this));
    },

    clearSelection: function() {
        this.layerList.options.length = 0;
        this.featureList.options.length = 0;
        this.oSelection = null;
        this.featureDiv.className = 'selectionPanelContent noSelection';
        this.featureDiv.innerHTML = OpenLayers.i18n('noSelection');
    },

    renderSelectionLayers: function(oSelection) {
        //TODO: this just gets the first map, we need them all
        this.oSelection = null;
        for (var mapName in oSelection) {
            this.oSelection = oSelection[mapName];
            break;
        }
        
        if (!this.oSelection) {
            return;
        }
 
        //clear the layer list select box of any previous selections
        this.featureDiv.className = 'selectionPanelContent';
        while (this.layerList.length>0) {
          this.layerList.remove(this.layerList.options[0]);
        }
        var nLayers = this.oSelection.getNumLayers();
        for (var i=0; i<nLayers; i++) {
            var layerObj = this.oSelection.getLayer(i);
            
            //find the legend label from the Map layer objects
            //join up all layers from all maps into an array
            var mapLayers = [];
            var map = this.getMap();
            for (j=0; j<map.aMaps.length; ++j) {
              if (map.aMaps[j].aLayers) {
                for (k=0; k<map.aMaps[j].aLayers.length; ++k) {
                  mapLayers.push(map.aMaps[j].aLayers[k]);
                }
              }
            }
            
            var labelName = layerObj.getName();
            for (var j=0; j<mapLayers.length; ++j) {
              if (mapLayers[j].layerName == labelName) {
                labelName = mapLayers[j].legendLabel;
                break;
              }
            }
            var opt = new Option(labelName, i);
            this.layerList.options[i] = opt;
        }
        this.layerList.selectedIndex = 0;
        this.renderSelectionFeatures();
    },

    renderSelectionFeatures: function() {
        var layerIdx = this.layerList.selectedIndex;
        var layerObj = this.oSelection.getLayer(layerIdx);

        //clear the feature list select box of any previous selections
        while (this.featureList.length>0) {
          this.featureList.remove(this.featureList.options[0]);
        }

        var nElements = layerObj.getNumElements();
        for (var i=0; i<nElements; i++) {
            var opt = new Option(i+1, i);
            this.featureList.options[i] = opt;
        }
        this.featureList.selectedIndex = 0;
        this.renderFeature();
    },

    renderFeature: function() {
        var layerIdx = this.layerList.selectedIndex;
        var featureIdx = this.featureList.selectedIndex;
        var layerObj = this.oSelection.getLayer(layerIdx);
        var nProperties = layerObj.getNumProperties();
        var aNames = layerObj.getPropertyNames();

        var table = document.createElement('table');

        var thead = document.createElement('thead');
        var tr = document.createElement('tr');
        var th = document.createElement('th');
        th.innerHTML = OpenLayers.i18n('attribute');
        tr.appendChild(th);
        var th = document.createElement('th');
        th.innerHTML = OpenLayers.i18n('value');
        tr.appendChild(th);
        thead.appendChild(tr);
        table.appendChild(thead);

        var tbody = document.createElement('tbody');
        table.appendChild(tbody);
        for (var i=0; i<nProperties; i++) {
            var tr = document.createElement('tr');
            if (i%2) {
                tr.className = 'oddRow';
            }
            var th = document.createElement('th');
            th.innerHTML = aNames[i];
            var td = document.createElement('td');
            td.innerHTML = layerObj.getElementValue(featureIdx, i);
            tr.appendChild(th);
            tr.appendChild(td);
            tbody.appendChild(tr);
        }
        this.featureDiv.innerHTML = '';
        this.featureDiv.appendChild(table);
    }
});



// This could be removed from this file if we want to keep only ONE default renderer.

/* Class: Fusion.Widget.SelectionPanel.SelectionRendererHorizontal
 * This class provide a alternate behavior for the selection panel.
 * Generate a table which have one feature per row.
 * 
 */
Fusion.Widget.SelectionPanel.SelectionRendererHorizontal = OpenLayers.Class(Fusion.Widget.SelectionPanel.SelectionRenderer,
{
    initialize : function(selectionPanel) {
        Fusion.Widget.SelectionPanel.SelectionRenderer.prototype.initialize.apply(this, [selectionPanel]);
        
        var d = document.createElement('div');
        this.featureDiv = document.createElement('div');
        this.featureDiv.innerHTML = 'No Selection';
        this.featureDiv.className = 'selectionPanelContent noSelection';
        d.appendChild(this.featureDiv);

        if (this.iResultsPerPage != 0) {
            this.previousButton = document.createElement('img');
            this.previousButton.src = this.oSelectionPanel.previousIcon;
            this.previousButton.style.position = "absolute";
            this.previousButton.style.left = "0px";
            this.previousButton.style.padding = "3px";
            OpenLayers.Event.observe(this.previousButton, 'click',
                          OpenLayers.Function.bind(this.renderLayers, this, 'prev'));
            this.nextButton = document.createElement('img');
            this.nextButton.src = this.oSelectionPanel.nextIcon;
            this.nextButton.style.position = "absolute";
            this.nextButton.style.right = "0px";
            this.nextButton.style.padding = "3px";
            OpenLayers.Event.observe(this.nextButton, 'click',
                          OpenLayers.Function.bind(this.renderLayers, this, 'next'));
            
            d.appendChild(this.previousButton);
            d.appendChild(this.nextButton);
        }

        Fusion.addWidgetStyleSheet(this.oSelectionPanel.getLocation() + 'SelectionPanel/SelectionPanel.css');
        this.oSelectionPanel.domObj.appendChild(d);
    },

    updateSelection: function() {
        this.getMap().getSelection(
            OpenLayers.Function.bind(this.renderSelection, this));
    },

    clearSelection: function() {
        this.oSelection = null;
        this.featureDiv.className = 'selectionPanelContent noSelection';
        this.featureDiv.innerHTML = OpenLayers.i18n('noSelection');
    },
    
    renderSelection: function(oSelection) {
        //TODO: this just gets the first map, we need them all
        this.oSelection = null;
        for (var mapName in oSelection) {
            this.oSelection = oSelection[mapName];
            break;
        }
        this.updatePageIndexes();
        this.renderLayers("next");
    },
    
    renderLayers: function(renderingPage) {
        if (!this.oSelection) {
            return;
        }
        
        $(this.featureDiv).removeClass('noSelection');
        this.featureDiv.innerHTML = '';
        
        var mapWidget = this.getMap();
        var aMaps = mapWidget.getAllMaps();
        var mgLayer = null;
        //NOTE: We are obviously assuming only one Fusion.Layers.MapGuide instance here (if any)
        for (var i = 0; i < aMaps.length; i++) {
            if (aMaps[i].arch == "MapGuide") {
                mgLayer = aMaps[i];
                break;
            }
        }
        
        if (mgLayer == null)
            return;
        
        var nLayers = this.oSelection.getNumLayers();
        for (var i=0; i<nLayers; i++) {
            var table = document.createElement('table');
            table.style.borderLeft = "1px solid #CCCCCC";
            table.style.marginBottom = "10px";
            var layerObj = this.oSelection.getLayer(i);
            var aNames = layerObj.getPropertyNames();
            
            //find the legend label from the Map layer objects
            var mapLayers = mgLayer.aLayers;
            var labelName = layerObj.getName();
            for (var j=0; j<mapLayers.length; ++j) {
                if (mapLayers[j].layerName == labelName) {
                    labelName = mapLayers[j].legendLabel;
                    break;
                }
            }
            
            var thead = document.createElement('thead');
            var tr = document.createElement('tr');
            var th = document.createElement('th');
            th.innerHTML = labelName;
            th.colSpan=aNames.length;
            th.style.textAlign = "center";
            tr.appendChild(th);
            thead.appendChild(tr);
            tr = document.createElement('tr');
            for (var j=0; j<aNames.length; j++) {
                th = document.createElement('th');
                th.innerHTML = aNames[j];
                th.style.textAlign = "center";
                tr.appendChild(th);
            }
            thead.appendChild(tr);
            table.appendChild(thead);
            var tbody = document.createElement('tbody');
            var page = (renderingPage == 'next') ? this.getNextPage(layerObj): this.getPreviousPage(layerObj);
            this.renderFeatures(page,tbody);
            table.appendChild(tbody);
            this.featureDiv.appendChild(table);
        }

    },

    renderFeatures: function(page, dom) {
        if (!page)
            return;

        for (var i=0; i<page.length; i++) {
            var tr = document.createElement('tr');
            if (i%2) {
                tr.className = 'oddRow';
            }
            for (var j=0; j<page[i].length; j++) {
                var td = document.createElement('td');
                td.innerHTML = page[i][j];
                tr.appendChild(td);
            }
            dom.appendChild(tr);            
        }
    }
});
/**
 * Fusion.Widget.TaskPane
 *
 * $Id: TaskPane.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.TaskPane
 *
 * A utility widget that holds output from other widgets.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 ****************************************************************************/
 
Fusion.Event.TASK_PANE_LOADED = Fusion.Event.lastEventId++;

Fusion.Widget.TaskPane = OpenLayers.Class(Fusion.Widget, {
    aExecutedTasks: null,   //array of URLs for tasks execcuted in the TaskPane
    nCurrentTask: -1,
    nTasks: 0,
    
    initializeWidget: function(widgetTag){
        this.aExecutedTasks = [];
        var url = Fusion.getFusionURL();
        var homeIcon =  url + widgetTag.location + 'TaskPane/taskpane.png';
        var homeClass = 'TaskPane_home';
        var prevIcon =  url + widgetTag.location + 'TaskPane/taskpane.png';
        var prevClass = 'TaskPane_back';
        var nextIcon =  url + widgetTag.location + 'TaskPane/taskpane.png';
        var nextClass = 'TaskPane_forward';
        var taskIcon =  url + widgetTag.location + 'TaskPane/taskpane.png';
        var taskClass = 'TaskPane_tasks';
        var initialTask = widgetTag.location + 'TaskPane/TaskPane.html';
              
        var json = widgetTag.extension;
        
        if (json.InitialTask) {
            initialTask = taskURL = json.InitialTask[0];
        } else {
            initialTask = url + initialTask;
        }
        
        homeIcon  = json.HomeIcon  ? json.HomeIcon[0]  : homeIcon;
        homeClass = json.HomeClass ? json.HomeClass[0] : homeClass;
        prevIcon  = json.PreviousIcon  ? json.PreviousIcon[0]  : prevIcon;
        prevClass = json.PreviousClass ? json.PreviousClass[0] : prevClass;
        nextIcon  = json.NextIcon  ? json.NextIcon[0]  : nextIcon;
        nextClass = json.NextClass ? json.NextClass[0] : nextClass;
        taskIcon  = json.TaskIcon  ? json.TaskIcon[0]  : taskIcon;
        taskClass = json.TaskClass ? json.TaskClass[0] : taskClass;
        
        if (json.MenuContainer) {
            this.menuName = json.MenuContainer[0];
        }
        
        this.toolbar = new Jx.Toolbar();

        this.homeButton = new Jx.Button({
            image: homeIcon, 
            imageClass: homeClass,
            tooltip: OpenLayers.i18n('taskHome'),
            onClick: OpenLayers.Function.bind(this.goHome, this)
        });
        this.prevButton = new Jx.Button({
            image: prevIcon, 
            imageClass: prevClass,
            tooltip: OpenLayers.i18n('prevTask'),
            onClick: OpenLayers.Function.bind(this.gotoPrevTask, this)
        });
        this.nextButton = new Jx.Button({
            image: nextIcon, 
            imageClass: nextClass,
            tooltip: OpenLayers.i18n('nextTask'),
            onClick: OpenLayers.Function.bind(this.gotoNextTask, this)
        });
        this.toolbar.add(
            this.homeButton,
            this.prevButton,
            this.nextButton
        );

        this.taskMenu = new Jx.Menu({
            image: taskIcon,
            imageClass: taskClass,
            label: OpenLayers.i18n('taskList'), 
            right:0
        });
        $(this.taskMenu.domObj).addClass('taskMenu');
        $(this.taskMenu.button.domObj).addClass('jxButtonContentLeft');
        this.toolbar.add(this.taskMenu);
        
        var iframeName = this.name+'_IFRAME';
        this.iframe = document.createElement('iframe');
        new Jx.Layout(this.iframe);
        this.iframe.setAttribute('name', iframeName);
        this.iframe.setAttribute('id', iframeName);
        this.iframe.setAttribute('frameborder', 0);
        this.iframe.style.border = '0px solid #fff';
        this.oTaskPane = new Jx.Panel({
            toolbars: [this.toolbar], 
            hideTitle: true,
            content: this.iframe
        });
        $(this.domObj).addClass('taskPanePanel');
        Fusion.addWidgetStyleSheet(widgetTag.location + 'TaskPane/TaskPane.css');
        
        this.domObj.appendChild(this.oTaskPane.domObj);
        //we need to trigger an initial resize after the panel
        //is added to the DOM
        this.oTaskPane.domObj.resize();
        
        Fusion.registerEventID(Fusion.Event.TASK_PANE_LOADED);
        Fusion.registerForEvent(Fusion.Event.FUSION_INITIALIZED, OpenLayers.Function.bind(this.setTaskMenu, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.setInitialContent, this, initialTask));
    },
    
    updateButtons: function() {
        this.prevButton.setEnabled(this.nCurrentTask > 0);
        this.nextButton.setEnabled(this.nCurrentTask < this.aExecutedTasks.length - 1);
    },
    
    gotoPrevTask: function() {
        this.nCurrentTask = this.nCurrentTask>0 ? --this.nCurrentTask : 0;
        var url = this.aExecutedTasks[this.nCurrentTask];
        this.loadFrame(url);
    },

    gotoNextTask: function() {
        this.nCurrentTask = this.nCurrentTask<this.aExecutedTasks.length-1 ? 
                          ++this.nCurrentTask : this.aExecutedTasks.length-1;
        var url = this.aExecutedTasks[this.nCurrentTask];
        this.loadFrame(url);
    },

    goHome: function() {
        this.nCurrentTask = 0;
        var url = this.aExecutedTasks[this.nCurrentTask];
        this.loadFrame(url);
    },

    setInitialContent: function(url) {
        this.aExecutedTasks = [];
        this.nCurrentTask = -1;
        this.setContent(url);
    },


    addCommonParams:function(url){
        //add in some common parameters if they aren't supplied already
        var baseUrl = url.split("?");
        var params = OpenLayers.Util.getParameters(url);
        var widgetLayer = this.getMapLayer();
        if (!params["LOCALE"] && !params["locale"]) {
          params["locale"] = Fusion.locale;
        }
        if (!params["SESSION"] && !params["session"]) {
          params["session"] = widgetLayer.getSessionID();
        }
        if (!params["MAPNAME"] && !params["mapname"]) {
          params["mapname"] = widgetLayer.getMapName();
        }
        var newUrl = baseUrl[0] + "?" + OpenLayers.Util.getParameterString(params);
        return newUrl;
    },
    
    isSameWithLast:function(url){
        return this.aExecutedTasks[this.aExecutedTasks.length-1] == this.addCommonParams(url) ;
    },
    
    setContent: function(url) {
        Fusion.triggerEvent(Fusion.Event.TASK_PANE_LOADED);
        
        var newUrl = this.addCommonParams(url);
        this.aExecutedTasks.push(newUrl);
        ++this.nCurrentTask;
        this.loadFrame(newUrl);
    },
    
    loadFrame: function(url) {
        this.iframe.src = url;
        this.iframe.taskPaneId = this.widgetTag.name;
        this.updateButtons();
    },

    /**
     * Creates a list of tasks to be included in the task menu, once all widgets 
     * have been created.
     *
     */
    setTaskMenu: function() {
        if (this.menuName) {
            var container = this.getMap().widgetSet.getContainerByName(this.menuName);
            if (container) {
                container.createWidgets(this.getMap().widgetSet, this.taskMenu);
            }
        }
    }
   
});
/**
 * Fusion.Widget.Theme
 *
 * $Id: Theme.js 1523 2008-09-11 19:30:43Z pagameba $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.Theme
 *
 * The Theme widget prompts the user for some inputs and then creates a
 * new themed layer based on one of the existing map layers.
 *
 * If the Target property points to TaskPane widget, the task will be listed in
 * the menu list of the TaskPane and loaded there.
 * Otherwise if the target is an existing IFrame in the page it will be loaded
 * there, otherwise it will open a new window with that name.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Theme = OpenLayers.Class(Fusion.Widget, {
    isExclusive: false,
    uiClass: Jx.Button,
    sFeatures : 'menubar=no,location=no,resizable=no,status=no',

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.sTarget = json.Target ? json.Target[0] : "ThemeWindow";
        this.sBaseUrl = Fusion.getFusionURL() + 'widgets/Theme/thememain.php';
    },

    activate: function() {
        var url = this.sBaseUrl;
        //add in other parameters to the url here

        var widgetLayer = this.getMapLayer();
        var taskPaneTarget = Fusion.getWidgetById(this.sTarget);
        var pageElement = $(this.sTarget);

        var params = [];
        params.push('LOCALE='+Fusion.locale);
        params.push('SESSION='+widgetLayer.getSessionID());
        params.push('MAPNAME='+widgetLayer.getMapName());
        if (taskPaneTarget || pageElement) {
          params.push('POPUP=false');
        } else {
          params.push('POPUP=true');
        }

        if (url.indexOf('?') < 0) {
            url += '?';
        } else if (url.slice(-1) != '&') {
            url += '&';
        }
        url += params.join('&');
        if ( taskPaneTarget ) {
            if(!taskPaneTarget.isSameWithLast(url))
            {
                taskPaneTarget.setContent(url);
            }
        } else {
            if ( pageElement ) {
                pageElement.src = url;
            } else {
                window.open(url, this.sTarget, this.sWinFeatures);
            }
        }
    }
});
/**
 * Fusion.Widget.ViewOptions
 *
 * $Id: ViewOptions.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.ViewOptions
 *
 * A widget to allow selection of the display units for various widgets
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 ****************************************************************************/

Fusion.Widget.ViewOptions = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Menu,
    displayUnits: false,
    options: {
        'imperial': 'Miles', 
        'metric': 'Meters',
        'deg': 'Degrees'
    },
        
    menuItems: null,
    
    system: null,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;

        this.displayUnits = json.DisplayUnits ? json.DisplayUnits[0] : false;
        this.paramRegister.push("Units");
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.setMapUnits, this));
        this.menuItems = {};
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        var buttonSet = new Jx.ButtonSet();
        //set up the root menu
        for (var key in this.options) {
            var menuItem = new Jx.Menu.Item({
                label: OpenLayers.i18n(key),
                toggle: true,
                onDown: OpenLayers.Function.bind(this.setViewOptions, this, this.options[key])
            });
            buttonSet.add(menuItem);
            this.uiObj.add(menuItem);
            this.menuItems[key] = menuItem;
        }        
    },
    
    setViewOptions: function(units) {
        this.getMap().setViewOptions(units);
    },
    
    setMapUnits: function() {
        var units = this.displayUnits ? this.displayUnits : this.getMap().getUnits();
        this.setViewOptions(units);
        this.system = Fusion.unitSystem(Fusion.unitFromName(units));
        if (this.menuItems[this.system]) {
            this.menuItems[this.system].setActive(true);
        }
    },
    
    setParameter: function(param, value) {
        if (param == 'Units' && this.system != Fusion.unitSystem(Fusion.unitFromName(value))) {
            this.system = Fusion.unitSystem(Fusion.unitFromName(value))
            if (this.menuItems[this.system]) {
                this.menuItems[this.system].setActive(true);
            }
        }
    }
});
/**
 * Fusion.Widget.ViewSize
 *
 * $Id: ViewSize.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /*****************************************************************************
 * Class: Fusion.Widget.ViewSize
 *
 * Display the size of the current view in user-definable units
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 ****************************************************************************/
Fusion.Widget.ViewSize = OpenLayers.Class(Fusion.Widget, {
    defaultTemplate: 'x: {x}, y: {y}',
    domSpan: null,
    emptyText: '',
    
    /* the units to display distances in */
    units: Fusion.UNKNOWN,

    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        
        this.template = json.Template ? json.Template[0] : this.defaultTemplate;
        this.precision = json.Precision ? parseInt(json.Precision[0]) : -1;
        this.units = json.Units ? Fusion.unitFromName(json.Units[0]) : Fusion.UNKOWN;

        this.domSpan = document.createElement('span');
        this.domSpan.className = 'spanViewSize';
        this.domSpan.innerHTML = this.emptyText;
        if (this.domObj) {
            this.emptyText =  this.domObj.innerHTML;
            this.domObj.innerHTML = '';
            this.domObj.appendChild(this.domSpan);
        }
        
        this.getMap().registerForEvent(Fusion.Event.MAP_RESIZED, OpenLayers.Function.bind(this.updateViewSize, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_LOADED, OpenLayers.Function.bind(this.setUnits, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, OpenLayers.Function.bind(this.updateViewSize, this));
        this.registerParameter('Units');
        
        Fusion.addWidgetStyleSheet(widgetTag.location + '/ViewSize/ViewSize.css');
        
    },
    
    setUiObject: function(uiObj) {
        Fusion.Widget.prototype.setUiObject.apply(this, [uiObj]);
        if (this.uiObj.domObj) {
            this.uiObj.domObj.appendChild(this.domSpan);
        } else {
            this.uiObj.appendChild(this.domSpan);
        }
    },

    updateViewSize: function(e) {
        var map = this.getMap();
        var p = map.getSize();
        if (this.units != Fusion.PIXELS) {
            var gw = map.pixToGeoMeasure(p.w);
            var gh = map.pixToGeoMeasure(p.h);
            if (this.units != Fusion.UNKNOWN) {
                var convFactor = map.getMetersPerUnit();
                gw = Fusion.fromMeter(this.units, gw * convFactor);
                gh = Fusion.fromMeter(this.units, gh * convFactor);
            }
            if (this.precision >= 0) {
                var factor = Math.pow(10,this.precision);
                gw = Math.round(gw * factor)/factor;
                gh = Math.round(gh * factor)/factor;
            }
        }
        var unitAbbr = Fusion.unitAbbr(this.units);
        this.domSpan.innerHTML = this.template.replace('{w}',gw).replace('{h}',gh).replace('{units}', unitAbbr).replace('{units}', unitAbbr);
    },

    setUnits: function() {
      if (this.units == Fusion.UNKNOWN) {
        this.setParameter('Units',this.getMap().getUnits());
      }
      this.updateViewSize();
    },

    setParameter: function(param, value) {
        if (param == 'Units') {
            this.units = Fusion.unitFromName(value);
            this.updateViewSize();
        }
    }
});
/**
 * Fusion.Widget.Zoom
 *
 * $Id: Zoom.js 2686 2013-04-05 11:44:39Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.Zoom
 *
 * A widget that will zoom the map in or out.
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.Zoom = OpenLayers.Class(Fusion.Widget, {
    isExclusive: true,
    tolerance : 5,
    factor : 2,
    zoomIn: true,
    uiClass: Jx.Button,
    
    initializeWidget: function(widgetTag){
        var index = window.location.href.indexOf("?");
        var mainpath = window.location.href.substring(0,index);
        index = mainpath.lastIndexOf("/");
        mainpath = mainpath.substring(0,index+1);
        var asCursorString= "url(" + mainpath + "images/zoomin.cur" + "), auto";
        var zoomOutCursorString = "url(" + mainpath + "images/zoomout.cur" + "), auto";
        this.asCursor = [asCursorString,'-moz-zoom-in', 'auto'];
        this.zoomInCursor = [asCursorString,'-moz-zoom-in', 'auto'];
        this.zoomOutCursor = [zoomOutCursorString,'-moz-zoom-out', 'auto'];
        
        var json = widgetTag.extension;
        this.tolerance = json.Tolerance ? json.Tolerance[0] : this.tolerance;
        this.factor = json.Factor ? json.Factor[0] : this.factor;
        this.zoomIn = (json.Direction && json.Direction[0] == 'out') ? false : true;
        
        
        this.keypressWatcher = OpenLayers.Function.bind(this.keypressHandler, this);
        
        var mapWidget = this.getMap();
        this.map = mapWidget.oMapOL;
        this.handler = new OpenLayers.Handler.Box(this, {done: this.execute}, {keyMask:0});
        this.shiftHandler = new OpenLayers.Handler.Box(this, {done: this.altZoom}, 
                                        {keyMask:OpenLayers.Handler.MOD_SHIFT});
        mapWidget.handlers.push(this.handler);
        mapWidget.handlers.push(this.shiftHandler);
        
        //We're interested in these events, because this widget could get in the way
        this.getMap().registerForEvent(Fusion.Event.MAP_DIGITIZER_ACTIVATED, OpenLayers.Function.bind(this.onDigitizerActivated, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_DIGITIZER_DEACTIVATED, OpenLayers.Function.bind(this.onDigitizerDeactivated, this));
    },
    
    onDigitizerActivated: function() {
        //Do not allow this widget to interfere with active digitization, but do record the active state
        //so we can restore it afterwards
        this.bRestoreActiveState = this.isActive;
        //NOTE: This only deactivates zoom by mouse click and box drag. Mouse wheel zoom is still active, which
        //is a *good* thing.
        this.deactivate();
    },
    
    onDigitizerDeactivated: function() {
        if (this.bRestoreActiveState === true) {
            this.activate();
            this.bRestoreActiveState = false;
        }
    },
    
    shouldActivateWith: function(widget) {
        return (widget instanceof Fusion.Widget.SelectPolygon &&
                widget.widgetUniqueId == this.widgetUniqueId);
        
    },
    
   /**
     * activate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     */
    activate : function() {
        //TODO: Why Fusion.Widget has no active state flag? Probably should have one
        this.isActive = true;
        //console.log('Zoom.activate');
        this.handler.activate();
        this.shiftHandler.activate();
        /*cursor*/
        if (this.zoomIn) {
            this.getMap().setCursor(this.zoomInCursor);
        } else {
            this.getMap().setCursor(this.zoomOutCursor);
        }
        OpenLayers.Event.observe(document, 'keypress', this.keypressWatcher);
    },

    /**
     * deactivate the widget (listen to mouse events and change cursor)
     * This function should be defined for all functions that register
     * as a widget in the map
     **/
    deactivate : function() {
        this.isActive = false;
        //console.log('Zoom.deactivate');
        this.handler.deactivate();
        this.shiftHandler.deactivate();
        this.getMap().setCursor('auto');
        OpenLayers.Event.stopObserving(document, 'keypress', this.keypressWatcher);
    },

    /**
     * Method: zoomBox
     *
     * Parameters:
     * position - {<OpenLayers.Bounds>} or {<OpenLayers.Pixel>}
     */
    execute: function (position, altZoom) {
        /* if the last event had a shift modifier, swap the sense of this
                tool - zoom in becomes out and zoom out becomes in */
        var zoomIn = this.zoomIn;
        if (altZoom) {
            zoomIn = !zoomIn;
        }
        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 (zoomIn) {
                this.getMap().setExtents(bounds);
            } else {
                var newWidth = bounds.getWidth();
                var newHeight = bounds.getHeight();
                var currentExtents = this.getMap().getCurrentExtents();
                var currentWidth = currentExtents.getWidth();
                var currentHeight = currentExtents.getHeight();
                var factor = Math.min(newWidth/currentWidth, newHeight/currentHeight);
                var center = bounds.getCenterLonLat();
                this.getMap().zoom(center.lon, center.lat, factor);
            }
        } else { // it's a pixel
            var center = this.map.getLonLatFromPixel(position);
            var factor;
            if(!zoomIn && this.factor > 1) {
                factor = 1/this.factor;
            } else {
                factor = this.factor;
            }
            this.getMap().zoom(center.lon, center.lat, factor);
        }
    },

    /**
     * handler for zooming when the shift key is pressed.  This changes it
     * from in to out or vice versa
     *
     * Parameters:
     * position - {<OpenLayers.Bounds>} or {<OpenLayers.Pixel>}
     */
    altZoom: function(position) {
        this.execute(position, true);
    },
    
    /**
     * allows run-time setting of widget parameters 
     *
     * Parameters:
     * param - the widget parameter name to set; for the Zoom widget these may be:
     *               'Factor'
     * value - the value to use for the parameter
     */
    setParameter : function(param, value) {
        if (param == "Factor" && value > 0) {
            this.factor = value;
        }
    },
    
    keypressHandler: function(e) {
        var charCode=(e.charCode)?e.charCode:e.keyCode;
        if (charCode == Event.KEY_ESC) {
            this.handler.deactivate();
            this.handler.activate();
        }
    }
});
/**
 * Fusion.Widget.ZoomOnClick
 *
 * $Id: ZoomOnClick.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.ZoomOnClick
 *
 * Zoom the map by a fixed amount when a button is clicked
 * 
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/
Fusion.Widget.ZoomOnClick = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    factor: 4,
    
    initializeWidget: function(widgetTag) {
        var json = widgetTag.extension;
        this.factor = parseFloat(json.Factor ? json.Factor[0] : this.factor);
    },

    /**
     * called when the button is clicked by the Fusion.Widget widget
     */
    activate: function() {
        var center = this.getMap().getCurrentCenter();
        this.getMap().zoom(center.x, center.y, this.factor);
    },

    setParameter: function(param, value) {
        if (param == "Factor" && value > 0) {
            this.factor = value;
        }
    }
});
/**
 * Fusion.Widget.ZoomToSelection
 *
 * $Id: ZoomToSelection.js 2579 2012-09-07 09:20:12Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 /********************************************************************
 * Class: Fusion.Widget.ZoomToSelection
 *
 * Zoom to the current selection, if any
 *
 * Inherits from:
 *  - <Fusion.Widget>
 * **********************************************************************/

Fusion.Widget.ZoomToSelection = OpenLayers.Class(Fusion.Widget, {
    uiClass: Jx.Button,
    
    initializeWidget: function(widgetTag) {

        var json = widgetTag.extension;
        this.maxDimension = json.MaximumZoomDimension ? json.MaximumZoomDimension[0] : -1;
        this.zoomFactor = json.ZoomFactor ? json.ZoomFactor[0] : 2;
 
        this.enable = Fusion.Widget.ZoomToSelection.prototype.enable;
        
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_ON, OpenLayers.Function.bind(this.enable, this));
        this.getMap().registerForEvent(Fusion.Event.MAP_SELECTION_OFF, OpenLayers.Function.bind(this.disable, this));
    },

     /**
      * Function: activate
      * 
      * get the selection from the map (which may not be loaded yet).
      * zoomToSelection is called when the selection is ready.
      * 
      * Returns:
      * 
      *   return description
      */
    activate: function() {
        this.getMap().getSelection(OpenLayers.Function.bind(this.zoomToSelection, this));
    },

     /**
      * Function: zoomToSelection
      * 
      * set the extents of the map based on the pixel coordinates
      * passed
      * 
      * Parameters:
      * 
      *   selection - the active selection, or null if there is none
      * 
      */
    zoomToSelection: function(selection) {
        var aMaps = this.getMap().getAllMaps();
        var bounds = new OpenLayers.Bounds();
        for (var i=0; i<aMaps.length; ++i) {
          var mapName = aMaps[i].getMapName()
          if (selection[mapName]) {
            var ll = selection[mapName].getLowerLeftCoord();//make this an OL Pixel
            bounds.extend(new OpenLayers.LonLat(ll.x,ll.y));
            var ur = selection[mapName].getUpperRightCoord();
            bounds.extend(new OpenLayers.LonLat(ur.x,ur.y));
          }
        }
        var zoom_size = this.zoomFactor * Math.max( Math.abs(bounds.getWidth()), Math.abs(bounds.getHeight())) / 2;
        var center = bounds.getCenterLonLat();
        llx = center.lon - zoom_size;
        urx = center.lon + zoom_size;
        lly = center.lat - zoom_size;
        ury = center.lat + zoom_size;
        this.getMap().setExtents(new OpenLayers.Bounds(llx,lly,urx,ury));
    },
    
    enable: function() {
        if (this.oMap && this.oMap.hasSelection()) {
            Fusion.Widget.prototype.enable.apply(this, []);
        } else {
            this.disable();
        }
    }

});
/*
JavaScript ScaleBarTool for MapServer (scalebar.js)

Copyright (c) 2005 Tim Schaub of CommEn Space (http://www.commenspace.org)

This is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2.1 of the License, or (at
your option) any later version.

This software is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this software; if not, write to the Free Software Foundation,
Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

v1.3.1 - removed a typo that affected .sbBar with borders (thanks jlivni)
       - scalebar is now centered on .sbWrapper div by default, more css control
       - reduced likelihood of displaying very large numbers
       - added condition to deal with @import styles (thanks dokai)

*/

function ScaleBarTool(scaleDenominator) {
    // default properties
    // may be modified after construction
    // if modified after ScaleBarTool.place(), use ScaleBarTool.update()
    this.scaleDenominator = (scaleDenominator == null) ? 1 : scaleDenominator;
    this.displaySystem = 'metric'; // metric or english supported
    this.minWidth = 100; // pixels
    this.maxWidth = 200; // pixels
    this.divisions = 2;
    this.subdivisions = 2;
    this.showMinorMeasures = false;
    this.abbreviateLabel = false;
    this.singleLine = false;
    this.resolution = 72; // dpi
    this.align = 'center'; // left, center, or right supported
    // create ScaleBarTool elements
    this.container = document.createElement('div');
    this.container.className = 'sbWrapper';
    this.labelContainer = document.createElement('div');
    this.labelContainer.className = 'sbUnitsContainer';
    this.labelContainer.style.position = 'absolute';
    this.graphicsContainer = document.createElement('div');
    this.graphicsContainer.style.position = 'absolute';
    this.graphicsContainer.className = 'sbGraphicsContainer';
    this.numbersContainer = document.createElement('div');
    this.numbersContainer.style.position = 'absolute';
    this.numbersContainer.className = 'sbNumbersContainer';
    // private functions
    // put in some markers and bar pieces so style attributes can be grabbed
    // this is a solution for Safari support
    var markerMajor = document.createElement('div');
    markerMajor.className = 'sbMarkerMajor';
    this.graphicsContainer.appendChild(markerMajor);
    var markerMinor = document.createElement('div');
    markerMinor.className = 'sbMarkerMinor';
    this.graphicsContainer.appendChild(markerMinor);
    var barPiece = document.createElement('div');
    barPiece.className = 'sbBar';
    this.graphicsContainer.appendChild(barPiece);
    var barPieceAlt = document.createElement('div');
    barPieceAlt.className = 'sbBarAlt';
    this.graphicsContainer.appendChild(barPieceAlt);
}
ScaleBarTool.prototype.update = function(scaleDenominator) {
    if(scaleDenominator != null) {
        this.scaleDenominator = scaleDenominator;
    };
    // local functions (and object constructors)
    function HandsomeNumber(smallUglyNumber, bigUglyNumber, sigFigs) {
        var sigFigs = (sigFigs == null) ? 10 : sigFigs;
        var bestScore = Number.POSITIVE_INFINITY;
        var bestTieBreaker = Number.POSITIVE_INFINITY;
        // if all else fails, return a small ugly number
        var handsomeValue = smallUglyNumber;
        var handsomeNumDec = 3;
        // try the first three comely multiplicands (in order of comliness)
        for(var halvingExp = 0; halvingExp < 3; ++halvingExp) {
            var comelyMultiplicand = Math.pow(2, (-1 * halvingExp));
            var maxTensExp = Math.floor(Math.log(bigUglyNumber / comelyMultiplicand) / Math.LN10);
            for(var tensExp = maxTensExp; tensExp > (maxTensExp - sigFigs + 1); --tensExp) {
                var numDec = Math.max(halvingExp - tensExp, 0);
                var testMultiplicand = comelyMultiplicand * Math.pow(10, tensExp);
                // check if there is an integer multiple of testMultiplicand between smallUglyNumber and bigUglyNumber
                if((testMultiplicand * Math.floor(bigUglyNumber / testMultiplicand)) >= smallUglyNumber) {
                    // check if smallUglyNumber is an integer multiple of testMultiplicand
                    if(smallUglyNumber % testMultiplicand == 0) {
                        var testMultiplier = smallUglyNumber / testMultiplicand;
                    }
                    // otherwise go for the smallest integer multiple between small and big
                    else {
                        var testMultiplier = Math.floor(smallUglyNumber / testMultiplicand) + 1;
                    }
                    // test against the best (lower == better)
                    var testScore = testMultiplier + (2 * halvingExp);
                    var testTieBreaker = (tensExp < 0) ? (Math.abs(tensExp) + 1) : tensExp;
                    if((testScore < bestScore) || ((testScore == bestScore) && (testTieBreaker < bestTieBreaker))) {
                        bestScore = testScore;
                        bestTieBreaker = testTieBreaker;
                        handsomeValue = (testMultiplicand * testMultiplier).toFixed(numDec);
                        handsomeNumDec = numDec;
                    }
                }
            }
        }
        this.value = handsomeValue;
        this.score = bestScore;
        this.tieBreaker = bestTieBreaker;
        this.numDec = handsomeNumDec;
    };
    HandsomeNumber.prototype.toString = function() {
        return this.value.toString();
    };
    HandsomeNumber.prototype.valueOf = function() {
        return this.value;
    };
    function styleValue(aSelector, styleKey) {
        // returns an integer value associated with a particular selector and key
        // given a stylesheet with .someSelector {border: 2px solid red}
        // styleValue('.someSelector', 'borderWidth') returns 2
        var aValue = 0;
        if(document.styleSheets) {
            for(var sheetIndex = document.styleSheets.length - 1; sheetIndex >= 0; --sheetIndex) {
                var aSheet = document.styleSheets[sheetIndex];
                if(!aSheet.disabled) {
                    var allRules;
                    if(typeof(aSheet.cssRules) == 'undefined') {
                        if(typeof(aSheet.rules) == 'undefined') {
                            // can't get rules, assume zero
                            return 0;
                        }
                        else {
                            allRules = aSheet.rules;
                        }
                    }
                    else {
                        allRules = aSheet.cssRules;
                    }
                    for(var ruleIndex = 0; ruleIndex < allRules.length; ++ruleIndex) {
                        var aRule = allRules[ruleIndex];
                        if(aRule.selectorText && (aRule.selectorText.toLowerCase() == aSelector.toLowerCase())) {
                            if(aRule.style[styleKey] != '') {
                                aValue = parseInt(aRule.style[styleKey]);
                            }
                        }
                    }
                }
            }
        }
        // if the styleKey was not found, the equivalent value is zero
        return aValue ? aValue : 0;
    };
    function formatNumber(aNumber, numDecimals) {
        numDecimals = (numDecimals) ? numDecimals : 0;
        var formattedInteger = '' + Math.round(aNumber);
        var thousandsPattern = /(-?[0-9]+)([0-9]{3})/;
        while(thousandsPattern.test(formattedInteger)) {
            formattedInteger = formattedInteger.replace(thousandsPattern, '$1,$2');
        }
        if(numDecimals > 0) {
            var formattedDecimal = Math.floor(Math.pow(10, numDecimals) * (aNumber - Math.round(aNumber)));
            if(formattedDecimal == 0) {
                return formattedInteger;
            }
            else {
                return formattedInteger + '.' + formattedDecimal;
            }
        }
        else {
            return formattedInteger;
        }
    };
    // update the container title (for displaying scale as a tooltip)
    this.container.title = 'scale 1:' + formatNumber(this.scaleDenominator);
    // measurementProperties holds display units, abbreviations,
    // and conversion to inches (since we're using dpi) - per measurement sytem
    var measurementProperties = new Object();
    measurementProperties.english = {
        units: ['miles', 'feet', 'inches'],
        abbr: ['mi', 'ft', 'in'],
        inches: [63360, 12, 1]
    };
    measurementProperties.metric = {
        units: ['kilometers', 'meters', 'centimeters'],
        abbr: ['km', 'm', 'cm'],
        inches: [39370.07874, 39.370079, 0.393701]
    };
    // check each measurement unit in the display system
    var comparisonArray = new Array();
    for(var unitIndex = 0; unitIndex < measurementProperties[this.displaySystem].units.length; ++unitIndex) {
        comparisonArray[unitIndex] = new Object();
        var pixelsPerDisplayUnit = this.resolution * measurementProperties[this.displaySystem].inches[unitIndex] / this.scaleDenominator;
        var minSDDisplayLength = (this.minWidth / pixelsPerDisplayUnit) / (this.divisions * this.subdivisions);
        var maxSDDisplayLength = (this.maxWidth / pixelsPerDisplayUnit) / (this.divisions * this.subdivisions);
        // add up scores for each marker (even if numbers aren't displayed)
        for(var valueIndex = 0; valueIndex < (this.divisions * this.subdivisions); ++valueIndex) {
            var minNumber = minSDDisplayLength * (valueIndex + 1);
            var maxNumber = maxSDDisplayLength * (valueIndex + 1);
            var niceNumber = new HandsomeNumber(minNumber, maxNumber);
            comparisonArray[unitIndex][valueIndex] = {value: (niceNumber.value / (valueIndex + 1)), score: 0, tieBreaker: 0, numDec: 0, displayed: 0};
            // now tally up scores for all values given this subdivision length
            for(var valueIndex2 = 0; valueIndex2 < (this.divisions * this.subdivisions); ++valueIndex2) {
                displayedValuePosition = niceNumber.value * (valueIndex2 + 1) / (valueIndex + 1);
                niceNumber2 = new HandsomeNumber(displayedValuePosition, displayedValuePosition);
                var isMajorMeasurement = ((valueIndex2 + 1) % this.subdivisions == 0);
                var isLastMeasurement = ((valueIndex2 + 1) == (this.divisions * this.subdivisions));
                if((this.singleLine && isLastMeasurement) || (!this.singleLine && (isMajorMeasurement || this.showMinorMeasures))) {
                    // count scores for displayed marker measurements
                    comparisonArray[unitIndex][valueIndex].score += niceNumber2.score;
                    comparisonArray[unitIndex][valueIndex].tieBreaker += niceNumber2.tieBreaker;
                    comparisonArray[unitIndex][valueIndex].numDec = Math.max(comparisonArray[unitIndex][valueIndex].numDec, niceNumber2.numDec);
                    comparisonArray[unitIndex][valueIndex].displayed += 1;
                }
                else {
                    // count scores for non-displayed marker measurements
                    comparisonArray[unitIndex][valueIndex].score += niceNumber2.score / this.subdivisions;
                    comparisonArray[unitIndex][valueIndex].tieBreaker += niceNumber2.tieBreaker / this.subdivisions;
                }
            }
            // adjust scores so numbers closer to 1 are preferred for display
            var scoreAdjustment = (unitIndex + 1) * comparisonArray[unitIndex][valueIndex].tieBreaker / comparisonArray[unitIndex][valueIndex].displayed;
            comparisonArray[unitIndex][valueIndex].score *= scoreAdjustment;
        }
    }
    // get the value (subdivision length) with the lowest cumulative score
    var subdivisionDisplayLength = null;
    var displayUnits = null;
    var displayUnitsAbbr = null;
    var subdivisionPixelLength = null;
    var bestScore = Number.POSITIVE_INFINITY;
    var bestTieBreaker = Number.POSITIVE_INFINITY;
    var numDec = 0;
    for(var unitIndex = 0; unitIndex < comparisonArray.length; ++unitIndex) {
        for(valueIndex in comparisonArray[unitIndex]) {
            if((comparisonArray[unitIndex][valueIndex].score < bestScore) || ((comparisonArray[unitIndex][valueIndex].score == bestScore) && (comparisonArray[unitIndex][valueIndex].tieBreaker < bestTieBreaker))) {
                bestScore = comparisonArray[unitIndex][valueIndex].score;
                bestTieBreaker = comparisonArray[unitIndex][valueIndex].tieBreaker;
                subdivisionDisplayLength = comparisonArray[unitIndex][valueIndex].value;
                numDec = comparisonArray[unitIndex][valueIndex].numDec;
                displayUnits = measurementProperties[this.displaySystem].units[unitIndex];
                displayUnitsAbbr = measurementProperties[this.displaySystem].abbr[unitIndex];
                pixelsPerDisplayUnit = this.resolution * measurementProperties[this.displaySystem].inches[unitIndex] / this.scaleDenominator;
                subdivisionPixelLength = pixelsPerDisplayUnit * subdivisionDisplayLength; // round before use in style
            }
        }
    }
    // determine offsets for graphic elements
    var xOffsetMarkerMajor = (styleValue('.sbMarkerMajor', 'borderLeftWidth') + styleValue('.sbMarkerMajor', 'width') + styleValue('.sbMarkerMajor', 'borderRightWidth')) / 2;
    var xOffsetMarkerMinor = (styleValue('.sbMarkerMinor', 'borderLeftWidth') + styleValue('.sbMarkerMinor', 'width') + styleValue('.sbMarkerMinor', 'borderRightWidth')) / 2;
    var xOffsetBar = (styleValue('.sbBar', 'borderLeftWidth') + styleValue('.sbBar', 'borderRightWidth')) / 2;
    var xOffsetBarAlt = (styleValue('.sbBarAlt', 'borderLeftWidth') + styleValue('.sbBarAlt', 'borderRightWidth')) / 2;
    // support for browsers without the Document.styleSheets property (Opera)
    if(!document.styleSheets) {
        // this is a two part hack, one for the offsets here and one for the css below
        xOffsetMarkerMajor = 0.5;
        xOffsetMarkerMinor = 0.5;
    }
    // clean out any old content from containers
    while(this.labelContainer.hasChildNodes()) {
        this.labelContainer.removeChild(this.labelContainer.firstChild);
    }
    while(this.graphicsContainer.hasChildNodes()) {
        this.graphicsContainer.removeChild(this.graphicsContainer.firstChild);
    }
    while(this.numbersContainer.hasChildNodes()) {
        this.numbersContainer.removeChild(this.numbersContainer.firstChild);
    }
    // create all divisions
    var aMarker, aBarPiece, numbersBox, xOffset;
    var alignmentOffset = {
        left: 0,
        center: (-1 * this.divisions * this.subdivisions * subdivisionPixelLength / 2),
        right: (-1 * this.divisions * this.subdivisions * subdivisionPixelLength)
    };
    var xPosition = 0 + alignmentOffset[this.align];
    var markerMeasure = 0;
    for(var divisionIndex = 0; divisionIndex < this.divisions; ++divisionIndex) {
        // set xPosition and markerMeasure to start of division
        xPosition = divisionIndex * this.subdivisions * subdivisionPixelLength;
        xPosition += alignmentOffset[this.align];
        markerMeasure = (divisionIndex == 0) ? 0 : ((divisionIndex * this.subdivisions) * subdivisionDisplayLength).toFixed(numDec);
        // add major marker
        aMarker = document.createElement('div');
        aMarker.className = 'sbMarkerMajor';
        aMarker.style.position = 'absolute';
        aMarker.style.overflow = 'hidden';
        aMarker.style.left = Math.round(xPosition - xOffsetMarkerMajor) + 'px';
        aMarker.appendChild(document.createTextNode(' '));
        this.graphicsContainer.appendChild(aMarker);
        // add initial measure
        if(!this.singleLine) {
            numbersBox = document.createElement('div');
            numbersBox.className = 'sbNumbersBox';
            numbersBox.style.position = 'absolute';
            numbersBox.style.overflow = 'hidden';
            numbersBox.style.textAlign = 'center';
            if(this.showMinorMeasures) {
                numbersBox.style.width = Math.round(subdivisionPixelLength * 2) + 'px';
                numbersBox.style.left = Math.round(xPosition - subdivisionPixelLength) + 'px';
            }
            else {
                numbersBox.style.width = Math.round(this.subdivisions * subdivisionPixelLength * 2) + 'px';
                numbersBox.style.left = Math.round(xPosition - (this.subdivisions * subdivisionPixelLength)) + 'px';
            }
            numbersBox.appendChild(document.createTextNode(markerMeasure));
            this.numbersContainer.appendChild(numbersBox);
        }
        // create all subdivisions
        for(var subdivisionIndex = 0; subdivisionIndex < this.subdivisions; ++subdivisionIndex) {
            aBarPiece = document.createElement('div');
            aBarPiece.style.position = 'absolute';
            aBarPiece.style.overflow = 'hidden';
            aBarPiece.style.width = Math.round(subdivisionPixelLength) + 'px';
            if((subdivisionIndex % 2) == 0) {
                aBarPiece.className = 'sbBar';
                aBarPiece.style.left = Math.round(xPosition - xOffsetBar) + 'px';
            }
            else {
                aBarPiece.className = 'sbBarAlt';
                aBarPiece.style.left = Math.round(xPosition - xOffsetBarAlt) + 'px';
            }
            aBarPiece.appendChild(document.createTextNode(' '));
            this.graphicsContainer.appendChild(aBarPiece);
            // add minor marker if not the last subdivision
            if(subdivisionIndex < (this.subdivisions - 1)) {
                // set xPosition and markerMeasure to end of subdivision
                xPosition = ((divisionIndex * this.subdivisions) + (subdivisionIndex + 1)) * subdivisionPixelLength;
                xPosition += alignmentOffset[this.align];
                markerMeasure = (divisionIndex * this.subdivisions + subdivisionIndex + 1) * subdivisionDisplayLength;
                aMarker = document.createElement('div');
                aMarker.className = 'sbMarkerMinor';
                aMarker.style.position = 'absolute';
                aMarker.style.overflow = 'hidden';
                aMarker.style.left = Math.round(xPosition - xOffsetMarkerMinor) + 'px';
                aMarker.appendChild(document.createTextNode(' '));
                this.graphicsContainer.appendChild(aMarker);
                if(this.showMinorMeasures && !this.singleLine) {
                    // add corresponding measure
                    numbersBox = document.createElement('div');
                    numbersBox.className = 'sbNumbersBox';
                    numbersBox.style.position = 'absolute';
                    numbersBox.style.overflow = 'hidden';
                    numbersBox.style.textAlign = 'center';
                    numbersBox.style.width = Math.round(subdivisionPixelLength * 2) + 'px';
                    numbersBox.style.left = Math.round(xPosition - subdivisionPixelLength) + 'px';
                    numbersBox.appendChild(document.createTextNode(markerMeasure));
                    this.numbersContainer.appendChild(numbersBox);
                }
            }
        }
    }
    // set xPosition and markerMeasure to end of divisions
    xPosition = (this.divisions * this.subdivisions) * subdivisionPixelLength;
    xPosition += alignmentOffset[this.align];
    markerMeasure = ((this.divisions * this.subdivisions) * subdivisionDisplayLength).toFixed(numDec);
    // add the final major marker
    aMarker = document.createElement('div');
    aMarker.className = 'sbMarkerMajor';
    aMarker.style.position = 'absolute';
    aMarker.style.overflow = 'hidden';
    aMarker.style.left = Math.round(xPosition - xOffsetMarkerMajor) + 'px';
    aMarker.appendChild(document.createTextNode(' '));
    this.graphicsContainer.appendChild(aMarker);
    // add final measure
    if(!this.singleLine) {
        numbersBox = document.createElement('div');
        numbersBox.className = 'sbNumbersBox';
        numbersBox.style.position = 'absolute';
        numbersBox.style.overflow = 'hidden';
        numbersBox.style.textAlign = 'center';
        if(this.showMinorMeasures) {
            numbersBox.style.width = Math.round(subdivisionPixelLength * 2) + 'px';
            numbersBox.style.left = Math.round(xPosition - subdivisionPixelLength) + 'px';
        }
        else {
            numbersBox.style.width = Math.round(this.subdivisions * subdivisionPixelLength * 2) + 'px';
            numbersBox.style.left = Math.round(xPosition - (this.subdivisions * subdivisionPixelLength)) + 'px';
        }
        numbersBox.appendChild(document.createTextNode(markerMeasure));
        this.numbersContainer.appendChild(numbersBox);
    }
    // add content to the label container
    var labelBox = document.createElement('div');
    labelBox.style.position = 'absolute';
    var labelText;
    if(this.singleLine) {
        labelText = markerMeasure;
        labelBox.className = 'sbLabelBoxSingleLine';
        labelBox.style.top = '-0.6em';
        labelBox.style.left = (xPosition + 10) + 'px';
    }
    else {
        labelText = '';
        labelBox.className = 'sbLabelBox';
        labelBox.style.textAlign = 'center';
        labelBox.style.width = Math.round(this.divisions * this.subdivisions * subdivisionPixelLength) + 'px';
        labelBox.style.left = Math.round(alignmentOffset[this.align]) + 'px';
        labelBox.style.overflow = 'hidden';
    }
    if(this.abbreviateLabel) {
        labelText += ' ' + displayUnitsAbbr;
    }
    else {
        labelText += ' ' + displayUnits;
    }
    labelBox.appendChild(document.createTextNode(labelText));
    this.labelContainer.appendChild(labelBox);
    // support for browsers without the Document.styleSheets property (Opera)
    if(!document.styleSheets) {
        // override custom css with default
        var defaultStyle = document.createElement('style');
        defaultStyle.type = 'text/css';
        var styleText = '.sbBar {top: 0px; background: #666666; height: 1px; border: 0;}';
        styleText += '.sbBarAlt {top: 0px; background: #666666; height: 1px; border: 0;}';
        styleText += '.sbMarkerMajor {height: 7px; width: 1px; background: #666666; border: 0;}';
        styleText += '.sbMarkerMinor {height: 5px; width: 1px; background: #666666; border: 0;}';
        styleText += '.sbLabelBox {top: -16px;}';
        styleText += '.sbNumbersBox {top: 7px;}';
        defaultStyle.appendChild(document.createTextNode(styleText));
        document.getElementsByTagName('head').item(0).appendChild(defaultStyle);
    }
    // append the child containers to the parent container
    this.container.appendChild(this.graphicsContainer);
    this.container.appendChild(this.labelContainer);
    this.container.appendChild(this.numbersContainer);
};
ScaleBarTool.prototype.place = function(elementId) {
    if(elementId == null) {
        document.body.appendChild(this.container);
    }
    else {
        var anElement = document.getElementById(elementId);
        if(anElement != null) {
            anElement.appendChild(this.container);
        }
    }
    this.update();
};
/**
 * Fusion.Layers.Generic
 *
 * $Id: Generic.js 1590 2008-10-10 14:01:27Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/***************************************************************************
* Class: Fusion.Layers.Generic
*
* Implements the map layer for Generic mapping services.
*/

Fusion.Layers.Generic = OpenLayers.Class(Fusion.Layers, {
    arch: 'Generic',
    sActiveLayer: null,
    selectionType: 'INTERSECTS',
    bSelectionOn: false,
    oSelection: null,
    
    initialize: function(map, mapTag, isMapWidgetLayer) {
        // console.log('Generic.initialize');
        Fusion.Layers.prototype.initialize.apply(this, arguments);
        this.registerEventID(Fusion.Event.MAP_SESSION_CREATED);
        
        if (mapTag.extension.LayerType) {
          this.layerType = mapTag.extension.LayerType[0];
        }

        this._sMapname = mapTag.layerOptions['name'] ? mapTag.layerOptions['name'] : 'generic layer';
        this._sMapTitle = this._sMapname;
        
        this.bSingleTile = mapTag.singleTile;
        
        this.minScale = mapTag.layerOptions.minScale ? mapTag.layerOptions.minScale : 1;
        this.maxScale = mapTag.layerOptions.maxScale ? mapTag.layerOptions.maxScale : 'auto';
        if (isMapWidgetLayer) {
            this.loadMap(this.sMapResourceId);            
        }
    },

    loadMap: function(resourceId) {
        this.bMapLoaded = false;

        this.triggerEvent(Fusion.Event.LAYER_LOADING);
        
        this.internalLoadMap(resourceId);
        
        //this.triggerEvent(Fusion.Event.LAYER_LOADED);
        window.setTimeout(OpenLayers.Function.bind(this.asyncTrigger, this),1);
    },

    internalLoadMap: function(resourceId) {
        if (this.bIsMapWidgetLayer) {
          this.mapWidget._addWorker();
        }
        
        //remove this layer if it was already created
        if (this.oLayerOL) {
            this.oLayerOL.events.unregister("loadstart", this, this.loadStart);
            this.oLayerOL.events.unregister("loadend", this, this.loadEnd);
            this.oLayerOL.events.unregister("loadcancel", this, this.loadEnd);
            this.oLayerOL.destroy();
            this.oLayerOL = null;
        }
        
        if (typeof this.mapTag.layerOptions.sphericalMercator == 'undefined') {
            this.mapTag.layerOptions.sphericalMercator = true;
        }
        if (this.mapTag.layerOptions.sphericalMercator) {
          if (!this.mapTag.layerOptions.maxExtent) {
              this.mapTag.layerOptions.maxExtent = new OpenLayers.Bounds(-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892);
          }
          this.mapTag.layerOptions.units = "m";
          this.mapTag.layerOptions.projection = "EPSG:900913";
        } else {
          if (!this.mapTag.layerOptions.maxExtent) {
              this.mapTag.layerOptions.maxExtent = new OpenLayers.Bounds(-180,-90,180,90);
          }
          this.mapTag.layerOptions.units = "dd";
          this.mapTag.layerOptions.projection = "EPSG:4326";
        }
        if (typeof this.mapTag.layerOptions.numZoomLevels == 'undefined') {
            this.mapTag.layerOptions.numZoomLevels = 20;
        }
    

        switch (this.layerType) {
          case 'Google':
            switch (this.mapTag.layerOptions.type) {   //Google layer types are actual objects
              case 'G_PHYSICAL_MAP':              //defined by gmap, not a string
              case 'TERRAIN':
                this.mapTag.layerOptions.type = google.maps.MapTypeId.TERRAIN;
                break;
              case 'G_HYBRID_MAP':
              case 'HYBRID':
                this.mapTag.layerOptions.type = google.maps.MapTypeId.HYBRID;
                break;
              case 'G_SATELLITE_MAP':
              case 'SATELLITE':
                this.mapTag.layerOptions.type = google.maps.MapTypeId.SATELLITE;
                break;
              case 'G_NORMAL_MAP':
              case 'ROADMAP':
                this.mapTag.layerOptions.type = google.maps.MapTypeId.ROADMAP;
              default:
                // For the re-loaded Google layers
                if(this.mapTag.layerOptions.type == google.maps.MapTypeId.TERRAIN)
                  this.mapTag.layerOptions.type = google.maps.MapTypeId.TERRAIN;
                else if(this.mapTag.layerOptions.type == google.maps.MapTypeId.HYBRID)
                  this.mapTag.layerOptions.type = google.maps.MapTypeId.HYBRID;
                else if(this.mapTag.layerOptions.type == google.maps.MapTypeId.SATELLITE)
                  this.mapTag.layerOptions.type = google.maps.MapTypeId.SATELLITE;
                else 
                  this.mapTag.layerOptions.type = google.maps.MapTypeId.ROADMAP;
                break;
            }
            break;
         case 'VirtualEarth':
             this.mapTag.layerOptions.animationEnabled = false;
             switch (this.mapTag.layerOptions.type) {   //VE layer types are enumerated values
               case 'Aerial':              //defined in VEMapStyle from the VE api
               case 'a':
                 this.mapTag.layerOptions.type = VEMapStyle.Aerial;
                 break;
               case 'Shaded':
               case 's':
                 this.mapTag.layerOptions.type = VEMapStyle.Shaded;
                 break;
               case 'Hybrid':
               case 'h':
                 this.mapTag.layerOptions.type = VEMapStyle.Hybrid;
                 break;
               default:
                 this.mapTag.layerOptions.type = VEMapStyle.Road;
                 break;
             }
             break;
         case 'Yahoo':
            switch (this.mapTag.layerOptions.type) {   //Yahoo is similar to google
              case 'YAHOO_MAP_SAT':              //defined by YMap, not a string
              case 'YAHOO_SAT':
                this.mapTag.layerOptions.type = YAHOO_MAP_SAT;
                break;
              case 'YAHOO_MAP_HYB':
              case 'YAHOO_HYB':
                this.mapTag.layerOptions.type = YAHOO_MAP_HYB;
                break;
              case 'YAHOO_MAP_REG':
              case "YAHOO_REG":
              default:
                this.mapTag.layerOptions.type = YAHOO_MAP_REG;
                break;
            }
            break;
         case 'OpenStreetMap':
         case 'OSM':
            if (this.mapTag.layerOptions.type) {
                this.mapTag.layerOptions.type = this.mapTag.layerOptions.type;
            }
            else {
                this.mapTag.layerOptions.type = 'Mapnik';
            }
            break;
         case 'XYZ':
            this.oLayerOL = new OpenLayers.Layer[this.layerType](
                                  this.getMapName(), 
                                  this.sMapResourceId, 
                                  this.mapTag.layerOptions );
            break;
          default:
            this.oLayerOL = new OpenLayers.Layer[this.layerType](
                                  this.getMapName(), 
                                  this.sMapResourceId, 
                                  this.mapTag.layerParams, 
                                  this.mapTag.layerOptions );

            break;
        }
       
        if (!this.oLayerOL) {
            if(this.layerType == 'OpenStreetMap' || this.layerType == 'OSM') {
                //Test OSM sub-type before falling back to OpenLayers.Layer.OSM
                if (typeof(OpenLayers.Layer.OSM[this.mapTag.layerOptions.type]) != 'undefined') { 
                    this.oLayerOL = new OpenLayers.Layer.OSM[this.mapTag.layerOptions.type](this.getMapName(), null, this.mapTag.layerOptions );
                } else {
                    this.oLayerOL = new OpenLayers.Layer.OSM(this.getMapName(), null, this.mapTag.layerOptions );
                }
            }
            else {
                this.oLayerOL = new OpenLayers.Layer[this.layerType](this.getMapName(), this.mapTag.layerOptions );
                //fractionalZoom not permitted with tiled base layers regardless
                this.mapWidget.fractionalZoom = false;
                this.mapWidget.oMapOL.setOptions({fractionalZoom: false});
            }
        }
        
        this.oLayerOL.events.register("loadstart", this, this.loadStart);
        this.oLayerOL.events.register("loadend", this, this.loadEnd);
        this.oLayerOL.events.register("loadcancel", this, this.loadEnd);
        
        var parentGroup = null;
        var scaleRange = new Fusion.Layers.ScaleRange({
            minScale: this.minScale,
            maxScale: this.maxScale}, 
            Fusion.Constant.LAYER_RASTER_TYPE,{label:this._sMapname});
        
        rootOpts = {
          layerName: this._sMapname,
          resourceId: this.sMapResourceId,
          selectable: false,
          editable: false,
          layerTypes: [Fusion.Constant.LAYER_RASTER_TYPE],
          minScale: this.minScale,          
          maxScale: this.maxScale,
          scaleRanges: [scaleRange],
          parentGroup: this.mapWidget.layerRoot,
          displayInLegend: this.bDisplayInLegend,
          expandInLegend: this.bExpandInLegend,
          legendLabel: this._sMapname,
          uniqueId: 'layerRoot',
          visible: true,
          actuallyVisible: true
          //TODO: set other opts for group initialization as required
        };
        
        if (this.layerRoot) {
          parentGroup = this.mapWidget.layerRoot;
          var oldLayer = parentGroup.findLayerByAttribute("layerName", this.layerRoot.layerName);
          if (oldLayer)
            parentGroup.deleteLayer(oldLayer.uniqueId);
        }
        this.layerRoot = new Fusion.Layers.Layer(rootOpts,this);
        if (parentGroup) {
          parentGroup.addLayer(this.layerRoot);
        }
        
        //this is to distinguish between a regular map and an overview map
        if (this.bIsMapWidgetLayer) {
          this.mapWidget.addMap(this);
          this.mapWidget._removeWorker();
        }
    },
    
    asyncTrigger: function() {
        this.aLayers = new Array();
        this.aLayers[0] = new Fusion.Layers.Layer({
          layerName: this.getMapName(),
          legendLabel: this.getMapName(),
          displayInLegend: true,
          expandInLegend: true,
          actuallyVisible: true,
          visible: true,
          initiallyVisible: true,
          selectable: false
        }, this);
        this.bMapLoaded = true;
        this.triggerEvent(Fusion.Event.LAYER_LOADED);
    },
    
    //TBD: this function not yet converted for OL    
    reloadMap: function() {
        this.bMapLoaded = false;
        
        this.internalLoadMap(this.sResourceId);
        this.mapWidget.triggerEvent(Fusion.Event.MAP_RELOADED);
        this.drawMap();
        
        this.bMapLoaded = true;
    },
    
    drawMap: function() {
        if (!this.bMapLoaded) {
            return;
        }
        this.oLayerOL.mergeNewParams(params);
    },

    showLayer: function( layer, noDraw ) {
        this.processLayerEvents(layer, true);
        if (!noDraw) {
            this.oLayerOL.setVisibility(true);
        }
    },
    
    hideLayer: function( layer, noDraw ) {
        this.processLayerEvents(layer, false);
        if (!noDraw) {
            this.oLayerOL.setVisibility(false);
        }
    },
    
    showGroup: function( group, noDraw ) {
        this.processGroupEvents(group, true);
    },
    
    hideGroup: function( group, noDraw ) {
        this.processGroupEvents(group, false);
    },
    
    refreshLayer: function( layer ) {
        this.drawMap();
    },
    
    getLegendImageURL: function(fScale, layer, style,defaultIcon) {
      //var url = null; //TODO: provide a generic icon url 
      return defaultIcon;
    },
    
    getSessionID: function() {
        return '';
    },
    
    getLinkParams: function() {
      var queryParams = {};
      queryParams.layerType = this.layerType; //need this? and one for this.mapTag.layerOptions.type?

      return queryParams;
    }    

});
/**
 * Fusion.Layers.MapGuide
 *
 * $Id: MapGuide.js 2910 2015-05-07 13:18:03Z jng $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/***************************************************************************
* Class: Fusion.Layers.MapGuide
*
* Implements the map widget for MapGuide Open Source services.
*/

Fusion.Layers.MapGuide = OpenLayers.Class(Fusion.Layers, {
    arch: 'MapGuide',
    session: [null],
    aShowLayers: null,
    aHideLayers: null,
    aShowGroups: null,
    aHideGroups: null,
    aRefreshLayers: null,
    sActiveLayer: null,
    selectionType: 'INTERSECTS',
    bSelectionOn: false,
    oSelection: null,
    //Specifies how long to delay a map redraw (in ms) upon a layer/group toggle. 
    //This allows for rapid layer/group toggling without having a redraw made for each individual toggle
    //as long as each toggle is done within the time period defined here. If the delay is 0 or less, the
    //draw is immediate like before.
    drawDelay: 0,
    nCmsScaleTolerance: 2.0,  //When checking the scale list of a tiled map to determine if it is compatible with commercial layers, this value determines how much leeway a given scale can have to be considered equal
    bUsesCommercialLayerScaleList: false,
    //This is the CMS scale list as defined by MG Studio and interpreted by OpenLayers
    aCmsScales: [
        1128.49722, 2256.99444, 4513.98888, 9027.977761000002, 18055.95552,
        36111.91104, 72223.82208999999, 144447.6442, 288895.2884, 577790.5767000001, 
        1155581.153, 2311162.307, 4622324.614, 9244649.227, 18489298.45, 
        36978596.91, 73957193.82, 147914387.6, 295828775.3, 591657550.5
    ],
    bUseNativeServices: false,
    selectionAsOverlay: true,
    useAsyncOverlay: false,
    defaultFormat: 'PNG',
    oLayersOLTile: null,      //a list of baselayers
    oLayerOLDynamic: false,   //a layer object for tiled maps that also contains dynamic layers
    supports: {
      query: true,
      edit: true
    },
    alternateHostNames: null, //a comma-delimited list of alternate host names to use
    jsonParser: null,

    initialize: function(map, mapTag, isMapWidgetLayer) {
        // console.log('MapGuide.initialize');
        Fusion.Layers.prototype.initialize.apply(this, arguments);

        var newTheme = Fusion.getQueryParam('theme');
        if (newTheme != '') {
          this.sMapResourceId = newTheme;
          //clear the query param after it has been used once 
          Fusion.queryParams['theme'] = null;
        }
        this.jsonParser = new OpenLayers.Format.JSON();
        this.registerEventID(Fusion.Event.MAP_SESSION_CREATED);

        this.mapInfo = mapTag.mapInfo;
        this.imageFormat = mapTag.extension.ImageFormat ? mapTag.extension.ImageFormat[0] : this.defaultFormat;
        this.selectionType = mapTag.extension.SelectionType ? mapTag.extension.SelectionType[0] : 'INTERSECTS';
        this.selectionColor = mapTag.extension.SelectionColor ? mapTag.extension.SelectionColor[0] : '';
        this.selectionFormat = mapTag.extension.SelectionFormat ? mapTag.extension.SelectionFormat[0] : 'PNG';
        if (mapTag.extension.SelectionAsOverlay && mapTag.extension.SelectionAsOverlay[0] == 'false') {
          this.selectionAsOverlay = false;
        }
        if (!this.bIsMapWidgetLayer) {
          this.selectionAsOverlay = false;
        }
        
        if (mapTag.extension.DrawDelay) {
          this.drawDelay = parseInt(mapTag.extension.DrawDelay[0]);
          //console.log("Draw delay set to: " + this.drawDelay + "ms");
        }

        //add in the handler for CTRL-click actions for the map, not an overviewmap
        if (this.bIsMapWidgetLayer) {
          var ctrlClickEnabled = true;
          if (mapTag.extension.DisableCtrlClick && mapTag.extension.DisableCtrlClick[0] == 'true') {
              ctrlClickEnabled = false;
          }
          if (ctrlClickEnabled) {
            this.map = this.mapWidget.oMapOL;
            this.handler = new OpenLayers.Handler.Click(this,
                {click: OpenLayers.Function.bind(this.mouseUpCRTLClick, this)},
                {keyMask: OpenLayers.Handler.MOD_CTRL});
            this.handler.activate();
            this.nTolerance = 2; //pixels, default pixel tolernace for a point click; TBD make this configurable
          }
        }
        
        //Store the list of alternate host names
        if (mapTag.layerOptions.AlternateHostNames) {
            this.alternateHostNames = mapTag.layerOptions.AlternateHostNames;
        }
        
        rootOpts = {
          displayInLegend: this.bDisplayInLegend,
          expandInLegend: this.bExpandInLegend,
          legendLabel: this._sMapname,
          uniqueId: 'layerRoot',
          groupName: 'layerRoot',
          visible: true,
          actuallyVisible: true
          //TODO: set other opts for group initialization as required
        };
        this.layerRoot = new Fusion.Layers.Group(rootOpts,this);

        this.keepAliveInterval = parseInt(mapTag.extension.KeepAliveInterval ? mapTag.extension.KeepAliveInterval[0] : 300);
        this.noCache = true;
        this.oLayersOLTile = [];
        
        if (Fusion.siteVersion) {
            this.siteVersion = Fusion.siteVersion;
            this.checkNativeServiceSupport();
        }
        var sid = Fusion.sessionId;
        if (sid) {
            this.session[0] = sid;
            this.mapSessionCreated();
        } else {
            this.createSession();
        }
    },

    createSession: function() {
        if (!this.session[0]) {
            this.session[0] = this;
            var sl = Fusion.getScriptLanguage();
            var scriptURL = 'layers/' + this.arch + '/' + sl + '/CreateSession.' + sl;
            var options = {onSuccess: OpenLayers.Function.bind(this.createSessionCB, this)};
            Fusion.ajaxRequest(scriptURL, options);
        }
        if (this.session[0] instanceof Fusion.Layers.MapGuide) {
            // console.log('register for event');
            this.session[0].registerForEvent(Fusion.Event.MAP_SESSION_CREATED,
                OpenLayers.Function.bind(this.mapSessionCreated, this));
        } else {
            this.mapSessionCreated();
        }
    },

    checkNativeServiceSupport: function() {
        //NOTE: Using native services may cause a slight (but not too much) delay in any requests to PHP scripts 
        //that use layer property mappings as they will be lazy loaded due to us not calling LoadMap.php, which 
        //would've pre-cached such information. but we get much better map init performance
        this.bUseNativeServices = false;
        var vMajor = this.siteVersion[0];
        var vMinor = this.siteVersion[1];
        if (vMajor > 2) { // 3.0 or higher
            this.bUseNativeServices = true;
        } else {
            if (vMajor == 2) { // 2.x
                if (vMinor >= 6) { // >= 2.6
                    this.bUseNativeServices = true;
                }
            }
        }
    },

    createSessionCB: function(xhr) {
        if (xhr.status == 200) {
            var o = Fusion.parseJSON(xhr.responseText);
            if (o.success === false) {
                Fusion.reportFatalError(o.message);
            } else {
                var version = o.siteVersion;
                var bits = version.split('.');
                this.siteVersion = new Array(parseInt(bits[0]),
                                             parseInt(bits[1]),
                                             parseInt(bits[2]),
                                             parseInt(bits[3])
                );
                this.checkNativeServiceSupport();
                this.session[0] = o.sessionId;
                
                if (!Fusion.siteVersion)
                    Fusion.siteVersion = this.siteVersion;
                if (!Fusion.sessionId)
                    Fusion.sessionId = o.sessionId;
                
                var acceptLang = o.acceptLanguage.split(',');
                //IE - en-ca,en-us;q=0.8,fr;q=0.5,fr-ca;q=0.3
                for (var i=0; i<acceptLang.length; ++i) {
                  var locale = acceptLang[i].split(";");
                  Fusion.initializeLocale(locale[0]);
                  break;
                }
                this.triggerEvent(Fusion.Event.MAP_SESSION_CREATED);
            }
        }
    },

    mapSessionCreated: function() {
        if (this.sMapResourceId != '') {
          var options = {};
          if (this.bIsMapWidgetLayer) {
            var showlayers = Fusion.getQueryParam('showlayers');
            Fusion.queryParams['showlayers'] = null;
            var hidelayers = Fusion.getQueryParam('hidelayers');
            Fusion.queryParams['hidelayers'] = null;
            var showgroups = Fusion.getQueryParam('showgroups');
            Fusion.queryParams['showgroups'] = null;
            var hidegroups = Fusion.getQueryParam('hidegroups');
            Fusion.queryParams['hidegroups'] = null;
            var options = {
              showlayers: showlayers == '' ? [] : showlayers.split(','),
              hidelayers: hidelayers == '' ? [] : hidelayers.split(','),
              showgroups: showgroups == '' ? [] : showgroups.split(','),
              hidegroups: hidegroups == '' ? [] : hidegroups.split(',')
            };
        }
          this.loadMap(this.sMapResourceId, options);
        }
        this.keepAliveTimer = window.setInterval(OpenLayers.Function.bind(this.pingServer, this), this.keepAliveInterval * 1000);
    },

    sessionReady: function() {
        return (typeof this.session[0] == 'string');
    },

    getSessionID: function() {
        return this.session[0];
    },

    calcMapName: function(resourceId, bAppendUniqueId) {
        var slIdx = resourceId.lastIndexOf("/") + 1;
        var dIdx = resourceId.lastIndexOf(".");
        var name = resourceId.substring(slIdx, dIdx);
        if (bAppendUniqueId)
            name += (new Date()).getTime();
        return name;
    },

    loadMap: function(resourceId, options) {
        this.bMapLoaded = false;
        
        if (!this.sessionReady()) {
            this.sMapResourceId = resourceId;
            return;
        }

        this.triggerEvent(Fusion.Event.LAYER_LOADING);
        this.mapWidget._addWorker();

        this._fScale = -1;

        options = options || {};

        this.aShowLayers = options.showlayers || [];
        this.aHideLayers = options.hidelayers || [];
        this.aShowGroups = options.showgroups || [];
        this.aHideGroups = options.hidegroups || [];
        this.aRefreshLayers = options.refreshlayers || [];
        this.aLayers = [];

        this.oSelection = null;
        this.aSelectionCallbacks = [];
        this._bSelectionIsLoading = false;
        
        if (this.bUseNativeServices) {
            var features = (1 | 2 | 4); //We want the whole lot
            var r = new Fusion.Lib.MGRequest.MGCreateRuntimeMap(resourceId, features, 25);
            var mapName = this.calcMapName(resourceId, true);
            r.setParams({ 
                targetMapName: mapName,
                iconFormat: "GIF"
            });
            if (this.session.length == 1)
                r.setParams({ session: this.session[0] });
            Fusion.oBroker.dispatchRequest(r, OpenLayers.Function.bind(this.onRuntimeMapCreated, this));
        } else {
            var sl = Fusion.getScriptLanguage();
            var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/LoadMap.' + sl;

            var sessionid = this.getSessionID();

            var params = {'mapid': resourceId, "session": sessionid};
            var options = {onSuccess: OpenLayers.Function.bind(this.mapLoaded,this),
                           parameters:params};
            Fusion.ajaxRequest(loadmapScript, options);
        }
    },
    /**
     * Re-shapes the CREATERUNTIMEMAP response to match the structure that our
     * existing initialization code is expecting
     */
    convertResponse: function(o) {
        var rt = o.RuntimeMap;
        
        //LoadMap.php response
        var lm = {
            backgroundColor: ("#" + rt.BackgroundColor[0].substring(2)),
            siteVersion: rt.SiteVersion[0],
            mapId: rt.MapDefinition[0],
            mapName: rt.Name[0],
            mapTitle: rt.Name[0],
            //backgroundColor: rt.BackgroundColor[0],
            metersPerUnit: rt.CoordinateSystem[0].MetersPerUnit ? parseFloat(rt.CoordinateSystem[0].MetersPerUnit[0]) : 1,
            wkt: rt.CoordinateSystem[0].Wkt ? rt.CoordinateSystem[0].Wkt[0] : "",
            epsg: rt.CoordinateSystem[0].EpsgCode ? parseInt(rt.CoordinateSystem[0].EpsgCode[0]) : 4326,
            extent: [
                parseFloat(rt.Extents[0].LowerLeftCoordinate[0].X[0]),
                parseFloat(rt.Extents[0].LowerLeftCoordinate[0].Y[0]),
                parseFloat(rt.Extents[0].UpperRightCoordinate[0].X[0]),
                parseFloat(rt.Extents[0].UpperRightCoordinate[0].Y[0])
            ],
            hasBaseMapLayers: false,
            hasDynamicLayers: false,
            FiniteDisplayScales: [],
            groups: [],
            layers: []
        };
        if (rt.FiniteDisplayScale) {
            for (var i = 0; i < rt.FiniteDisplayScale.length; i++) {
                lm.FiniteDisplayScales.push(parseFloat(rt.FiniteDisplayScale[i]));
            }
        }
        if (rt.Group) {
            for (var i = 0; i < rt.Group.length; i++) {
                var grp = rt.Group[i];
                var cg = {
                    groupName: grp.Name[0],
                    legendLabel: (grp.LegendLabel ? grp.LegendLabel[0] : ""),
                    uniqueId: grp.ObjectId[0],
                    displayInLegend: (grp.DisplayInLegend[0] == "true"),
                    expandInLegend: (grp.ExpandInLegend[0] == "true"), 
                    parentUniqueId: grp.ParentId ? grp.ParentId[0] : "",
                    visible: (grp.Visible[0] == "true"),
                    actuallyVisible: (grp.ActuallyVisible[0] == "true"),
                    isBaseMapGroup: (grp.Type[0] == "2")
                };
                if (grp.Type[0] == "2")
                    lm.hasBaseMapLayers = true;
                else
                    lm.hasDynamicLayers = true;
                lm.groups.push(cg);
            }
        }
        //LoadScaleRanges.php response
        var lsr = {
            layers: []
        };
        if (rt.Layer) {
            for (var i = 0; i < rt.Layer.length; i++) {
                var lyr = rt.Layer[i];
                var cl = {
                    uniqueId: lyr.ObjectId[0],
                    layerName: lyr.Name[0],
                    layerTypes: [],
                    resourceId: lyr.LayerDefinition[0],
                    parentGroup: lyr.ParentId ? lyr.ParentId[0] : "",
                    selectable: (lyr.Selectable[0] == "true"),
                    visible: (lyr.Visible[0] == "true"),
                    actuallyVisible: (lyr.ActuallyVisible[0] == "true"),
                    editable: false,
                    isBaseMapLayer: (lyr.Type[0] == "2"),
                    legendLabel: (lyr.LegendLabel ? lyr.LegendLabel[0] : ""),
                    displayInLegend: (lyr.DisplayInLegend[0] == "true"),
                    expandInLegend: (lyr.ExpandInLegend[0] == "true")
                };
                if (lyr.Type[0] != "2" && !lm.hasDynamicLayers)
                    lm.hasDynamicLayers = true;
                lm.layers.push(cl);
                
                var clsr = {
                    uniqueId: cl.uniqueId,
                    scaleRanges: []
                };
                
                var ltypes = {};
                
                var minScale = 1.0e10;
                var maxScale = 0;
                
                if (lyr.ScaleRange) {
                    for (var j = 0; j < lyr.ScaleRange.length; j++) {
                        var sr = lyr.ScaleRange[j];
                        var csr = {
                            isCompressed: false,
                            maxScale: sr.MaxScale[0],
                            minScale: sr.MinScale[0],
                            styles: []
                        };
                        
                        minScale = Math.min(minScale, sr.MinScale[0]);
                        maxScale = Math.max(maxScale, sr.MaxScale[0]);
                        
                        if (sr.FeatureStyle) {
                            for (var f = 0; f < sr.FeatureStyle.length; f++) {
                                var fts = sr.FeatureStyle[f];
                                for (var k = 0; k < fts.Rule.length; k++) {
                                    var rule = fts.Rule[k];
                                    var cr = {
                                        categoryIndex: k,
                                        filter: rule.Filter ? rule.Filter[0] : "",
                                        geometryType: parseInt(fts.Type[0]),
                                        legendLabel: rule.LegendLabel ? rule.LegendLabel[0] : ""
                                    };
                                    if (typeof(ltypes[cr.geometryType]) == 'undefined')
                                        ltypes[cr.geometryType] = cr.geometryType;
                                    //One single absence of an icon is enough to hint that it's compressed
                                    if (!rule.Icon) {
                                        csr.isCompressed = true;
                                    } else {
                                        cr.imageData = "data:" + rt.IconMimeType[0] + ";base64," + rule.Icon[0];
                                    }
                                    csr.styles.push(cr);
                                }
                            }
                        }
                        clsr.scaleRanges.push(csr);
                    }
                } else {
                    //For a drawing layer which doesn't have scale ranges, we need add a dummy scale range for it.
                    //Otherwise, the legend tree will not display correctly.
                    minScale = 0;
                    maxScale = 1.0e10;
                    clsr.scaleRanges.push(new Fusion.Layers.ScaleRange({
                        minScale: 0,
                        maxScale: 1.0e10}, 
                        Fusion.Constant.LAYER_DWF_TYPE, {label:lyr.layerName}));
                }

                for (var lt in ltypes)
                    cl.layerTypes.push(lt);
                
                cl.minScale = minScale;
                cl.maxScale = maxScale;
                
                lsr.layers.push(clsr);
            }
        }
        return {
            LoadMap: lm,
            LoadScaleRanges: lsr
        };
    },
    initLoadMapResponse: function(o) {
        this._sResourceId = o.mapId;
        this._sMapname = o.mapName;
        this._sMapTitle = o.mapTitle;
        
        // Fix defect that background color in overview map will affect background color in main map.
        // We'll first check if the loaded map is the one shown in main map.
        var currentMaps = this.mapWidget.mapGroup.maps;
        var isInMapWidget = false;
        for(var index = 0, len = currentMaps.length; index < len; index++) {
            var mapInMaps = currentMaps[index];
            if(mapInMaps.resourceId == this._sResourceId) {
                isInMapWidget = this;
                break;
            }
        }
        if(isInMapWidget) {
            this.mapWidget.setMetersPerUnit(o.metersPerUnit);
            this.mapWidget.setBackgroundColor(o.backgroundColor);
        }

        this.mapTag.layerOptions.maxExtent = OpenLayers.Bounds.fromArray(o.extent);

        this.layerRoot.clear();
        this.layerRoot.legendLabel = this._sMapTitle;
        this.layerRoot.displayInLegend = true;
        this.layerRoot.expandInLegend = true;

        this.parseMapLayersAndGroups(o);

        this.minScale = 1.0e10;
        this.maxScale = 0;
        for (var i=0; i<this.aLayers.length; i++) {
          this.minScale = Math.min(this.minScale, this.aLayers[i].minScale);
          this.maxScale = Math.max(this.maxScale, this.aLayers[i].maxScale);
        }
        //a scale value of 0 is undefined
        if (this.minScale <= 0) {
          this.minScale = 1.0;
        }

        for (var i=0; i<this.aShowLayers.length; i++) {
            var layer =  this.layerRoot.findLayerByAttribute('layerName', this.aShowLayers[i]);
            if (layer) {
                this.aShowLayers[i] = layer.uniqueId;
            } else {
                this.aShowLayers[i] = '';
            }
        }
        for (var i=0; i<this.aHideLayers.length; i++) {
            var layer =  this.layerRoot.findLayerByAttribute('layerName', this.aHideLayers[i]);
            if (layer) {
                this.aHideLayers[i] = layer.uniqueId;
            } else {
                this.aHideLayers[i] = '';
            }
        }

        for (var i=0; i<this.aShowGroups.length; i++) {
            var group =  this.layerRoot.findGroupByAttribute('groupName', this.aShowGroups[i]);
            if (group) {
                this.aShowGroups[i] = group.uniqueId;
            } else {
                this.aShowGroups[i] = '';
            }
        }

        for (var i=0; i<this.aHideGroups.length; i++) {
            var group =  this.layerRoot.findGroupByAttribute('groupName', this.aHideGroups[i]);
            if (group) {
                this.aHideGroups[i] = group.uniqueId;
            } else {
                this.aHideGroups[i] = '';
            }
        }

        if (o.hasBaseMapLayers && this.bIsMapWidgetLayer) {	//Use tile if there is base layer and in main map
            this.bSingleTile = false;
        }

        //set projection units and code if supplied
        var wktProj;
        if (o.wkt && o.wkt.length > 0){
            //Proj4js prefers EPSG codes over raw WKT. So if an EPSG code exists, use that over the WKT
            if (o.epsg != 0) {
                wktProj = new OpenLayers.Projection("EPSG:" + o.epsg);
                this.mapTag.layerOptions.projection = "EPSG:" + o.epsg;
            } else {
                wktProj = new OpenLayers.Projection(o.wkt);
            }
        } 
        if (!wktProj || (wktProj && wktProj.proj && !wktProj.proj.readyToUse)) {
            if (o.epsg != 0) {
                wktProj = new OpenLayers.Projection("EPSG:" + o.epsg);
                this.mapTag.layerOptions.projection = "EPSG:" + o.epsg;
            } else {
                //default to the local non-projected system if not otherwise specified
                o.wkt = "LOCAL_CS[\"Non-Earth (Meter)\",LOCAL_DATUM[\"Local Datum\",0],UNIT[\"Meter\", 1],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH]]";
                wktProj = new OpenLayers.Projection(o.wkt);
            }
        }
        //TODO: consider passing the metersPerUnit value into the framework
        //to allow for scaling that doesn't match any of the pre-canned units
        this.mapTag.layerOptions.units = Fusion.getClosestUnits(o.metersPerUnit);

        //add in scales array if supplied
        if (o.FiniteDisplayScales && o.FiniteDisplayScales.length>0) {
            this.scales = o.FiniteDisplayScales;
            this.mapWidget.fractionalZoom = false;
            this.mapWidget.oMapOL.fractionalZoom = false;
        }
            
        if (!this.bSingleTile) {
            if (o.groups.length >0) {
                var tiledLayerIndex = 0;
                this.noCache = false;
                this.mapWidget.registerForEvent(Fusion.Event.MAP_EXTENTS_CHANGED, OpenLayers.Function.bind(this.mapExtentsChanged, this));
                
                for (var i=0; i<o.groups.length; i++) {
                    if(o.groups[i].isBaseMapGroup) {
                        this.oLayersOLTile[tiledLayerIndex] = this.createOLLayer(this._sMapname + "_Tiled[" + tiledLayerIndex + "]", false, 2, false, o.groups[i].groupName);              
                        tiledLayerIndex++;
                     }
                }
            } else {
                this.bSingleTile = true;
            }
        }
 
        //remove this layer if it was already created
        if (this.oLayerOL) {
            this.oLayerOL.events.unregister("loadstart", this, this.loadStart);
            this.oLayerOL.events.unregister("loadend", this, this.loadEnd);
            this.oLayerOL.events.unregister("loadcancel", this, this.loadEnd);
            this.oLayerOL.destroy();
        }

        if (this.oLayersOLTile.length != 0) {
            this.oLayerOL = this.oLayersOLTile[this.oLayersOLTile.length-1]; // The last baselayer at the bottom.
        } else {
            this.oLayerOL = this.createOLLayer(this._sMapname, this.bSingleTile, 2, false, "");
        }
        
        if (wktProj && wktProj.proj && wktProj.proj.readyToUse) {
          this.oLayerOL.projection = wktProj;
          this.oLayerOL.projection.proj.units = this.mapTag.layerOptions.units;
        }
        this.oLayerOL.events.register("loadstart", this, this.loadStart);
        this.oLayerOL.events.register("loadend", this, this.loadEnd);
        this.oLayerOL.events.register("loadcancel", this, this.loadEnd);

        
        //remove the dynamic overlay layer if it was already created
        if (this.oLayerOL2) {
            this.oLayerOL2.destroy();
        }

        //this is to distinguish between a regular map and an overview map
        this.bMapLoaded = true;
        if (this.bIsMapWidgetLayer) {
            this.mapWidget.addMap(this);
            
            if(this.oLayersOLTile.length > 1) {
                for(var i=this.oLayersOLTile.length-2; i>=0; i--) {
                    // Workaround to make multiple baselayers display. 
                    // Openlayers only supports single baselayer.
                    this.oLayersOLTile[i].isBaseLayer = false; 
                    this.mapWidget.oMapOL.addLayer(this.oLayersOLTile[i]);
                }                               
            }
            
            //if we have a tiled map that also contains dynamic layers, we need to create
            //an additional overlay layer to render them on top of the tiles
            if(!this.bSingleTile) {
                this.oLayerOL2 = this.createOLLayer(this._sMapname + "_DynamicOverlay",true,2,true, "");
                this.mapWidget.oMapOL.addLayer(this.oLayerOL2);
                this.oLayerOL2.setVisibility(true);
            }
        }
        
        //Fix Defect: the Base Layer Group should be invisible when the "initially visiable in map" is set to false
        var i = 0;
        var j = 0;
        for(i = 0;i < this.layerRoot.groups.length; i++){  
            if(this.layerRoot.groups[i].isBaseMapGroup && !this.layerRoot.groups[i].initiallyVisible){
                for(j = 0; j<this.oLayersOLTile.length; j++) {
                    if(this.oLayersOLTile[j].params.basemaplayergroupname === this.layerRoot.groups[i].name) {
                        this.oLayersOLTile[j].setVisibility(false);
                    }
                }    
            }   
        }
    },
    /**
     * Callback function from a LoadMap.php request
     */
    mapLoaded: function(r) {
        if (r.status == 200) {
            var o = Fusion.parseJSON(r.responseText);
            this.initLoadMapResponse(o);
        }
        this.mapWidget._removeWorker();
        this.triggerEvent(Fusion.Event.LAYER_LOADED);

    },
    /**
     * Callback function from a CREATERUNTIMEMAP request
     */
    onRuntimeMapCreated: function(r) {
        if (r.status == 200) {
            var o = Fusion.parseJSON(r.responseText);
            var co = this.convertResponse(o);
            this.initLoadMapResponse(co.LoadMap);
            //Need to wait for the right event to trigger loadScaleRanges, so stash our
            //prepared result for when it comes
            this._initScaleRanges = co.LoadScaleRanges;
        }
        this.mapWidget._removeWorker();
        this.triggerEvent(Fusion.Event.LAYER_LOADED);
    },
//TBD: this function not yet converted for OL
    reloadMap: function() {

        this.mapWidget._addWorker();
        //console.log('loadMap: ' + resourceId);
        this.aShowLayers = [];
        this.aHideLayers = [];
        this.aShowGroups = [];
        this.aHideGroups = [];
        this.aRefreshLayers = [];
        this.layerRoot.clear();
        var oldLayers = $A(this.aLayers);
        this.aLayers = [];

        if (this.bUseNativeServices) {
            var features = (1 | 2 | 4); //We want the whole lot
            var r = new Fusion.Lib.MGRequest.MGDescribeRuntimeMap(this._sMapname, features, 25);
            r.setParams({
                iconFormat: "GIF",
                session: this.getSessionID()
            });
            Fusion.oBroker.dispatchRequest(r, OpenLayers.Function.bind(this.onRuntimeMapReloaded, this, oldLayers));
        } else {
            var sl = Fusion.getScriptLanguage();
            var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/LoadMap.' + sl;

            var sessionid = this.getSessionID();

            var params = {'mapname': this._sMapname, 'session': sessionid};
            var options = {
                  onSuccess: OpenLayers.Function.bind(this.mapReloaded,this,oldLayers),
                  onException: OpenLayers.Function.bind(this.reloadFailed, this),
                  parameters: params};
            Fusion.ajaxRequest(loadmapScript, options);
        }
    },

    reloadFailed: function(r) {
        this.mapWidget._removeWorker();
        Fusion.reportFatalError(OpenLayers.i18n('mapLoadError', {'error':r.transport.responseText}));
    },

    /**
     * Function: loadScaleRanges
     *
     * This function should be called after the map has loaded. It
     * loads the scsle ranges for each layer. I tis for now only
     * used by the legend widget.
     */

    loadScaleRanges: function() {
        if (this.bUseNativeServices && this._initScaleRanges) {
            this.initLoadScaleRangeResponse(this._initScaleRanges);
            delete this._initScaleRanges;
        } else {
            var sl = Fusion.getScriptLanguage();
            var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/LoadScaleRanges.' + sl;

            //IE7 or lower: No pre-caching for you!
            var preCacheIcons = !(Browser.Engine.trident4 || Browser.Engine.trident5);
            //console.log("Layer icon pre-caching enabled: " + preCacheIcons);
            var sessionid = this.getSessionID();

            var params = {'mapname': this._sMapname, "session": sessionid, "preCacheIcons": preCacheIcons};
            var options = {onSuccess: OpenLayers.Function.bind(this.scaleRangesLoaded,this),
                           parameters:params};
            Fusion.ajaxRequest(loadmapScript, options);
        }
    },

    initLoadScaleRangeResponse: function(o) {
        if (o.layers && o.layers.length > 0) {
            var iconOpt = {
                url: o.icons_url || null,
                width: o.icons_width || 16,
                height: o.icons_height || 16
            };
            for (var i=0; i<o.layers.length; i++)  {
                var oLayer = this.getLayerById(o.layers[i].uniqueId);
                if (oLayer) {
                    oLayer.scaleRanges = [];
                    for (var j=0; j<o.layers[i].scaleRanges.length; j++) {
                        var scaleRange = new Fusion.Layers.ScaleRange(o.layers[i].scaleRanges[j],
                                                                             oLayer.layerType, iconOpt);
                        oLayer.scaleRanges.push(scaleRange);
                    }
                }
            }
        }
        this.mapWidget.triggerEvent(Fusion.Event.MAP_SCALE_RANGE_LOADED);
    },

    scaleRangesLoaded: function(r)
    {
        if (r.status == 200) {
            var o = Fusion.parseJSON(r.responseText);
            this.initLoadScaleRangeResponse(o);
        }
    },
    
    onRuntimeMapReloaded: function(oldLayers, r) {
        if (r.status == 200) {
            var json = Fusion.parseJSON(r.responseText);
            var co = this.convertResponse(json);
            var o = co.LoadMap;
            this.parseMapLayersAndGroups(o);
            //Need to wait for the right event to trigger loadScaleRanges, so stash our
            //prepared result for when it comes
            this._initScaleRanges = co.LoadScaleRanges;
            for (var i=0; i<this.aLayers.length; ++i) {
              var newLayer = this.aLayers[i];
              for (var j=0; j<oldLayers.length; ++j){
                if (oldLayers[j].uniqueId == newLayer.uniqueId) {
                  newLayer.selectedFeatureCount = oldLayers[j].selectedFeatureCount;
                  newLayer.noCache = oldLayers[j].noCache;
                  break;
                }
              }
            }
            this.mapWidget.triggerEvent(Fusion.Event.MAP_RELOADED);
            this.drawMap();
        }
        this.mapWidget._removeWorker();
    },
    
//TBD: this function not yet converted for OL
    mapReloaded: function(oldLayers,r) {
        if (r.status == 200) {
            var o = Fusion.parseJSON(r.responseText);
            this.parseMapLayersAndGroups(o);
            for (var i=0; i<this.aLayers.length; ++i) {
              var newLayer = this.aLayers[i];
              for (var j=0; j<oldLayers.length; ++j){
                if (oldLayers[j].uniqueId == newLayer.uniqueId) {
                  newLayer.selectedFeatureCount = oldLayers[j].selectedFeatureCount;
                  newLayer.noCache = oldLayers[j].noCache;
                  break;
                }
              }
            }
            this.mapWidget.triggerEvent(Fusion.Event.MAP_RELOADED);
            this.drawMap();
        }
        this.mapWidget._removeWorker();
    },

    reorderLayers: function(aLayerIndex) {
        var sl = Fusion.getScriptLanguage();
        var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/SetLayers.' + sl;

        var params = {
            'mapname': this._sMapname,
            'session': this.getSessionID(),
            'layerindex': aLayerIndex.join()
        };

        var options = {
            onSuccess: OpenLayers.Function.bind(this.mapLayersReset, this, aLayerIndex),
            parameters: params};
        Fusion.ajaxRequest(loadmapScript, options);
    },

    mapLayersReset: function(aLayerIndex,r) {
      if (r.status == 200) {
        var o = Fusion.parseJSON(r.responseText);
            if (o.success) {
                var layerCopy = $A(this.aLayers);
                this.aLayers = [];
                this.aVisibleLayers = [];
          for (var i=0; i<aLayerIndex.length; ++i) {
            this.aLayers.push( layerCopy[ aLayerIndex[i] ] );
            if (this.aLayers[i].visible) {
                this.aVisibleLayers.push(this.aLayers[i].layerName);
            }
          }

                this.drawMap();
                this.triggerEvent(Fusion.Event.MAP_LAYER_ORDER_CHANGED);
            } else {
                alert(OpenLayers.i18n('setLayersError', {'error':o.layerindex}));
            }
        }
    },

    parseMapLayersAndGroups: function(o) {
        for (var i=0; i<o.groups.length; i++) {
            var group = new Fusion.Layers.Group(o.groups[i], this);
            var parent;
            if (group.parentUniqueId != '') {
                parent = this.layerRoot.findGroupByAttribute('uniqueId', group.parentUniqueId);
            } else {
                parent = this.layerRoot;
            }
            parent.addGroup(group, this.bLayersReversed);
        }

        for (var i=0; i<o.layers.length; i++) {
            var layer = new Fusion.Layers.Layer(o.layers[i], this);
            var parent;
            if (layer.parentGroup != '') {
                parent = this.layerRoot.findGroupByAttribute('uniqueId', layer.parentGroup);
            } else {
                parent = this.layerRoot;
            }
            parent.addLayer(layer, this.bLayersReversed);
            this.aLayers.push(layer);
        }
    },

    _drawMapInternal: function() {
        var params = {
            ts : (new Date()).getTime(),  //add a timestamp to prevent caching on the server
            showLayers : this.aShowLayers.length > 0 ? this.aShowLayers.toString() : null,
            hideLayers : this.aHideLayers.length > 0 ? this.aHideLayers.toString() : null,
            showGroups : this.aShowGroups.length > 0 ? this.aShowGroups.toString() : null,
            hideGroups : this.aHideGroups.length > 0 ? this.aHideGroups.toString() : null,
            refreshLayers : this.aRefreshLayers.length > 0 ? this.aRefreshLayers.toString() : null
        };

        this.aShowLayers = [];
        this.aHideLayers = [];
        this.aShowGroups = [];
        this.aHideGroups = [];
        this.aRefreshLayers = [];

        if (this.oLayerOL2) {
            this.oLayerOL2.mergeNewParams(params);
        } else {
            this.oLayerOL.mergeNewParams(params);
        }
        //console.log("Draw call completed");
        this.drawTimeout = null;
    },

    drawMap: function() {
        if (!this.bMapLoaded) {
            return;
        }
        if (this.drawDelay <= 0) { //No delay, draw immediately
            //console.log("Draw immediate");
            this._drawMapInternal();
        } else {
            //Check if we have a pending draw call and re-schedule if there is one
            if (this.drawTimeout) {
                //console.log("Delay pending draw");
                clearTimeout(this.drawTimeout);
                this.drawTimeout = null;
            }
            this.drawTimeout = setTimeout(OpenLayers.Function.bind(this._drawMapInternal, this), this.drawDelay);
            //console.log("Schedule draw in " + this.drawDelay + "ms");
        }
    },

    drawSelection: function() {
        if (this.queryLayer) {
            this.queryLayer.redraw(true);
        } else {
            this.drawMap();
        }
    },

    /**
     * Function: createOLLayer
     *
     * Returns an OpenLayers MapGuide layer object
     */
    createOLLayer: function(layerName, bSingleTile, behavior, forceAsOverlay, baselayerGroupName) {
      /* prevent the useOverlay flag based on MapGuide config element */
      this.useAsyncOverlay = Fusion.getConfigurationItem('mapguide', 'useAsyncOverlay');
      if (!this.useAsyncOverlay) {          //v2.0.1 or earlier
        this.selectionAsOverlay = false;
      }
      
      var layerOptions = {
        maxResolution: 'auto',
        useOverlay: this.selectionAsOverlay,
        useAsyncOverlay: this.useAsyncOverlay,
        ratio: this.ratio,
        transitionEffect: 'resize'
      };

      //add in scales array if supplied
      if (this.scales && this.scales.length>0) {
        layerOptions.scales = this.scales;
      }
      if (this.maxScale != Infinity) {
        layerOptions.minScale = this.maxScale;    //OL interpretation of min/max scale is reversed from Fusion
      } else {
        if (this.mapWidget.minScale) {
          layerOptions.minScale = this.mapWidget.maxScale;
        }// otherwise minscale is set automatically by OL
      }
      //only set both max and min scale when not using scales array
      if (!this.mapWidget.oMapOL.scales && !this.scales) {
        layerOptions.maxScale = this.minScale;
      }

      layerOptions.displayOutsideMaxExtent = true;
      layerOptions.singleTile = bSingleTile;
      OpenLayers.Util.extend(layerOptions, this.mapTag.layerOptions);

      var params = {};
      if ( bSingleTile ) {
        params = {        //single tile params
          session: this.getSessionID(),
          mapname: this._sMapname,
          format: this.imageFormat,
          behavior: behavior,
          clientagent: this.clientAgent
        };
        params.showLayers = this.aShowLayers.length > 0 ? this.aShowLayers.toString() : null;
        params.hideLayers = this.aHideLayers.length > 0 ? this.aHideLayers.toString() : null;
        params.showGroups = this.aShowGroups.length > 0 ? this.aShowGroups.toString() : null;
        params.hideGroups = this.aHideGroups.length > 0 ? this.aHideGroups.toString() : null;
        params.refreshLayers = this.aRefreshLayers.length > 0 ? this.aRefreshLayers.toString() : null;

        if (behavior == 5) {
          params.selectioncolor = this.selectionColor;
          params.format = this.selectionFormat;
        }
        
        if(forceAsOverlay)
        {
            layerOptions.isBaseLayer = false;
        }

      } else {
        params = {      //tiled version
          mapdefinition: this._sResourceId,
          basemaplayergroupname: baselayerGroupName, 
          session: this.getSessionID(),
          clientagent: this.clientAgent
        };
      }
      
      //Fix for IE6 PNG transparency
      if (params.format && params.format.toLowerCase().indexOf('png') >= 0) {
        layerOptions.alpha = true;
      }

      var url;
      if ( !bSingleTile && layerOptions.useHttpTile) {
        url = Fusion.getConfigurationItem('mapguide', 'tileCacheUrl');
      } else {
        url = Fusion.getConfigurationItem('mapguide', 'mapAgentUrl');
      }
      
      if (this.alternateHostNames)
      {
        var hosts = this.alternateHostNames.split(",");
        var httpIndex = url.indexOf("http://") + 7;
        if (httpIndex < 7) {
            httpIndex = url.indexOf("https://") + 8;
        }
        var proto = url.substring(0, httpIndex);
        var relIndex = url.indexOf("/", httpIndex+1);
        var relPath = url.substring(relIndex);
        
        layerOptions.alternateUrls = [];
        
        for (var i = 0; i < hosts.length; i++) {
            var altUrl = proto + hosts[i] + relPath;
            layerOptions.alternateUrls.push(altUrl);
        }
      }
      
      var oNewLayerOL = new OpenLayers.Layer.MapGuide( layerName, url, params, layerOptions );
      if (!bSingleTile) {
        if (oNewLayerOL.scales.length == this.aCmsScales.length) { 
            //NOTE: This is not a property of OpenLayers.Layer.MapGuide, it is something we've bolted on
            oNewLayerOL.bUsesCommercialLayerScaleList = false;
            for (var i = 0; i < this.aCmsScales.length; i++) {
                if (!this.scalesAreApproximate(oNewLayerOL.scales[i], this.aCmsScales[i]))
                {
                    return oNewLayerOL; //Doesn't match. Nothing more to do here
                }
            }
            oNewLayerOL.bUsesCommercialLayerScaleList = true;
            this.bUsesCommercialLayerScaleList = true;
        }
      }
      return oNewLayerOL;
    },
    
    scalesAreApproximate: function(scale1, scale2) {
        return Math.abs(scale1 - scale2) < this.nCmsScaleTolerance;
    },
    
    applyZoomOffset: function(offset) {
        //console.log("Applying zoom offset of: " + offset);
        //We need to redraw to prevent potential mismatch after switching of commerical layers
        //TODO: This is called for each commerical layer in the basemap switcher widget, do
        //redraw() calls at this point result in redundant requests?
        if (this.oLayerOL && this.oLayerOL.bUsesCommercialLayerScaleList === true) {
            this.oLayerOL.zoomOffset = offset;
            this.oLayerOL.redraw();
        }
        if (this.oLayerOL2 && this.oLayerOL2.bUsesCommercialLayerScaleList === true) {
            this.oLayerOL2.zoomOffset = offset;
            this.oLayerOL2.redraw();
        }
        if (this.oLayersOLTile) {
            for (var i = 0; i < this.oLayersOLTile.length; i++) {
                if (this.oLayersOLTile[i].bUsesCommercialLayerScaleList === true) {
                    this.oLayersOLTile[i].zoomOffset = offset;
                    this.oLayersOLTile[i].redraw();
                }
            }
        }
    },

    /**
     * Function: getLayerByName
     *
     * Returns the MapGuide layer object as identified by the layer name
     */
    getLayerByName : function(name)
    {
        var oLayer = null;
        for (var i=0; i<this.aLayers.length; i++)
        {
            if (this.aLayers[i].layerName == name)
            {
                oLayer = this.aLayers[i];
                break;
            }
        }
        return oLayer;
    },

    /**
     * Function: getLayerById
     *
     * Returns the MapGuide layer object as identified by the layer unique id
     */
    getLayerById : function(id)
    {
        var oLayer = null;
        for (var i=0; i<this.aLayers.length; i++)
        {
            if (this.aLayers[i].uniqueId == id)
            {
                oLayer = this.aLayers[i];
                break;
            }
        }
        return oLayer;
    },

    getSelectionCB: function(userFunc, r) {
      if (r.status == 200) {
          var o = Fusion.parseJSON(r.responseText);
          var oSelection = new Fusion.SelectionObject(o);
          userFunc(oSelection);
      }
    },

    /**
     * advertise a new selection is available
     */
    newSelection: function() {
        if (this.oSelection) {
            this.oSelection = null;
        }
        this.bSelectionOn = true;
        this.triggerEvent(Fusion.Event.MAP_SELECTION_ON);
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectedFeatureCount: function() {
      var total = 0;
      for (var j=0; j<this.aLayers.length; ++j) {
        total += this.aLayers[j].selectedFeatureCount;
      }
      return total;
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectedLayers: function() {
      var layers = [];
      for (var j=0; j<this.aLayers.length; ++j) {
        if (this.aLayers[j].selectedFeatureCount>0) {
          layers.push(this.aLayers[j]);
        }
      }
      return layers;
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectableLayers: function() {
      var layers = [];
      for (var j=0; j<this.aLayers.length; ++j) {
        if (this.aLayers[j].selectable) {
          layers.push(this.aLayers[j]);
        }
      }
      return layers;
    },

    /**
     * Updates the current map selection with the provided XML selection string.
     * Optionally zooms to the new selection on the map, if zoomTo is set to true.
     */
    setSelection: function (selText, zoomTo) {

        //TODO Update this.previousSelection when the selection is set using
        //this API to allow the selection to be extended with a shift-click.

        if(selText != "" && selText != null) {
            if (this.bUseNativeServices) {
                this.updateMapSelection(selText, zoomTo, false, true);
            } else {
                this.updateSelection(selText, zoomTo, false);
            }
        }
        else {
            this.clearSelection();
        }
    },

    updateSelection: function (selText, zoomTo, extendSelection) {
        this.updateMapSelection(selText, zoomTo);
        this.getSelectedFeatureProperties(selText);
    },


    getSelectedFeatureProperties: function (selText) {
      this.mapWidget._addWorker();
      var sl = Fusion.getScriptLanguage();
      var getPropertiesScript = 'layers/' + this.arch + '/' + sl  + '/GetSelectionProperties.' + sl;
      var params = {
          'mapname': this.getMapName(),
          'session': this.getSessionID(),
          'selection': selText,
          'seq': Math.random()
      };
      var options = {onSuccess: OpenLayers.Function.bind(this.processSelectedFeatureProperties, this),
                     parameters:params};
      Fusion.ajaxRequest(getPropertiesScript, options);
    },

    updateMapSelection: function (selText, zoomTo, mergeSelection, returnAttributes) {
        this.mapWidget._addWorker();
        if (this.bUseNativeServices) {
            var reqData = 4; //hyperlinks only
            if (returnAttributes == true) {
                reqData |= 1; //Attributes
            }
            //NOTE: 
            // This code path assumes our "2.6" or above MapGuide Server is assumed to have this particular 
            // issue fixed: http://trac.osgeo.org/mapguide/changeset/8288
            // This will be true for MapGuide Open Source 2.6 Final. This may not be true for AIMS 2015.
            var r = new Fusion.Lib.MGRequest.MGQueryMapFeatures2(this.getSessionID(),
                                                                 this._sMapname,
                                                                 null, //geometry
                                                                 -1, //max features
                                                                 1, //persist
                                                                 this.selectionType,
                                                                 selText,
                                                                 null,
                                                                 0, //All layers
                                                                 reqData,
                                                                 this.selectionColor,
                                                                 this.selectionImageFormat);
            var callback = OpenLayers.Function.bind(this.onNativeSelectionUpdate, this, zoomTo, returnAttributes);
            Fusion.oBroker.dispatchRequest(r, callback);
        } else {
            var sl = Fusion.getScriptLanguage();
            var updateSelectionScript = 'layers/' + this.arch + '/' + sl  + '/SaveSelection.' + sl;
            var params = {
              'mapname': this.getMapName(),
              'session': this.getSessionID(),
              'selection': selText,
              'seq': Math.random(),
              'getextents' : zoomTo ? 'true' : 'false'
            };
            var options = {onSuccess: OpenLayers.Function.bind(this.renderSelection, this, zoomTo),
                         parameters:params};
            Fusion.ajaxRequest(updateSelectionScript, options);
        }
    },



     /**
     * asynchronously load the current selection.  When the current
     * selection changes, the selection is not loaded because it
     * could be a lengthy process.  The user-supplied function will
     * be called when the selection is available.
     *
     * @param userFunc {Function} a function to call when the
     *        selection has loaded
     *
     * @param layers {string} Optional parameter.  A comma separated
     *        list of layer names (Roads,Parcels). If it is not
     *        given, all the layers that have a selection will be used
     *
     * @param startcount {string} Optional parameter.  A comma separated
     *        list of a statinh index and the number of features to be retured for
     *        each layer given in the layers parameter. Index starts at 0
     *        (eg: 0:4,2:6 : return 4 elements for the first layers starting at index 0 and
     *         six elements for layer 2 starting at index 6). If it is not
     *        given, all the elemsnts will be returned.
     */
    getSelection: function(userFunc, layers, startcount) {
        /*for now always go back to server to fetch selection */
        if (userFunc)
        {
            if (this.previousAttributes) {
                userFunc(new Fusion.SelectionObject(this.previousAttributes));
            } else {
                //this.aSelectionCallbacks.push(userFunc);
                //this.mapWidget._addWorker();
                // this._bSelectionIsLoading = true;
                var s = 'layers/' + this.arch + '/' + Fusion.getScriptLanguage() + "/Selection." + Fusion.getScriptLanguage() ;
                var options = {
                    parameters: {
                        'session': this.getSessionID(),
                        'mapname': this._sMapname,
                        'layers': layers,
                        'startcount': startcount
                    },
                    onSuccess: OpenLayers.Function.bind(this.getSelectionCB, this, userFunc)
                };
                Fusion.ajaxRequest(s, options);
            }
        }
    },

    /**
       Call back function when selection is cleared
    */
    selectionCleared: function()
    {
        //clear the selection count for the layers
        for (var j=0; j<this.aLayers.length; ++j) {
          this.aLayers[j].selectedFeatureCount = 0;
        }

        this.bSelectionOn = false;
        if (this.queryLayer) {
          this.queryLayer.setVisibility(false);
        }
        this.triggerEvent(Fusion.Event.MAP_SELECTION_OFF);
        this.drawMap();
        this.oSelection = null;
    },

    /**
       Utility function to clear current selection
    */
    clearSelection: function() {
      if (this.hasSelection()) {
          var s = 'layers/' + this.arch + '/' + Fusion.getScriptLanguage() + "/ClearSelection." + Fusion.getScriptLanguage() ;
          var options = {
              parameters: {'session': this.getSessionID(),
                          'mapname': this._sMapname},
              onSuccess: OpenLayers.Function.bind(this.selectionCleared, this)
          };
          Fusion.ajaxRequest(s, options);
      }
      if (this.previousSelection != null)
      {
          this.previousSelection.clear();
      }
    },

    /**
       removes the queryLayer from the map
    */
    removeQueryLayer: function() {
      if (this.queryLayer) {
        this.queryLayer.destroy();
        this.queryLayer = null;
      }
    },

    /**
       Call back function when select functions are called (eg queryRect)
    */
    processQueryResults: function(zoomTo, r) {
        this.renderSelection(zoomTo, r);
        this.processSelectedFeatureProperties(r);
    },

    processSelectedFeaturePropertiesNode: function(oNode) {
        if (oNode.hasSelection) {
            this.newSelection();
        } else {
            this.clearSelection();
        }
    },

    /**
       Call back function when select functions are called (eg queryRect)
       to handle feature attributes
    */
    processSelectedFeatureProperties: function(r) {
        this.mapWidget._removeWorker();
        if (r.responseText) {   //TODO: make the equivalent change to MapServer.js
            var oNode = Fusion.parseJSON(r.responseText);
            this.processSelectedFeaturePropertiesNode(oNode);
        }
    },

    onNativeSelectionUpdate: function(zoomTo, returnAttributes, r) {
        //Set up the expected response text for renderSelection()
        if (returnAttributes) { //This update requires a client-side update of selection and attribute info
            var o = Fusion.parseJSON(r.responseText);
            var sel = new Fusion.SimpleSelectionObject(o);
            var attributes = this.convertExtendedFeatureInfo(o);
            this.previousSelection = sel;
            this.previousAttributes = attributes;
        }
        var sel = this.previousSelection;
        var resp = {
            hasSelection: false,
            layers: [],
            extents: null
        };
        for (var i = 0; i < sel.getNumLayers(); i++) {
            var layer = sel.getLayer(i);
            var nFeatures = layer.getNumFeatures();
            if (nFeatures > 0) {
                resp.hasSelection = true;
                var layerId = layer.getName();
                var oLayer = this.getLayerById(layerId);
                resp.layers.push(oLayer.layerName);
                resp[oLayer.layerName] = {
                    featureCount: nFeatures
                };
            }
        }
        if (this.previousAttributes) {
            resp.extents = this.previousAttributes.extents;
        }
        r.responseText = this.jsonParser.write(resp);
        this.renderSelection(zoomTo, r);
        this.processSelectedFeaturePropertiesNode(this.previousAttributes);
    },

    /**
       Call back function when select functions are called (eg queryRect)
       to render the selection
    */
    renderSelection: function(zoomTo, r) {
        this.mapWidget._removeWorker();
        if (r.responseText) {   //TODO: make the equivalent change to MapServer.js
            var oNode = Fusion.parseJSON(r.responseText);

            if (oNode.hasSelection) {
              if (this.selectionAsOverlay) {
                if (!this.queryLayer) {
                  this.queryLayer = this.createOLLayer("query layer", true, 5, true, "");
                  this.mapWidget.oMapOL.addLayer(this.queryLayer);
                  this.mapWidget.registerForEvent(Fusion.Event.MAP_LOADING,
                        OpenLayers.Function.bind(this.removeQueryLayer, this));
                } else {
                  this.queryLayer.setVisibility(true);
                }
              }

              //Fix Ticket #1145.
              //When the user invokes the setSelection() function to update the selection,
              //clear the selection count for all layers before proceeding
              for (var j=0; j<this.aLayers.length; ++j) {
                this.aLayers[j].selectedFeatureCount = 0;
              }

              // set the feature count on each layer making up this map
              for (var i=0; i<oNode.layers.length; ++i) {
                var layerName = oNode.layers[i];
                for (var j=0; j<this.aLayers.length; ++j) {
                  if (layerName == this.aLayers[j].layerName) {
                    this.aLayers[j].selectedFeatureCount = oNode[layerName].featureCount;
                  }
                }
              }

              if (zoomTo) {
                var ext = oNode.extents;
                if (ext != null) {
                  var extents = new OpenLayers.Bounds(ext.minx, ext.miny, ext.maxx, ext.maxy);
                  this.mapWidget.setExtents(extents);
                }
              }
              this.drawSelection();
            } else {
              this.clearSelection();
              return;
            }
        }
    },

    /**
       Do a query on the map
    */
    query: function(options) {
        this.mapWidget._addWorker();

        //clear the selection count for the layers
        for (var j=0; j<this.aLayers.length; ++j) {
          this.aLayers[j].selectedFeatureCount = 0;
        }

        var persist = 1;
        var layerAttributeFilter = 3;
        var maxFeatures = options.maxFeatures;
        if(maxFeatures == null || maxFeatures == 0)
        {
            maxFeatures = -1;
        }
        if(options.filter == null)
        {
            options.filter = '';
        }
        
        if (this.bUseNativeServices) {
            var reqData = 1; //attributes
            //TODO: Can't use inline selection image yet as we'll have to modify OpenLayers to accept an inline selection
            //over doing a GETDYNAMICMAPOVERLAYIMAGE request. When we can, or the value of 2 into the mask for inline 
            //selection as well
            var r = new Fusion.Lib.MGRequest.MGQueryMapFeatures2(this.getSessionID(),
                                                                this._sMapname,
                                                                options.geometry,
                                                                maxFeatures,
                                                                persist,
                                                                options.selectionType || this.selectionType,
                                                                options.filter,
                                                                options.layers,
                                                                layerAttributeFilter,
                                                                reqData,
                                                                this.selectionColor,
                                                                this.selectionImageFormat);
            var callback = (options.extendSelection == true) ? OpenLayers.Function.bind(this.processAndMergeExtendedFeatureInfo, this) : OpenLayers.Function.bind(this.processExtendedFeatureInfo, this);
            Fusion.oBroker.dispatchRequest(r, callback);
        } else {
            var r = new Fusion.Lib.MGRequest.MGQueryMapFeatures(this.getSessionID(),
                                                                this._sMapname,
                                                                options.geometry,
                                                                maxFeatures,
                                                                persist,
                                                                options.selectionType || this.selectionType,
                                                                options.filter,
                                                                options.layers,
                                                                layerAttributeFilter);
            var callback = (options.extendSelection == true) ? OpenLayers.Function.bind(this.processAndMergeFeatureInfo, this) : OpenLayers.Function.bind(this.processFeatureInfo, this);
            Fusion.oBroker.dispatchRequest(r, callback);
            //Uncomment below lines (and comment line above) if QUERYMAPFEATURES does not support JSON output (http://trac.osgeo.org/mapguide/ticket/2090)
            //Fusion.oBroker.dispatchRequest(r, 
            //    OpenLayers.Function.bind(Fusion.xml2json, this, 
            //        callback));
        }
    },

    showLayer: function( layer, noDraw ) {
        this.processLayerEvents(layer, true);
        this.aShowLayers.push(layer.uniqueId);
        //A layer cannot be both hidden and shown, which can be the case if there is a draw
        //delay and the user has toggled on/off the same layer in quick succession before the
        //delay has elapsed
        if (this.drawDelay > 0) {
            this.aHideLayers.erase(layer.uniqueId);
        }
        if (!noDraw) {
            this.drawMap();
        }
    },

    hideLayer: function( layer, noDraw ) {
        this.processLayerEvents(layer, false);
        this.aHideLayers.push(layer.uniqueId);
        //A layer cannot be both hidden and shown, which can be the case if there is a draw
        //delay and the user has toggled on/off the same layer in quick succession before the
        //delay has elapsed
        if (this.drawDelay > 0) {
            this.aShowLayers.erase(layer.uniqueId);
        }
        if (!noDraw) {
            this.drawMap();
        }
    },

    showGroup: function( group, noDraw ) {
        this.processGroupEvents(group, true);
        if (group.groupName == 'layerRoot') {
            this.oLayerOL.setVisibility(true);
            if (this.oLayerOL2) this.oLayerOL2.setVisibility(true);
        } else if (group.isBaseMapGroup) {
            for(var i=0; i<this.oLayersOLTile.length; i++) {
                if(this.oLayersOLTile[i].params.basemaplayergroupname == group.name) {
                    this.oLayersOLTile[i].setVisibility(true);
                }
            }
        } else {
            this.aShowGroups.push(group.uniqueId);
            //A group cannot be both hidden and shown, which can be the case if there is a draw
            //delay and the user has toggled on/off the same group in quick succession before the
            //delay has elapsed
            if (this.drawDelay > 0) {
                this.aHideGroups.erase(group.uniqueId);
            }
            if (!noDraw) {
                this.drawMap();
            }
        }
    },
    hideGroup: function( group, noDraw ) {
        this.processGroupEvents(group, false);
        if (group.groupName == 'layerRoot') {
            this.oLayerOL.setVisibility(false);
            if (this.oLayerOL2) this.oLayerOL2.setVisibility(false);
        } else if (group.isBaseMapGroup) {
            for(var i=0; i<this.oLayersOLTile.length; i++) {
                if(this.oLayersOLTile[i].params.basemaplayergroupname == group.name) {
                    this.oLayersOLTile[i].setVisibility(false);
                }
            }
        } else {
            this.aHideGroups.push(group.uniqueId);
            //A group cannot be both hidden and shown, which can be the case if there is a draw
            //delay and the user has toggled on/off the same group in quick succession before the
            //delay has elapsed
            if (this.drawDelay > 0) {
                this.aShowGroups.erase(group.uniqueId);
            }
            if (!noDraw) {
                this.drawMap();
            }
        }
    },
    refreshLayer: function( layer ) {
        this.aRefreshLayers.push(layer.uniqueId);
        this.drawMap();
    },

    /**
     * called when there is a click on the map holding the CTRL key: query features at that postion.
     **/
    mouseUpCRTLClick: function(evt) {
      if (evt.ctrlKey) {
        var min = this.mapWidget.pixToGeo(evt.xy.x-this.nTolerance, evt.xy.y-this.nTolerance);
        var max = this.mapWidget.pixToGeo(evt.xy.x+this.nTolerance, evt.xy.y+this.nTolerance);
        if (!min) {
          return;
        }
        var sGeometry = 'POLYGON(('+ min.x + ' ' +  min.y + ', ' +  min.x + ' ' +  max.y + ', ' + max.x + ' ' +  max.y + ', ' + max.x + ' ' +  min.y + ', ' + min.x + ' ' +  min.y + '))';
        //var sGeometry = 'POINT('+ min.x + ' ' +  min.y + ')';

        var maxFeatures = 1;
        var persist = 0;
        var selection = 'INTERSECTS';
        var filter = '';
        var layerNames = '';
        var layerAttributeFilter = 3;
        var sep = '';
        for (var i=0; i<this.aLayers.length; ++i) {
          layerNames += sep + this.aLayers[i].layerName;
          sep = ',';
        }
        var r = new Fusion.Lib.MGRequest.MGQueryMapFeatures(this.getSessionID(),
                                                            this._sMapname,
                                                            sGeometry,
                                                            maxFeatures, persist, selection, filter, layerNames,
                                                            layerAttributeFilter);
        var callback = OpenLayers.Function.bind(this.crtlClickDisplay, this);
        Fusion.oBroker.dispatchRequest(r, callback);
      }
    },

    /**
     * open a window if a URL is defined for the feature.
     **/
    crtlClickDisplay: function(xhr) {
        //console.log('ctrlclcik  _display');
        if (xhr.status == 200) {
            var o = Fusion.parseJSON(xhr.responseText);
            var h = o['FeatureInformation']['Hyperlink'];
            if (h) {
                window.open(h[0], "");
            }
        }
    },

    //GETVISIBLEMAPEXTENT must be called for tiled maps whenever the extents
    //are changed so that tooltips will work properly
    mapExtentsChanged: function() {
      if (!this.singleTile) {
          var center = this.mapWidget.oMapOL.getCenter();
          var display = this.mapWidget.oMapOL.getSize();
          
          var r = new Fusion.Lib.MGRequest.MGGetVisibleMapExtent(this.getSessionID(),
                                                              this._sMapname,
                                                              center.lon, center.lat,
                                                              this.mapWidget.oMapOL.getScale(),
                                                              null,
                                                              this._nDpi,
                                                              display.w, display.h);
          Fusion.oBroker.dispatchRequest(r);
      }
    },

    pingServer: function() {
        var s = 'layers/' + this.arch + '/' + Fusion.getScriptLanguage() + "/Common." + Fusion.getScriptLanguage() ;
        var params = {onSuccess: OpenLayers.Function.bind(this.checkPingResponse, this)};
        params.parameters = {'session': this.getSessionID()};
        Fusion.ajaxRequest(s, params);
    },
    
    checkPingResponse: function(xhr) {
        if (xhr.responseText) {
            var o = Fusion.parseJSON(xhr.responseText);
            if (!o.success) {
                clearInterval(this.keepAliveTimer);
                Fusion.reportFatalError(o.message);
            }
        }
    },

    getLinkParams: function() {
      var queryParams = {};
      queryParams.theme = this.sMapResourceId;

      //determine which layers have been toggled
      var showLayers = [];
      var hideLayers = [];
      for (var i=0; i<this.aLayers.length; ++i) {
        var layer = this.aLayers[i];
        if (layer.visible && !layer.initiallyVisible) {  //layer was turned on
          showLayers.push(layer.layerName);
        }
        if (!layer.visible && layer.initiallyVisible) {  //layer was turned off
          hideLayers.push(layer.layerName);
        }
      }
      queryParams.showlayers = showLayers.join(',');
      queryParams.hidelayers = hideLayers.join(',');

      //determine which groups have been toggled
      var showGroups = [];
      var hideGroups = [];
      for (var i=0; i<this.layerRoot.groups.length; ++i) {
        var group = this.layerRoot.groups[i];
        if (group.visible && !group.initiallyVisible) {  //layer was turned on
          showGroups.push(group.groupName);
        }
        if (!group.visible && group.initiallyVisible) {  //layer was turned off
          hideGroups.push(group.groupName);
        }
      }
      queryParams.showgroups = showGroups.join(',');
      queryParams.hidegroups = hideGroups.join(',');

      return queryParams;
    },
    
    getMapTip: function(mapTipWidget) {
      //console.log('showMaptip');
        var oBroker = Fusion.oBroker;
        var x = mapTipWidget.oCurrentPosition.x;
        var y = mapTipWidget.oCurrentPosition.y;
        var min = this.mapWidget.pixToGeo(x-mapTipWidget.nTolerance, y-mapTipWidget.nTolerance);
        var max = this.mapWidget.pixToGeo(x+mapTipWidget.nTolerance, y+mapTipWidget.nTolerance);
        //this can fail if no map is loaded
        if (!min) {
            return;
        }
        var sGeometry = 'POLYGON(('+ min.x + ' ' +  min.y + ', ' +  min.x + ' ' +  max.y + ', ' + max.x + ' ' +  max.y + ', ' + max.x + ' ' +  min.y + ', ' + min.x + ' ' +  min.y + '))';

        //var sGeometry = 'POINT('+ min.x + ' ' +  min.y + ')';

        var maxFeatures = 1;
        var persist = 0;
        var selection = 'INTERSECTS';
        var filter = '';
        // only select visible layers with maptips defined (1+4)
        var layerAttributeFilter = 5;
        //TODO: possibly make the layer names configurable?
        var layerNames = mapTipWidget.aLayers.toString();
        if (this.bUseNativeServices) {
            var reqData = (4 | 8); //Tooltips and hyperlinks
            var r = new Fusion.Lib.MGRequest.MGQueryMapFeatures2(this.getSessionID(),
                                            this._sMapname,
                                            sGeometry,
                                            maxFeatures, persist, selection, filter, layerNames,
                                            layerAttributeFilter,
                                            reqData,
                                            this.selectionColor,
                                            this.selectionImageFormat);
            oBroker.dispatchRequest(r, OpenLayers.Function.bind(this.parseMapTip, this));
        } else {
            var r = new Fusion.Lib.MGRequest.MGQueryMapFeatures(this.getSessionID(),
                                            this._sMapname,
                                            sGeometry,
                                            maxFeatures, persist, selection, filter, layerNames,
                                            layerAttributeFilter);
            oBroker.dispatchRequest(r, OpenLayers.Function.bind(this.parseMapTip, this));
            //Uncomment below lines (and comment line above) if QUERYMAPFEATURES does not support JSON output (http://trac.osgeo.org/mapguide/ticket/2090)
            //oBroker.dispatchRequest(r, 
            //    OpenLayers.Function.bind(Fusion.xml2json, this, 
            //        OpenLayers.Function.bind(this.parseMapTip, this)));
        }
    },
    
    parseMapTip: function(xhr) {
        var o;
        var tooltip = Fusion.parseJSON(xhr.responseText);
        this.oMaptip = {t:"",h:""};
        var t = tooltip['FeatureInformation']['Tooltip'];
        if (t) {
          this.oMaptip.t = t[0].replace(/\\n/g, "<br>");
        }
        var h = tooltip['FeatureInformation']['Hyperlink'];
        if (h) {
          this.oMaptip.h = h[0];
        }
        this.mapWidget.triggerEvent(Fusion.Event.MAP_MAPTIP_REQ_FINISHED, this.oMaptip);
    },
    
    getLegendImageURL: function(fScale, layer, style,defaultIcon) {
        if(layer.layerTypes[0] == 4){
            return defaultIcon;
        }
        else
        {
            if (style.iconOpt && style.iconOpt.url)
            {
                //if (style.iconOpt.url.indexOf("data:image") >= 0)
                //    console.log("Fetching pre-cached icon");
                return style.iconOpt.url;
            }
                
            var origUrl = Fusion.getConfigurationItem('mapguide', 'mapAgentUrl');
            var altUrl = null;
            if (this.oLayerOL && this.oLayerOL.alternateUrls && this.oLayerOL.alternateUrls.length > 0) {
                altUrl = this.oLayerOL.getNextAltURL();
            }
            var url = (altUrl == null) ? origUrl : altUrl;
            
            url += "?OPERATION=GETLEGENDIMAGE&SESSION=" + layer.oMap.getSessionID();
            url += "&VERSION=1.0.0&SCALE=" + fScale;
            op = /\(/g; cp = /\)/g; 
            url += "&LAYERDEFINITION=" + encodeURIComponent(layer.resourceId).replace(op, "%28").replace(cp, "%29");
            url += "&THEMECATEGORY=" + style.categoryIndex;
            url += "&TYPE=" + style.geometryType;
            url += "&CLIENTAGENT=" + encodeURIComponent(this.clientAgent);
            if (this.noCache) {
                url += "&TS=" + (new Date()).getTime();
            }
            return url;
        }
    },
    
    typeNameToValue: function(name) {
        switch(name) { //Values from MgPropertyType
            case "byte":
                return 2;
            case "boolean":
                return 1;
            case "blob":
                return 10;
            case "clob":
                return 11;
            case "datetime":
                return 3;
            case "double":
                return 5;
            case "int16":
                return 6;
            case "int32":
                return 7;
            case "int64":
                return 8;
            case "single":
                return 4;
            case "string":
                return 9;
        }
        return -1;
    },
    
    convertExtendedFeatureInfo: function(efi) {
        var bHasSelection = false;
        var result = {};
        var layerNames = [];
        var featuresByLayer = {};
        if (efi.FeatureInformation.SelectedFeatures) {
            var selLayers = efi.FeatureInformation.SelectedFeatures[0].SelectedLayer;
            bHasSelection = (selLayers.length > 0);
            var box = new OpenLayers.Bounds();
            
            for (var i = 0; i < selLayers.length; i++) {
                var selLayer = selLayers[i];
                var selFeatures = selLayer.Feature || [];
                var layerName = selLayer["@name"];
                if (!result[layerName]) {
                    if (selLayer.LayerMetadata) {
                        var layerMeta = selLayer.LayerMetadata[0];
                        var pnames = [];
                        var ptypes = [];
                        var pvals = [];
                        for (var j = 0; j < layerMeta.Property.length; j++) {
                            var metaProp = layerMeta.Property[j];
                            pnames.push(metaProp.Name[0]);
                            ptypes.push(metaProp.Type[0]);
                            pvals.push(metaProp.DisplayName[0]);
                        }
                    }
                    result[layerName] = {
                        metadata: [],  //NOTE: Probably a defect, but regular code path is putting blank string arrays here too
                        metadatanames: ["dimension", "bbox", "center", "area", "length"],
                        numelements: selFeatures.length,
                        propertynames: pnames,
                        propertytypes: ptypes,
                        propertyvalues: pvals,
                        values: []
                    };
                    layerNames.push(layerName);
                }
                
                for (var j = 0; j < selFeatures.length; j++) {
                    var feat = selFeatures[j];
                    var featVals = [];
                    if (feat.Property) {
                        for (var k = 0; k < feat.Property.length; k++) {
                            //Fusion represents null as empty string. Don't think that's right but we'll run with whatever
                            //the old code path produces
                            featVals.push(feat.Property[k].Value == null ? "" : feat.Property[k].Value[0]);
                        }
                    }
                    result[layerName].values.push(featVals);
                    //NOTE: Probably a defect, but regular code path is putting blank string arrays here too, so let's do the same
                    result[layerName].metadata.push(["","","","",""]);
                    if (feat.Bounds) {
                        var bounds = feat.Bounds[0].split(" "); //minx miny maxx maxy
                        box.extend(new OpenLayers.LonLat(parseFloat(bounds[0]), parseFloat(bounds[1])));
                        box.extend(new OpenLayers.LonLat(parseFloat(bounds[2]), parseFloat(bounds[3])));
                    }
                }
            }
            return OpenLayers.Util.extend(result, {
                hasSelection: bHasSelection,
                extents: {
                    minx: box.left,
                    miny: box.bottom,
                    maxx: box.right,
                    maxy: box.top
                },
                layers: layerNames
            });
        } else {
            result.hasSelection = false;
            return result;
        }
    },

    processAndMergeExtendedFeatureInfo: function(r) {
        this.processSelectedExtendedFeatureInfo(r, true);
        //this.processSelectedFeatureInfo(r, true);
    },
    
    processExtendedFeatureInfo: function(r) {
        this.processSelectedExtendedFeatureInfo(r, false);
        //this.processSelectedFeatureInfo(r, false);
    },
    
    mergeAttributes: function(attributes, prevAttributes) {
        if (!prevAttributes) {
            //Nothing to merge, return original
            return attributes;
        }
        //Start off with prevAttributes as the base
        var merged = {};
        merged.hasSelection = prevAttributes.hasSelection;
        merged.extents = prevAttributes.extents;
        merged.layers = prevAttributes.layers;
        for (var i = 0; i < merged.layers.length; i++) {
            var layerName = merged.layers[i][0];
            merged[layerName] = prevAttributes[layerName];
        }
        //Expand extents
        if (!merged.extents && attributes.extents) {
            merged.extents = attributes.extents;
        } else {
            if (attributes.extents) {
                if (attributes.extents.minx < merged.extents.minx)
                    merged.extents.minx = attributes.extents.minx;
                if (attributes.extents.miny < merged.extents.miny)
                    merged.extents.miny = attributes.extents.miny;
                if (attributes.extents.maxx > merged.extents.maxx)
                    merged.extents.maxx = attributes.extents.maxx;
                if (attributes.extents.maxy > merged.extents.maxy)
                    merged.extents.maxy = attributes.extents.maxy;
            }
        }
        //Bring in attributes
        for (var i = 0; i < attributes.layers.length; i++) {
            var layerName = attributes.layers[i][0];
            if (typeof(merged[layerName]) == 'undefined') {
                merged[layerName] = attributes[layerName];
            } else {
                var newValues = attributes[layerName].values;
                for (var v = 0; v < newValues.length; v++) {
                    merged[layerName].values.push(newValues[v]);
                    merged[layerName].numelements++;
                }
            }
        }
        return merged;
    },
    
    processSelectedExtendedFeatureInfo: function(r, mergeSelection) {
        var o = Fusion.parseJSON(r.responseText);
        var sel = new Fusion.SimpleSelectionObject(o);
        var attributes = this.convertExtendedFeatureInfo(o);
        if (mergeSelection == true)
        {
            sel.merge(this.previousSelection);
            attributes = this.mergeAttributes(attributes, this.previousAttributes);
        }
        var selText = sel.getSelectionXml();
        this.previousSelection = sel;
        //Because the QUERYMAPFEATURES 2.6.0 response contains mostly everything we need, we cut down
        //on lots of async request ping-pong. So we can just update the selection image and notify any
        //interested parties of the new selection attributes
        if (selText != "" && selText != null) {
            this.previousAttributes = attributes;
            this.updateMapSelection(selText, false, mergeSelection);
        } else {
            //Only clear if we're not merging an empty selection
            if (mergeSelection == false) {
                this.previousAttributes = null;
                this.clearSelection();
            }
        }
        this.mapWidget._removeWorker();
    },
    
    processAndMergeFeatureInfo: function (r) {
        this.processSelectedFeatureInfo(r, true);
    },

    processFeatureInfo: function (r) {
        this.processSelectedFeatureInfo(r, false);
    },

    processSelectedFeatureInfo: function (r, mergeSelection) {
        var o = Fusion.parseJSON(r.responseText);

        var newSelection = new Fusion.SimpleSelectionObject(o);
        if(mergeSelection == true)
        {
            newSelection.merge(this.previousSelection);
        }
        this.previousSelection = newSelection;

        var selText = newSelection.getSelectionXml();
        this.setSelection(selText, false);
        this.mapWidget._removeWorker();
    }

});

Fusion.SimpleSelectionObject = OpenLayers.Class({
    aLayers : null,
    nLayers : 0,

    initialize: function(featureInfoResponse)
    {
        this.aLayers = [];
        this.nLayers = 0;
        try
        {
            var layers = featureInfoResponse.FeatureInformation.FeatureSet[0].Layer;
            if (layers != null)
            {
                for(var i = 0; i < layers.length; i++)
                {
                    var layerId = layers[i]['@id'][0];

                    var classElt = layers[i]['Class'][0];
                    var className = layers[i]['Class'][0]['@id'][0];

                    var layer = new Fusion.SimpleSelectionObject.Layer(layerId, className);

                    this.addLayer(layer);

                    var features = classElt.ID;
                    for(var j=0; j < features.length; j++)
                    {
                        layer.addFeature(features[j]);
                    }
                }
            }
        }
        catch(e) {}

    },

    addLayer: function(layer)
    {
        this.aLayers[this.nLayers] = layer;
        this.nLayers++;
    },

    getNumLayers : function()
    {
        return this.nLayers;
    },

    getLayerByName : function(name)
    {
        var oLayer = null;
        for (var i=0; i<this.nLayers; i++)
        {
            if (this.aLayers[i].getName() == name)
            {
                oLayer = this.aLayers[i];
                break;
            }
        }
        return oLayer;
    },

    getLayer : function(iIndice)
    {
        if (iIndice >=0 && iIndice < this.nLayers)
        {
            return this.aLayers[iIndice];
        }
        else
        {
            return null;
        }
    },

    getSelectionXml : function()
    {
        var xmlSelection = "";
        if(this.nLayers > 0)
        {
            xmlSelection = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FeatureSet>\n";
            for(var i = 0; i < this.nLayers; i++)
            {
                var layer = this.aLayers[i];
                xmlSelection += "<Layer id=\"" + layer.getName() + "\">\n";
                xmlSelection += "<Class id=\"" + layer.getClassName() + "\">\n";
                for(var j = 0; j < layer.getNumFeatures(); j++)
                {
                    var featId = layer.featIds[j];
                    xmlSelection += "<ID>" +  featId + "</ID>\n";
                }
                xmlSelection += "</Class>\n</Layer>\n";
            }
            xmlSelection += "</FeatureSet>\n";
        }
        return xmlSelection;
    },

    merge : function(previousSelection)
    {
        if (previousSelection != null && previousSelection.nLayers > 0)
        {
            for (var prevSelIndex = 0; prevSelIndex < previousSelection.nLayers; prevSelIndex++)
            {
                var prevSelLayer = previousSelection.aLayers[prevSelIndex];

                // find the previously selected layer name in the current selection
                var currentLayer = this.getLayerByName(prevSelLayer.getName());
                if (currentLayer != null)
                {
                    // add the previously selected features for this layer
                    for (var j = 0; j < prevSelLayer.getNumFeatures(); j++)
                    {
                        var prevSelFeatureIndexes = currentLayer.featIds.find(prevSelLayer.featIds[j]);
                        if (prevSelFeatureIndexes == null)
                        {
                            currentLayer.addFeature(prevSelLayer.featIds[j]);
                        }
                        else
                        {
                            // the feature was previously selected, so toggle it off when selected again
                            currentLayer.removeFeatures(prevSelFeatureIndexes);
                        }
                    }
                    if (currentLayer.featIds.length == 0)
                    {
                        this.clear();
                    }
                }
                else
                {
                    // the current selection does not include this previously selected layer

                    // need to add this previously selected layer and its features
                    var missingLayer = new Fusion.SimpleSelectionObject.Layer(prevSelLayer.getName(), prevSelLayer.getClassName());
                    for (var k = 0; k < prevSelLayer.getNumFeatures(); k++)
                    {
                        missingLayer.addFeature(prevSelLayer.featIds[k]);
                    }
                    this.addLayer(missingLayer);
                }
            }
        }
    },

    clear: function()
    {
        this.aLayers = [];
        this.nLayers = 0;
    }
});

Fusion.SimpleSelectionObject.Layer = OpenLayers.Class({
    name: "",
    className: "",
    featIds: null,
    nFeatures: 0,

    initialize: function(layerName, className)
    {
        this.name =  layerName;
        this.className = className;
        this.nFeatures = 0;
        this.featIds =  [];
    },

    addFeature : function (featId)
    {
        this.featIds[this.nFeatures] = featId;
        this.nFeatures++;
    },

    getName : function()
    {
        return this.name;
    },

    getClassName : function()
    {
        return this.className;
    },

    getNumFeatures : function()
    {
        return this.nFeatures;
    },

    removeFeatures : function (featureIndexes)
    {
        var numIndexes = featureIndexes.length;
        for (var featIndex = 0; featIndex < numIndexes; featIndex++)
        {
            this.featIds.remove(featureIndexes[featIndex]);
            this.nFeatures--;
        }
    }
});

Array.prototype.find = function(searchStr) {
  var returnArray = null;
  for (i=0; i<this.length; i++) {
    if (typeof(searchStr) == 'function') {
      if (searchStr.test(this[i])) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(i);
      }
    } else {
      if (this[i]===searchStr) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(i);
      }
    }
  }
  return returnArray;
};

Array.prototype.remove = function(indexToRemove) {
    this.splice(indexToRemove, 1);
};/*
 csmap2proj4.js - A Mentor code to proj4 string lookup table

 This file was generated by CsMap2Proj.php
 */
Proj4js.defs["EPSG:4030"] = "+proj=longlat +ellps=WGS84 +no_defs "; //Mentor: LL
Proj4js.defs["EPSG:4152"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: LL-HPGN
Proj4js.defs["EPSG:4322"] = "+proj=longlat +ellps=WGS72 +no_defs "; //Mentor: LL72
Proj4js.defs["EPSG:4267"] = "+proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs "; //Mentor: LL27
Proj4js.defs["EPSG:4202"] = "+proj=longlat +ellps=aust_SA +no_defs "; //Mentor: LL-ASTRLA66-Grid
Proj4js.defs["EPSG:4203"] = "+proj=longlat +ellps=aust_SA +no_defs "; //Mentor: LL-AGD84-Grid
Proj4js.defs["EPSG:4283"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: LL-GDA94
Proj4js.defs["EPSG:4122"] = "+proj=longlat +a=6378135 +b=6356750.304921594 +no_defs "; //Mentor: LL-ATS77
Proj4js.defs["EPSG:4617"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: LL-CSRS
Proj4js.defs["EPSG:4230"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: LL-ERP50
Proj4js.defs["EPSG:4258"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: LL-ETRF89
Proj4js.defs["EPSG:4807"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +no_defs "; //Mentor: LL-NTF-Grid
Proj4js.defs["EPSG:4171"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: LL-RGF93
Proj4js.defs["EPSG:4612"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: LL-JGD2K-7P
Proj4js.defs["EPSG:4272"] = "+proj=longlat +ellps=intl +datum=nzgd49 +no_defs "; //Mentor: LL-GD1949-Grid
Proj4js.defs["EPSG:4135"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: LL-OLDHI
Proj4js.defs["EPSG:4124"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: RT90-3/7P.LL
Proj4js.defs["EPSG:4248"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: LL-PSAD56
Proj4js.defs["EPSG:4168"] = "+proj=longlat +a=6378300 +b=6356751.689189189 +towgs84=-199,32,322,0,0,0,0 +no_defs "; //Mentor: Accra1929.LL
Proj4js.defs["EPSG:4143"] = "+proj=longlat +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +no_defs "; //Mentor: Abidjan1987.LL
Proj4js.defs["EPSG:4201"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Adindan.LL
Proj4js.defs["EPSG:4205"] = "+proj=longlat +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +no_defs "; //Mentor: Afgooye.LL
Proj4js.defs["EPSG:4204"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: AinElAbd.LL
Proj4js.defs["EPSG:4289"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: Amersfoort/a.LL
Proj4js.defs["EPSG:4196"] = "+proj=longlat +ellps=intl +towgs84=-45,417,-3.5,0,0,0.814,-0.6 +no_defs "; //Mentor: Ammassalik58a.LL
Proj4js.defs["EPSG:4169"] = "+proj=longlat +ellps=clrk66 +towgs84=-115,118,426,0,0,0,0 +no_defs "; //Mentor: Samoa1962.LL
Proj4js.defs["EPSG:4176"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Antarctic98.LL
Proj4js.defs["EPSG:4601"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Antigua1943.LL
Proj4js.defs["EPSG:4208"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Aratu_1.LL
Proj4js.defs["EPSG:4209"] = "+proj=longlat +a=6378249.145 +b=6356514.966398753 +no_defs "; //Mentor: Arc1950.LL/01
Proj4js.defs["EPSG:4210"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Arc1960.LL
Proj4js.defs["EPSG:4712"] = "+proj=longlat +ellps=intl +towgs84=-205,107,53,0,0,0,0 +no_defs "; //Mentor: ASCENSN.LL
Proj4js.defs["EPSG:4183"] = "+proj=longlat +ellps=intl +towgs84=-104,167,-38,0,0,0,0 +no_defs "; //Mentor: AZORES.LL
Proj4js.defs["EPSG:4211"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: Batavia_1.LL
Proj4js.defs["EPSG:4214"] = "+proj=longlat +ellps=krass +no_defs "; //Mentor: Beijing1954/a.LL
Proj4js.defs["EPSG:4313"] = "+proj=longlat +ellps=intl +towgs84=106.869,-52.2978,103.724,-0.33657,0.456955,-1.84218,1 +no_defs "; //Mentor: Belge72a.LL
Proj4js.defs["EPSG:4714"] = "+proj=longlat +ellps=intl +towgs84=-127,-769,472,0,0,0,0 +no_defs "; //Mentor: BELLEVUE.LL
Proj4js.defs["EPSG:4216"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Bermuda.LL
Proj4js.defs["EPSG:4165"] = "+proj=longlat +ellps=intl +towgs84=-173,253,27,0,0,0,0 +no_defs "; //Mentor: Bissau_1.LL
Proj4js.defs["EPSG:4218"] = "+proj=longlat +ellps=intl +towgs84=307,304,-318,0,0,0,0 +no_defs "; //Mentor: Bogota.LL
Proj4js.defs["EPSG:4219"] = "+proj=longlat +ellps=bessel +towgs84=-384,664,-48,0,0,0,0 +no_defs "; //Mentor: BukitRimpah_1.LL
Proj4js.defs["EPSG:4220"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Camacupa_1.LL
Proj4js.defs["EPSG:4221"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Campo.LL
Proj4js.defs["EPSG:4728"] = "+proj=longlat +ellps=intl +towgs84=-307,-92,127,0,0,0,0 +no_defs "; //Mentor: CANARY.LL
Proj4js.defs["EPSG:4717"] = "+proj=longlat +ellps=clrk66 +towgs84=-2,151,181,0,0,0,0 +no_defs "; //Mentor: CANAVRL.LL
Proj4js.defs["EPSG:4222"] = "+proj=longlat +a=6378249.145 +b=6356514.966398753 +no_defs "; //Mentor: Cape-1.LL
Proj4js.defs["EPSG:4223"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: Carthage.LL
Proj4js.defs["EPSG:4149"] = "+proj=longlat +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +no_defs "; //Mentor: CH1903/GSB.LL
Proj4js.defs["EPSG:4150"] = "+proj=longlat +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +no_defs "; //Mentor: CH1903Plus_1.LL
Proj4js.defs["EPSG:4672"] = "+proj=longlat +ellps=intl +towgs84=175,-38,113,0,0,0,0 +no_defs "; //Mentor: CHATHAM.LL
Proj4js.defs["EPSG:4151"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: CHTRF95.LL
Proj4js.defs["EPSG:4224"] = "+proj=longlat +ellps=intl +towgs84=-134,229,-29,0,0,0,0 +no_defs "; //Mentor: Chua.LL
Proj4js.defs["EPSG:4632"] = "+proj=longlat +ellps=intl +towgs84=-382,-59,-262,0,0,0,0 +no_defs "; //Mentor: Combani1950.LL
Proj4js.defs["EPSG:4315"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-23,259,-9,0,0,0,0 +no_defs "; //Mentor: Conakry1905.LL
Proj4js.defs["EPSG:4225"] = "+proj=longlat +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +no_defs "; //Mentor: Corrego.LL
Proj4js.defs["EPSG:4623"] = "+proj=longlat +ellps=intl +towgs84=-186,230,110,0,0,0,0 +no_defs "; //Mentor: CSG1967.LL
Proj4js.defs["EPSG:4155"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-83,37,124,0,0,0,0 +no_defs "; //Mentor: Dabola1981.LL
Proj4js.defs["EPSG:4274"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Datum73a.LL
Proj4js.defs["EPSG:4316"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: DealulPiscului1933.LL
Proj4js.defs["EPSG:4227"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: DeirEzZor_2.LL
Proj4js.defs["EPSG:4314"] = "+proj=longlat +ellps=bessel +datum=potsdam +no_defs "; //Mentor: DHDN/2.LL
Proj4js.defs["EPSG:4602"] = "+proj=longlat +ellps=clrk80 +towgs84=725,685,536,0,0,0,0 +no_defs "; //Mentor: Dominica1945.LL
Proj4js.defs["EPSG:4719"] = "+proj=longlat +ellps=intl +towgs84=211,147,111,0,0,0,0 +no_defs "; //Mentor: EASTER.LL
Proj4js.defs["EPSG:4133"] = "+proj=longlat +ellps=GRS80 +towgs84=0.055,-0.541,-0.185,0.0183,-0.0003,-0.007,-0.014 +no_defs "; //Mentor: Estonia92a.LL
Proj4js.defs["EPSG:4180"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Estonia97.LL
Proj4js.defs["EPSG:4154"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Europ50/1977.LL
Proj4js.defs["EPSG:4668"] = "+proj=longlat +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs "; //Mentor: EUROP79.LL
Proj4js.defs["EPSG:4159"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: ELD1979.LL
Proj4js.defs["EPSG:4232"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Fahud_1.LL
Proj4js.defs["EPSG:4132"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Final1958.LL
Proj4js.defs["EPSG:4625"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Martinique38.LL
Proj4js.defs["EPSG:4621"] = "+proj=longlat +ellps=intl +towgs84=137,248,-430,0,0,0,0 +no_defs "; //Mentor: FortMarigot_1.LL
Proj4js.defs["EPSG:4121"] = "+proj=longlat +ellps=GRS80 +towgs84=-199.87,74.79,246.62,0,0,0,0 +no_defs "; //Mentor: GGRS87.LL
Proj4js.defs["EPSG:4603"] = "+proj=longlat +ellps=clrk80 +towgs84=72,213.7,93,0,0,0,0 +no_defs "; //Mentor: Grenada1953.LL
Proj4js.defs["EPSG:4170"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: GRSSA.LL
Proj4js.defs["EPSG:4675"] = "+proj=longlat +ellps=clrk66 +towgs84=-100,-248,259,0,0,0,0 +no_defs "; //Mentor: GUAM63.LL
Proj4js.defs["EPSG:4624"] = "+proj=longlat +ellps=GRS80 +towgs84=2,2,-2,0,0,0,0 +no_defs "; //Mentor: Guyane95a.LL
Proj4js.defs["EPSG:4147"] = "+proj=longlat +ellps=krass +towgs84=-17.51,-108.32,-62.39,0,0,0,0 +no_defs "; //Mentor: Hanoi1972.LL
Proj4js.defs["EPSG:4148"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Hartebeesthoek94.LL
Proj4js.defs["EPSG:4237"] = "+proj=longlat +ellps=GRS67 +no_defs "; //Mentor: HD72/7P.LL
Proj4js.defs["EPSG:4255"] = "+proj=longlat +ellps=intl +towgs84=-333,-222,114,0,0,0,0 +no_defs "; //Mentor: HeratNorth_1.LL
Proj4js.defs["EPSG:4658"] = "+proj=longlat +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +no_defs "; //Mentor: HJORSEY.LL
Proj4js.defs["EPSG:4254"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: HitoXVIII63a.LL
Proj4js.defs["EPSG:4738"] = "+proj=longlat +a=6378293.645208759 +b=6356617.987679838 +no_defs "; //Mentor: HONGKONG.LL
Proj4js.defs["EPSG:4611"] = "+proj=longlat +ellps=intl +towgs84=-162.619,-276.959,-161.764,0.067753,-2.24365,-1.15883,-1.09425 +no_defs "; //Mentor: HongKong80a.LL
Proj4js.defs["EPSG:4236"] = "+proj=longlat +ellps=intl +towgs84=-637,-549,-203,0,0,0,0 +no_defs "; //Mentor: HuTzuShan_1.LL
Proj4js.defs["EPSG:4633"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN56/Lifou.LL
Proj4js.defs["EPSG:4630"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN72/NukuHiva.LL
Proj4js.defs["EPSG:4239"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +towgs84=217,823,299,0,0,0,0 +no_defs "; //Mentor: Indian1954.LL
Proj4js.defs["EPSG:4131"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Indian1960/E.LL
Proj4js.defs["EPSG:4240"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Indian75/E.LL
Proj4js.defs["EPSG:4238"] = "+proj=longlat +a=6378160 +b=6356774.50408554 +no_defs "; //Mentor: Indonesian1974.LL
Proj4js.defs["EPSG:4173"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IRENET95.LL
Proj4js.defs["EPSG:4725"] = "+proj=longlat +ellps=intl +towgs84=189,-79,-202,0,0,0,0 +no_defs "; //Mentor: JHNSTN.LL
Proj4js.defs["EPSG:4144"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Kalianpur1937.LL
Proj4js.defs["EPSG:4145"] = "+proj=longlat +a=6377301.243 +b=6356100.230165384 +no_defs "; //Mentor: Kalianpur1962.LL
Proj4js.defs["EPSG:4146"] = "+proj=longlat +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +no_defs "; //Mentor: Kalianpur1975.LL
Proj4js.defs["EPSG:4244"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +towgs84=-97,787,86,0,0,0,0 +no_defs "; //Mentor: Kandawala.LL
Proj4js.defs["EPSG:4698"] = "+proj=longlat +ellps=intl +towgs84=145,-187,103,0,0,0,0 +no_defs "; //Mentor: KERGUELN.LL
Proj4js.defs["EPSG:4245"] = "+proj=longlat +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +no_defs "; //Mentor: Kertau48.LL
Proj4js.defs["EPSG:4123"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: KKJa.LL
Proj4js.defs["EPSG:4166"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Korean95.LL
Proj4js.defs["EPSG:4247"] = "+proj=longlat +ellps=intl +towgs84=-273.5,110.6,-357.9,0,0,0,0 +no_defs "; //Mentor: LaCanoa/E.LL
Proj4js.defs["EPSG:4250"] = "+proj=longlat +ellps=clrk80 +towgs84=-130,29,364,0,0,0,0 +no_defs "; //Mentor: Leigon_1.LL
Proj4js.defs["EPSG:4251"] = "+proj=longlat +ellps=clrk80 +towgs84=-90,40,88,0,0,0,0 +no_defs "; //Mentor: Liberia.LL
Proj4js.defs["EPSG:4207"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Lisbon37a.LL
Proj4js.defs["EPSG:4669"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Lithuania94.LL
Proj4js.defs["EPSG:4142"] = "+proj=longlat +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +no_defs "; //Mentor: Locodjo1965.LL
Proj4js.defs["EPSG:4181"] = "+proj=longlat +ellps=intl +towgs84=-193,13.7,-39.3,-0.41,-2.933,2.688,0.43 +no_defs "; //Mentor: Luxembourg30a.LL
Proj4js.defs["EPSG:4253"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Luzon.LL
Proj4js.defs["EPSG:4615"] = "+proj=longlat +ellps=intl +towgs84=-499,-249,314,0,0,0,0 +no_defs "; //Mentor: MADEIRA.LL
Proj4js.defs["EPSG:4256"] = "+proj=longlat +ellps=clrk80 +towgs84=41,-220,-134,0,0,0,0 +no_defs "; //Mentor: Mahe1971.LL
Proj4js.defs["EPSG:4257"] = "+proj=longlat +ellps=bessel +towgs84=-587.8,519.75,145.76,0,0,0,0 +no_defs "; //Mentor: Makassar/E.LL
Proj4js.defs["EPSG:4259"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Malongo1987.LL
Proj4js.defs["EPSG:4262"] = "+proj=longlat +ellps=bessel +towgs84=639,405,60,0,0,0,0 +no_defs "; //Mentor: Massawa.LL
Proj4js.defs["EPSG:4312"] = "+proj=longlat +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +no_defs "; //Mentor: MGI-AT.LL
Proj4js.defs["EPSG:4264"] = "+proj=longlat +ellps=intl +towgs84=-252.95,-4.11,-96.38,0,0,0,0 +no_defs "; //Mentor: Mhast/gc.LL
Proj4js.defs["EPSG:4268"] = "+proj=longlat +a=6378450.047548896 +b=6356826.621488444 +no_defs "; //Mentor: MICHIGAN.LL
Proj4js.defs["EPSG:4727"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: MIDWAY.LL
Proj4js.defs["EPSG:4263"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Minna.LL
Proj4js.defs["EPSG:4638"] = "+proj=longlat +ellps=clrk66 +towgs84=30,430,368,0,0,0,0 +no_defs "; //Mentor: Miquelon1950.LL
Proj4js.defs["EPSG:4604"] = "+proj=longlat +ellps=clrk80 +towgs84=174,359,365,0,0,0,0 +no_defs "; //Mentor: Montserrat1958.LL
Proj4js.defs["EPSG:4639"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: MOP1978a.LL
Proj4js.defs["EPSG:4130"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,-0,-0,-0,0 +no_defs "; //Mentor: Moznet.LL
Proj4js.defs["EPSG:4266"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: Mporaloko_1.LL
Proj4js.defs["EPSG:4158"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: NAPARIMA.LL
Proj4js.defs["EPSG:4271"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Naparima1972.LL
Proj4js.defs["EPSG:4644"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Noumea74.LL
Proj4js.defs["EPSG:4273"] = "+proj=longlat +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +no_defs "; //Mentor: NGO48a.LL
Proj4js.defs["EPSG:4270"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: NHRWN-O.LL
Proj4js.defs["EPSG:4307"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: NordSahara1959.LL
Proj4js.defs["EPSG:4275"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +no_defs "; //Mentor: NTF.LL
Proj4js.defs["EPSG:4167"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: NZGD2000.LL
Proj4js.defs["EPSG:4182"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: OBSRV66.LL
Proj4js.defs["EPSG:4229"] = "+proj=longlat +ellps=helmert +no_defs "; //Mentor: Old-Egyp.LL
Proj4js.defs["EPSG:4277"] = "+proj=longlat +ellps=airy +datum=OSGB36 +no_defs "; //Mentor: OSGB.LL
Proj4js.defs["EPSG:4134"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: PDOSurvey93a.LL
Proj4js.defs["EPSG:4637"] = "+proj=longlat +ellps=intl +towgs84=325,154,172,0,0,0,0 +no_defs "; //Mentor: Perroud1950.LL
Proj4js.defs["EPSG:4636"] = "+proj=longlat +ellps=intl +towgs84=365,194,166,0,0,0,0 +no_defs "; //Mentor: Petrels1972.LL
Proj4js.defs["EPSG:4729"] = "+proj=longlat +ellps=intl +towgs84=185,165,42,0,0,0,0 +no_defs "; //Mentor: PITCAIRN.LL
Proj4js.defs["EPSG:4282"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: PointeNoire60.LL
Proj4js.defs["EPSG:4620"] = "+proj=longlat +ellps=clrk80 +towgs84=-106,-129,165,0,0,0,0 +no_defs "; //Mentor: Point1958.LL
Proj4js.defs["EPSG:4190"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: PosGAr98.LL
Proj4js.defs["EPSG:4139"] = "+proj=longlat +ellps=clrk66 +towgs84=11,72,-101,0,0,0,0 +no_defs "; //Mentor: PRVI.LL
Proj4js.defs["EPSG:4284"] = "+proj=longlat +ellps=krass +no_defs "; //Mentor: Pulkovo42.LL
Proj4js.defs["EPSG:4179"] = "+proj=longlat +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +no_defs "; //Mentor: Pulkovo42/58a.LL
Proj4js.defs["EPSG:4178"] = "+proj=longlat +ellps=krass +towgs84=24,-123,-94,0.02,-0.25,-0.13,1.1 +no_defs "; //Mentor: Pulkovo42/83a.LL
Proj4js.defs["EPSG:4286"] = "+proj=longlat +ellps=helmert +no_defs "; //Mentor: Qatar48.LL
Proj4js.defs["EPSG:4285"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Qatar1974.LL
Proj4js.defs["EPSG:4614"] = "+proj=longlat +ellps=intl +towgs84=-119.425,-303.659,-11.0006,1.1643,0.174458,1.09626,3.65706 +no_defs "; //Mentor: QatarNtl95a.LL
Proj4js.defs["EPSG:4194"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Qornoq1927.LL
Proj4js.defs["EPSG:4153"] = "+proj=longlat +ellps=intl +towgs84=-133.63,-157.5,-158.62,0,0,0,0 +no_defs "; //Mentor: Rassadiran_1.LL
Proj4js.defs["EPSG:4189"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: REGVEN.LL
Proj4js.defs["EPSG:4626"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: REUNION.LL
Proj4js.defs["EPSG:4627"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Reunion92.LL
Proj4js.defs["EPSG:4265"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: ROME1940.LL
Proj4js.defs["EPSG:4184"] = "+proj=longlat +ellps=intl +towgs84=-203,141,53,0,0,0,0 +no_defs "; //Mentor: SAOBRAZ.LL
Proj4js.defs["EPSG:4292"] = "+proj=longlat +ellps=intl +towgs84=-355,21,72,0,0,0,0 +no_defs "; //Mentor: Sapper.LL
Proj4js.defs["EPSG:4293"] = "+proj=longlat +ellps=bess_nam +no_defs "; //Mentor: Schwarzk.LL
Proj4js.defs["EPSG:4195"] = "+proj=longlat +ellps=intl +towgs84=105,326,-102.5,0,0,0.814,-0.6 +no_defs "; //Mentor: Scoresbysund52a.LL
Proj4js.defs["EPSG:4613"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: GunungSegara.LL
Proj4js.defs["EPSG:4616"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Selvagem.LL
Proj4js.defs["EPSG:4175"] = "+proj=longlat +ellps=clrk80 +towgs84=-88,4,101,0,0,0,0 +no_defs "; //Mentor: SierraLeone1968.LL
Proj4js.defs["EPSG:4156"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: CzechJTSK/5.LL
Proj4js.defs["EPSG:4164"] = "+proj=longlat +ellps=krass +towgs84=-76,-138,67,0,0,0,0 +no_defs "; //Mentor: SouthYemen_1.LL
Proj4js.defs["EPSG:4643"] = "+proj=longlat +ellps=intl +towgs84=-480.26,-438.32,-643.429,16.3119,20.1721,-4.0349,-111.7 +no_defs "; //Mentor: ST71Belep/a.LL
Proj4js.defs["EPSG:4642"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: ST84IlePins/a.LL
Proj4js.defs["EPSG:4622"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Guadeloupe48.LL
Proj4js.defs["EPSG:4605"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: StKitts1955.LL
Proj4js.defs["EPSG:4606"] = "+proj=longlat +ellps=clrk80 +towgs84=-149,128,296,0,0,0,0 +no_defs "; //Mentor: StLucia1955.LL
Proj4js.defs["EPSG:4619"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: SWEREF99.LL
Proj4js.defs["EPSG:4629"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Tahaa54.LL
Proj4js.defs["EPSG:4628"] = "+proj=longlat +ellps=intl +towgs84=162,117,154,0,0,0,0 +no_defs "; //Mentor: Tahiti52.LL
Proj4js.defs["EPSG:4297"] = "+proj=longlat +ellps=intl +towgs84=-189,-242,-91,0,0,0,0 +no_defs "; //Mentor: Tananarive1925.LL
Proj4js.defs["EPSG:4127"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Tete/a.LL
Proj4js.defs["EPSG:4298"] = "+proj=longlat +ellps=evrstSS +no_defs "; //Mentor: TIMBALAI.LL
Proj4js.defs["EPSG:4299"] = "+proj=longlat +a=6377340.189 +b=6356034.447938534 +no_defs "; //Mentor: TM1965/a.LL
Proj4js.defs["EPSG:4302"] = "+proj=longlat +a=6378293.645208759 +b=6356617.987679838 +no_defs "; //Mentor: Trinidad1903.LL
Proj4js.defs["EPSG:4734"] = "+proj=longlat +ellps=intl +towgs84=-632,438,-609,0,0,0,0 +no_defs "; //Mentor: TRISTAN.LL
Proj4js.defs["EPSG:4304"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-73,-247,227,0,0,0,0 +no_defs "; //Mentor: Voirol1875_1.LL
Proj4js.defs["EPSG:4324"] = "+proj=longlat +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +no_defs "; //Mentor: WGS72-TBE/a.LL
Proj4js.defs["EPSG:4610"] = "+proj=longlat +a=6378140 +b=6356755.288157528 +no_defs "; //Mentor: Xian80.LL
Proj4js.defs["EPSG:4309"] = "+proj=longlat +ellps=intl +towgs84=-155,171,37,0,0,0,0 +no_defs "; //Mentor: Yacare/E.LL
Proj4js.defs["EPSG:4163"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: YemenNtl96.LL
Proj4js.defs["EPSG:4311"] = "+proj=longlat +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +no_defs "; //Mentor: Zanderij.LL
Proj4js.defs["EPSG:4149"] = "+proj=longlat +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +no_defs "; //Mentor: LLCH1903
Proj4js.defs["EPSG:4736"] = "+proj=longlat +ellps=clrk80 +towgs84=260,12,-147,0,0,0,0 +no_defs "; //Mentor: DeceptionIsland_1.LL
Proj4js.defs["EPSG:4735"] = "+proj=longlat +ellps=intl +towgs84=647,1777,-1124,0,0,0,0 +no_defs "; //Mentor: Kusaie51.LL
Proj4js.defs["EPSG:4661"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Latvia1992.LL
Proj4js.defs["EPSG:4190"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: PGA98.LL
Proj4js.defs["EPSG:4733"] = "+proj=longlat +ellps=intl +towgs84=276,-57,149,0,0,0,0 +no_defs "; //Mentor: WakeIs1952.LL
Proj4js.defs["EPSG:4659"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IslandsNet1993.LL
Proj4js.defs["EPSG:4755"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Nasional1995.LL
Proj4js.defs["EPSG:4754"] = "+proj=longlat +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +no_defs "; //Mentor: Libyan2006_1.LL
Proj4js.defs["EPSG:4749"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: RGN-Caledonie/91-93.LL
Proj4js.defs["EPSG:4747"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Greenland1996.LL
Proj4js.defs["EPSG:4740"] = "+proj=longlat +a=6378136 +b=6356751.361745712 +towgs84=0,0,1.5,-0,-0,0.076,0 +no_defs "; //Mentor: ParametropZemp1990.LL
Proj4js.defs["EPSG:4702"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Mauritania1999.LL
Proj4js.defs["EPSG:4737"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Korean2000.LL
Proj4js.defs["EPSG:4670"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IGM1995.LL
Proj4js.defs["EPSG:4674"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: SRG-SA/2000.LL
Proj4js.defs["EPSG:4141"] = "+proj=longlat +ellps=GRS80 +towgs84=-48,55,52,0,0,0,0 +no_defs "; //Mentor: Israel.LL
Proj4js.defs["EPSG:4161"] = "+proj=longlat +ellps=intl +towgs84=27.5,14,186.4,0,0,0,0 +no_defs "; //Mentor: PampaCastillo.LL
Proj4js.defs["EPSG:4188"] = "+proj=longlat +ellps=airy +towgs84=482.5,-130.6,564.6,-1.042,-0.214,-0.631,8.15 +no_defs "; //Mentor: OSNI52.LL
Proj4js.defs["EPSG:4192"] = "+proj=longlat +ellps=intl +towgs84=-206.1,-174.7,-87.7,0,0,0,0 +no_defs "; //Mentor: Douala1948.LL
Proj4js.defs["EPSG:4212"] = "+proj=longlat +ellps=clrk80 +towgs84=31.95,300.99,419.19,0,0,0,0 +no_defs "; //Mentor: Barbados1938.LL
Proj4js.defs["EPSG:4231"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Europ87.LL
Proj4js.defs["EPSG:4242"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Jamaica1969.LL
Proj4js.defs["EPSG:4246"] = "+proj=longlat +ellps=clrk80 +towgs84=-294.7,-200.1,525.5,0,0,0,0 +no_defs "; //Mentor: KuwaitOil.LL
Proj4js.defs["EPSG:4281"] = "+proj=longlat +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +no_defs "; //Mentor: Palestine23.LL
Proj4js.defs["EPSG:4318"] = "+proj=longlat +ellps=WGS84 +towgs84=-3.2,-5.7,2.8,0,0,0,0 +no_defs "; //Mentor: NtlGeodeticNet.LL
Proj4js.defs["EPSG:4319"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: KuwaitUtility.LL
Proj4js.defs["EPSG:4607"] = "+proj=longlat +ellps=clrk80 +towgs84=195.671,332.517,274.607,0,0,0,0 +no_defs "; //Mentor: StVincent1945.LL
Proj4js.defs["EPSG:4641"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN53/Mare.LL
Proj4js.defs["EPSG:4657"] = "+proj=longlat +a=6377019.27 +b=6355762.5391 +towgs84=-28,199,5,0,0,0,0 +no_defs "; //Mentor: Reykjavik.LL
Proj4js.defs["EPSG:4660"] = "+proj=longlat +ellps=intl +towgs84=982.609,552.753,-540.873,32.3934,-153.257,-96.2266,16.805 +no_defs "; //Mentor: Helle1954.LL
Proj4js.defs["EPSG:4662"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN72/GrandeTerre.LL
Proj4js.defs["EPSG:4663"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: PortoSanto95.LL
Proj4js.defs["EPSG:4664"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: AzoresEast1995.LL
Proj4js.defs["EPSG:4665"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: AzoresCntrl1995.LL
Proj4js.defs["EPSG:4666"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: Lisbon1890_1.LL
Proj4js.defs["EPSG:4667"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IraqKuwait1992.LL
Proj4js.defs["EPSG:4673"] = "+proj=longlat +ellps=intl +towgs84=174.05,-25.49,112.57,-0,-0,0.554,0.2263 +no_defs "; //Mentor: Chatham1979.LL
Proj4js.defs["EPSG:4678"] = "+proj=longlat +ellps=krass +towgs84=44.585,-131.212,-39.544,0,0,0,0 +no_defs "; //Mentor: Lao97.LL
Proj4js.defs["EPSG:4679"] = "+proj=longlat +ellps=clrk80 +towgs84=-80.01,253.26,291.19,0,0,0,0 +no_defs "; //Mentor: Jouik61.LL
Proj4js.defs["EPSG:4680"] = "+proj=longlat +ellps=clrk80 +towgs84=124.5,-63.5,-281,0,0,0,0 +no_defs "; //Mentor: Nouakchott65.LL
Proj4js.defs["EPSG:4682"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Gulshan303.LL
Proj4js.defs["EPSG:4683"] = "+proj=longlat +ellps=clrk66 +towgs84=-127.62,-67.24,-47.04,-3.068,4.903,1.578,-1.06 +no_defs "; //Mentor: Philippine1992.LL
Proj4js.defs["EPSG:4684"] = "+proj=longlat +ellps=intl +towgs84=-133,-321,50,0,0,0,0 +no_defs "; //Mentor: Gan70.LL
Proj4js.defs["EPSG:4686"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: MarcoGNR.LL
Proj4js.defs["EPSG:4687"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: RGP-Francaise.LL
Proj4js.defs["EPSG:4688"] = "+proj=longlat +ellps=intl +towgs84=347.103,1078.12,2623.92,-33.8875,70.6773,-9.3943,186.074 +no_defs "; //Mentor: FatuIva/72.LL
Proj4js.defs["EPSG:4689"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN63/Hiva Oa.LL
Proj4js.defs["EPSG:4691"] = "+proj=longlat +ellps=intl +towgs84=215.525,149.593,176.229,-3.2624,-1.692,-1.1571,10.4773 +no_defs "; //Mentor: Moorea87.LL
Proj4js.defs["EPSG:4692"] = "+proj=longlat +ellps=intl +towgs84=217.037,86.959,23.956,0,0,0,0 +no_defs "; //Mentor: Maupiti1983.LL
Proj4js.defs["EPSG:4693"] = "+proj=longlat +ellps=WGS84 +towgs84=0,-0.15,0.68,0,0,0,0 +no_defs "; //Mentor: Nakhl-eGhanem.LL
Proj4js.defs["EPSG:4699"] = "+proj=longlat +ellps=clrk80 +towgs84=-770.1,158.4,-498.2,0,0,0,0 +no_defs "; //Mentor: LePouce34.LL
Proj4js.defs["EPSG:4701"] = "+proj=longlat +ellps=clrk80 +towgs84=-79.9,-158,-168.9,0,0,0,0 +no_defs "; //Mentor: CongoBelge55.LL
Proj4js.defs["EPSG:4708"] = "+proj=longlat +ellps=aust_SA +towgs84=-491,-22,435,0,0,0,0 +no_defs "; //Mentor: CocosIsl1965.LL
Proj4js.defs["EPSG:4709"] = "+proj=longlat +ellps=intl +towgs84=145,75,-272,0,0,0,0 +no_defs "; //Mentor: IwoJima45.LL
Proj4js.defs["EPSG:4710"] = "+proj=longlat +ellps=intl +towgs84=-320,550,-494,0,0,0,0 +no_defs "; //Mentor: StHelena71.LL
Proj4js.defs["EPSG:4711"] = "+proj=longlat +ellps=intl +towgs84=124,-234,-25,0,0,0,0 +no_defs "; //Mentor: Marcus52.LL
Proj4js.defs["EPSG:4713"] = "+proj=longlat +ellps=clrk80 +towgs84=-79,-129,145,0,0,0,0 +no_defs "; //Mentor: AyabelleLH.LL
Proj4js.defs["EPSG:4715"] = "+proj=longlat +ellps=intl +towgs84=-104,-129,239,0,0,0,0 +no_defs "; //Mentor: CampAreaAstro_1.LL
Proj4js.defs["EPSG:4716"] = "+proj=longlat +ellps=intl +towgs84=298,-304,-375,0,0,0,0 +no_defs "; //Mentor: PhoenixIs66.LL
Proj4js.defs["EPSG:4718"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Solomon68.LL
Proj4js.defs["EPSG:4720"] = "+proj=longlat +ellps=WGS72 +no_defs "; //Mentor: Fiji1986.LL
Proj4js.defs["EPSG:4721"] = "+proj=longlat +ellps=intl +towgs84=265.025,384.929,-194.046,0,0,0,0 +no_defs "; //Mentor: Fiji56.LL
Proj4js.defs["EPSG:4722"] = "+proj=longlat +ellps=intl +towgs84=-794,119,-298,0,0,0,0 +no_defs "; //Mentor: SouthGeorgia68.LL
Proj4js.defs["EPSG:4723"] = "+proj=longlat +ellps=clrk66 +towgs84=67.8,106.1,138.8,0,0,0,0 +no_defs "; //Mentor: GrandCayman59.LL
Proj4js.defs["EPSG:4724"] = "+proj=longlat +ellps=intl +towgs84=208,-435,-229,0,0,0,0 +no_defs "; //Mentor: DiegoGarcia69.LL
Proj4js.defs["EPSG:4726"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: LittleCayman61.LL
Proj4js.defs["EPSG:4730"] = "+proj=longlat +ellps=intl +towgs84=170,42,84,0,0,0,0 +no_defs "; //Mentor: Santo65.LL
Proj4js.defs["EPSG:4732"] = "+proj=longlat +a=6378270 +b=6356794.343434343 +towgs84=102,52,-38,0,0,0,0 +no_defs "; //Mentor: Marshalls60.LL
Proj4js.defs["EPSG:4739"] = "+proj=longlat +ellps=intl +towgs84=-156,-271,-189,0,0,0,0 +no_defs "; //Mentor: HongKong63/1967.LL
Proj4js.defs["EPSG:4743"] = "+proj=longlat +ellps=clrk80 +towgs84=84.1,-320.1,218.7,0,0,0,0 +no_defs "; //Mentor: Karbala79/P.LL
Proj4js.defs["EPSG:4748"] = "+proj=longlat +a=6378306.3696 +b=6356571.996 +towgs84=51,391,-36,0,0,0,0 +no_defs "; //Mentor: VanuaLv1915.LL
Proj4js.defs["EPSG:4805"] = "+proj=longlat +ellps=bessel +pm=ferro +no_defs "; //Mentor: MGI-AT/F.LL/a
Proj4js.defs["EPSG:4255"] = "+proj=longlat +ellps=intl +towgs84=-333,-222,114,0,0,0,0 +no_defs "; //Mentor: HeratNorth_1.LL
Proj4js.defs["EPSG:30791"] = "+proj=lcc +lat_1=36 +lat_0=36 +lon_0=2.7 +k_0=0.999625544 +x_0=500135 +y_0=300090 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UnifieN
Proj4js.defs["EPSG:4307"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: NordSahara1959.LL
Proj4js.defs["EPSG:30792"] = "+proj=lcc +lat_1=33.3 +lat_0=33.3 +lon_0=2.7 +k_0=0.999625769 +x_0=500135 +y_0=300090 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UnifieS
Proj4js.defs["EPSG:30729"] = "+proj=utm +zone=29 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-29N
Proj4js.defs["EPSG:30730"] = "+proj=utm +zone=30 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-30N
Proj4js.defs["EPSG:30731"] = "+proj=utm +zone=31 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-31N
Proj4js.defs["EPSG:30732"] = "+proj=utm +zone=32 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-32N
Proj4js.defs["EPSG:4304"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-73,-247,227,0,0,0,0 +no_defs "; //Mentor: Voirol1875_1.LL
Proj4js.defs["EPSG:30491"] = "+proj=lcc +lat_1=36 +lat_0=36 +lon_0=2.7 +k_0=0.999625544 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=-73,-247,227,0,0,0,0 +units=m +no_defs "; //Mentor: V1875.NordAlgerie
Proj4js.defs["EPSG:30492"] = "+proj=lcc +lat_1=33.3 +lat_0=33.3 +lon_0=2.7 +k_0=0.999625769 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=-73,-247,227,0,0,0,0 +units=m +no_defs "; //Mentor: Voirol1875_1.SudAlgerie
Proj4js.defs["EPSG:4220"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Camacupa_1.LL
Proj4js.defs["EPSG:22091"] = "+proj=tmerc +lat_0=0 +lon_0=11.5 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.TM-1130SE
Proj4js.defs["EPSG:22092"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.TM-12SE
Proj4js.defs["EPSG:22032"] = "+proj=utm +zone=32 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.UTM-32S
Proj4js.defs["EPSG:22033"] = "+proj=utm +zone=33 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.UTM-33S
Proj4js.defs["EPSG:4259"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Malongo1987.LL
Proj4js.defs["EPSG:25932"] = "+proj=utm +zone=32 +south +ellps=intl +units=m +no_defs "; //Mentor: Malongo1987.UTM-32S
Proj4js.defs["EPSG:4264"] = "+proj=longlat +ellps=intl +towgs84=-252.95,-4.11,-96.38,0,0,0,0 +no_defs "; //Mentor: Mhast/gc.LL
Proj4js.defs["EPSG:26432"] = "+proj=utm +zone=32 +south +ellps=intl +towgs84=-252.95,-4.11,-96.38,0,0,0,0 +units=m +no_defs "; //Mentor: Mhast/gc.UTM-32S
Proj4js.defs["EPSG:4701"] = "+proj=longlat +ellps=clrk80 +towgs84=-79.9,-158,-168.9,0,0,0,0 +no_defs "; //Mentor: CongoBelge55.LL
Proj4js.defs["EPSG:4176"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Antarctic98.LL
Proj4js.defs["EPSG:4698"] = "+proj=longlat +ellps=intl +towgs84=145,-187,103,0,0,0,0 +no_defs "; //Mentor: KERGUELN.LL
Proj4js.defs["EPSG:4637"] = "+proj=longlat +ellps=intl +towgs84=325,154,172,0,0,0,0 +no_defs "; //Mentor: Perroud1950.LL
Proj4js.defs["EPSG:4636"] = "+proj=longlat +ellps=intl +towgs84=365,194,166,0,0,0,0 +no_defs "; //Mentor: Petrels1972.LL
Proj4js.defs["EPSG:4736"] = "+proj=longlat +ellps=clrk80 +towgs84=260,12,-147,0,0,0,0 +no_defs "; //Mentor: DeceptionIsland_1.LL
Proj4js.defs["EPSG:4715"] = "+proj=longlat +ellps=intl +towgs84=-104,-129,239,0,0,0,0 +no_defs "; //Mentor: CampAreaAstro_1.LL
Proj4js.defs["EPSG:4722"] = "+proj=longlat +ellps=intl +towgs84=-794,119,-298,0,0,0,0 +no_defs "; //Mentor: SouthGeorgia68.LL
Proj4js.defs["EPSG:3336"] = "+proj=utm +zone=42 +south +ellps=intl +towgs84=145,-187,103,0,0,0,0 +units=m +no_defs "; //Mentor: KERGUELN.UTM-42S
Proj4js.defs["EPSG:3033"] = "+proj=lcc +lat_1=-68.5 +lat_2=-74.5 +lat_0=-50 +lon_0=70 +x_0=6000000 +y_0=6000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.AusAntarctic/LM
Proj4js.defs["EPSG:3204"] = "+proj=lcc +lat_1=-60.66666666666666 +lat_2=-63.33333333333334 +lat_0=-90 +lon_0=-66 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SP19-20
Proj4js.defs["EPSG:3205"] = "+proj=lcc +lat_1=-60.66666666666666 +lat_2=-63.33333333333334 +lat_0=-90 +lon_0=-54 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SP21-22
Proj4js.defs["EPSG:3206"] = "+proj=lcc +lat_1=-60.66666666666666 +lat_2=-63.33333333333334 +lat_0=-90 +lon_0=-42 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SP23-24
Proj4js.defs["EPSG:3207"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=-174 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ01-02
Proj4js.defs["EPSG:3208"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=-66 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ19-20
Proj4js.defs["EPSG:3209"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=-54 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ21-22
Proj4js.defs["EPSG:3210"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=42 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ37-38
Proj4js.defs["EPSG:3211"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=54 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ39-40
Proj4js.defs["EPSG:3212"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=66 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ41-42
Proj4js.defs["EPSG:3213"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=78 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ43-44
Proj4js.defs["EPSG:3214"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=90 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ45-46
Proj4js.defs["EPSG:3215"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=102 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ47-48
Proj4js.defs["EPSG:3216"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=114 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ49-50
Proj4js.defs["EPSG:3217"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=126 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ51-52
Proj4js.defs["EPSG:3218"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=138 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ53-54
Proj4js.defs["EPSG:3219"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=150 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ55-56
Proj4js.defs["EPSG:3220"] = "+proj=lcc +lat_1=-64.66666666666667 +lat_2=-67.33333333333333 +lat_0=-90 +lon_0=162 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SQ57-58
Proj4js.defs["EPSG:3221"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=-102 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR13-14
Proj4js.defs["EPSG:3222"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=-90 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR15-16
Proj4js.defs["EPSG:3223"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=-78 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR17-18
Proj4js.defs["EPSG:3224"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=-66 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR19-20
Proj4js.defs["EPSG:3225"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=-18 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR27-28
Proj4js.defs["EPSG:3226"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=-6 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR29-30
Proj4js.defs["EPSG:3227"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=6 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR31-32
Proj4js.defs["EPSG:3228"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=18 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR33-34
Proj4js.defs["EPSG:3229"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=30 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR35-36
Proj4js.defs["EPSG:3230"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=42 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR37-38
Proj4js.defs["EPSG:3231"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=54 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR39-40
Proj4js.defs["EPSG:3232"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=66 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR41-42
Proj4js.defs["EPSG:3233"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=78 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR43-44
Proj4js.defs["EPSG:3234"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=90 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR45-46
Proj4js.defs["EPSG:3235"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=102 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR47-48
Proj4js.defs["EPSG:3236"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=114 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR49-50
Proj4js.defs["EPSG:3237"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=126 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR51-52
Proj4js.defs["EPSG:3238"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=138 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR53-54
Proj4js.defs["EPSG:3239"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=150 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR55-56
Proj4js.defs["EPSG:3240"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=162 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR57-58
Proj4js.defs["EPSG:3241"] = "+proj=lcc +lat_1=-68.66666666666667 +lat_2=-71.33333333333333 +lat_0=-90 +lon_0=174 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SR59-60
Proj4js.defs["EPSG:3242"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-153 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS04-06
Proj4js.defs["EPSG:3243"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-135 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS07-09
Proj4js.defs["EPSG:3244"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-117 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS10-12
Proj4js.defs["EPSG:3245"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-99 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS13-15
Proj4js.defs["EPSG:3246"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-81 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS16-18
Proj4js.defs["EPSG:3247"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-63 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS19-21
Proj4js.defs["EPSG:3248"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-27 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS25-27
Proj4js.defs["EPSG:3249"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=-9 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS28-30
Proj4js.defs["EPSG:3250"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=9 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS31-33
Proj4js.defs["EPSG:3251"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=27 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS34-36
Proj4js.defs["EPSG:3252"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=45 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS37-39
Proj4js.defs["EPSG:3253"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=63 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS40-42
Proj4js.defs["EPSG:3254"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=81 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS43-45
Proj4js.defs["EPSG:3255"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=99 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS46-48
Proj4js.defs["EPSG:3256"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=117 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS49-51
Proj4js.defs["EPSG:3257"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=135 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS52-54
Proj4js.defs["EPSG:3258"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=153 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS55-57
Proj4js.defs["EPSG:3259"] = "+proj=lcc +lat_1=-72.66666666666667 +lat_2=-75.33333333333333 +lat_0=-90 +lon_0=171 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-SS58-60
Proj4js.defs["EPSG:3260"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=-168 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST01-04
Proj4js.defs["EPSG:3261"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=-144 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST05-08
Proj4js.defs["EPSG:3262"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=-120 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST09-12
Proj4js.defs["EPSG:3263"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=-96 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST13-16
Proj4js.defs["EPSG:3264"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=-72 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST17-20
Proj4js.defs["EPSG:3265"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=-48 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST21-24
Proj4js.defs["EPSG:3266"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=-24 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST25-28
Proj4js.defs["EPSG:3267"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST29-32
Proj4js.defs["EPSG:3268"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=24 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST33-36
Proj4js.defs["EPSG:3269"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=48 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST37-40
Proj4js.defs["EPSG:3270"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=72 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST41-44
Proj4js.defs["EPSG:3271"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=96 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST45-48
Proj4js.defs["EPSG:3272"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=120 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST49-52
Proj4js.defs["EPSG:3273"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=144 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST53-56
Proj4js.defs["EPSG:3274"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-90 +lon_0=168 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.SCAR-ST57-60
Proj4js.defs["EPSG:3294"] = "+proj=lcc +lat_1=-76.66666666666667 +lat_2=-79.33333333333333 +lat_0=-78 +lon_0=162 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.USGS-AntarticMtn
Proj4js.defs["EPSG:2001"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Antigua1943.BWIgrid
Proj4js.defs["EPSG:4601"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Antigua1943.LL
Proj4js.defs["EPSG:22191"] = "+proj=tmerc +lat_0=-90 +lon_0=-72 +k=1 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Campo.Argentina1
Proj4js.defs["EPSG:22192"] = "+proj=tmerc +lat_0=-90 +lon_0=-69 +k=1 +x_0=2500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Campo.Argentina2
Proj4js.defs["EPSG:22193"] = "+proj=tmerc +lat_0=-90 +lon_0=-66 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Campo.Argentina3
Proj4js.defs["EPSG:22194"] = "+proj=tmerc +lat_0=-90 +lon_0=-63 +k=1 +x_0=4500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Campo.Argentina4
Proj4js.defs["EPSG:22195"] = "+proj=tmerc +lat_0=-90 +lon_0=-60 +k=1 +x_0=5500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Campo.Argentina5
Proj4js.defs["EPSG:22196"] = "+proj=tmerc +lat_0=-90 +lon_0=-57 +k=1 +x_0=6500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Campo.Argentina6
Proj4js.defs["EPSG:22197"] = "+proj=tmerc +lat_0=-90 +lon_0=-54 +k=1 +x_0=7500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Campo.Argentina7
Proj4js.defs["EPSG:4221"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Campo.LL
Proj4js.defs["EPSG:2315"] = "+proj=utm +zone=19 +south +ellps=intl +units=m +no_defs "; //Mentor: CampoInchauspe.UTM-19S
Proj4js.defs["EPSG:2316"] = "+proj=utm +zone=20 +south +ellps=intl +units=m +no_defs "; //Mentor: CampoInchauspe.UTM-20S
Proj4js.defs["EPSG:2083"] = "+proj=tmerc +lat_0=-90 +lon_0=-69 +k=1 +x_0=2500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Hito63a.Argentina-2
Proj4js.defs["EPSG:4190"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: PosGAr98.LL
Proj4js.defs["EPSG:4190"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: PGA98.LL
Proj4js.defs["EPSG:22171"] = "+proj=tmerc +lat_0=-90 +lon_0=-72 +k=1 +x_0=1500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: PGA98.Argentina-1
Proj4js.defs["EPSG:22172"] = "+proj=tmerc +lat_0=-90 +lon_0=-69 +k=1 +x_0=2500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: PGA98.Argentina-2
Proj4js.defs["EPSG:22173"] = "+proj=tmerc +lat_0=-90 +lon_0=-66 +k=1 +x_0=3500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: PGA98.Argentina-3
Proj4js.defs["EPSG:22174"] = "+proj=tmerc +lat_0=-90 +lon_0=-63 +k=1 +x_0=4500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: PGA98.Argentina-4
Proj4js.defs["EPSG:22175"] = "+proj=tmerc +lat_0=-90 +lon_0=-60 +k=1 +x_0=5500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: PGA98.Argentina-5
Proj4js.defs["EPSG:22176"] = "+proj=tmerc +lat_0=-90 +lon_0=-57 +k=1 +x_0=6500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: PGA98.Argentina-6
Proj4js.defs["EPSG:22177"] = "+proj=tmerc +lat_0=-90 +lon_0=-54 +k=1 +x_0=7500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: PGA98.Argentina-7
Proj4js.defs["EPSG:4674"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: SRG-SA/2000.LL
Proj4js.defs["EPSG:4161"] = "+proj=longlat +ellps=intl +towgs84=27.5,14,186.4,0,0,0,0 +no_defs "; //Mentor: PampaCastillo.LL
Proj4js.defs["EPSG:4686"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: MarcoGNR.LL
Proj4js.defs["EPSG:2082"] = "+proj=tmerc +lat_0=-90 +lon_0=-69 +k=1 +x_0=2500000 +y_0=0 +ellps=intl +towgs84=27.5,14,186.4,0,0,0,0 +units=m +no_defs "; //Mentor: pCastillo.Argentina-2
Proj4js.defs["EPSG:2935"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=41.53333333333333 +k=1 +x_0=1300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A1
Proj4js.defs["EPSG:2936"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=44.53333333333333 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A2
Proj4js.defs["EPSG:2937"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=47.53333333333333 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A3
Proj4js.defs["EPSG:28408"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=8500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-8
Proj4js.defs["EPSG:20249"] = "+proj=utm +zone=49 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-49-Grid
Proj4js.defs["EPSG:20250"] = "+proj=utm +zone=50 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-50-Grid
Proj4js.defs["EPSG:20251"] = "+proj=utm +zone=51 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-51-Grid
Proj4js.defs["EPSG:20252"] = "+proj=utm +zone=52 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-52-Grid
Proj4js.defs["EPSG:20253"] = "+proj=utm +zone=53 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-53-Grid
Proj4js.defs["EPSG:20254"] = "+proj=utm +zone=54 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-54-Grid
Proj4js.defs["EPSG:20255"] = "+proj=utm +zone=55 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-55-Grid
Proj4js.defs["EPSG:20256"] = "+proj=utm +zone=56 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-56-Grid
Proj4js.defs["EPSG:20257"] = "+proj=utm +zone=57 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-57-Grid
Proj4js.defs["EPSG:20349"] = "+proj=utm +zone=49 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-49-Grid
Proj4js.defs["EPSG:20350"] = "+proj=utm +zone=50 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-50-Grid
Proj4js.defs["EPSG:20351"] = "+proj=utm +zone=51 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-51-Grid
Proj4js.defs["EPSG:20352"] = "+proj=utm +zone=52 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-52-Grid
Proj4js.defs["EPSG:20353"] = "+proj=utm +zone=53 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-53-Grid
Proj4js.defs["EPSG:20354"] = "+proj=utm +zone=54 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-54-Grid
Proj4js.defs["EPSG:20355"] = "+proj=utm +zone=55 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-55-Grid
Proj4js.defs["EPSG:20356"] = "+proj=utm +zone=56 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-56-Grid
Proj4js.defs["EPSG:20357"] = "+proj=utm +zone=57 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG-57-Grid
Proj4js.defs["EPSG:28348"] = "+proj=utm +zone=48 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-48
Proj4js.defs["EPSG:28349"] = "+proj=utm +zone=49 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-49
Proj4js.defs["EPSG:28350"] = "+proj=utm +zone=50 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-50
Proj4js.defs["EPSG:28351"] = "+proj=utm +zone=51 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-51
Proj4js.defs["EPSG:28352"] = "+proj=utm +zone=52 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-52
Proj4js.defs["EPSG:28353"] = "+proj=utm +zone=53 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-53
Proj4js.defs["EPSG:28354"] = "+proj=utm +zone=54 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-54
Proj4js.defs["EPSG:28355"] = "+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-55
Proj4js.defs["EPSG:28356"] = "+proj=utm +zone=56 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-56
Proj4js.defs["EPSG:28357"] = "+proj=utm +zone=57 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-57
Proj4js.defs["EPSG:28358"] = "+proj=utm +zone=58 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MGA-58
Proj4js.defs["EPSG:3110"] = "+proj=lcc +lat_1=-36 +lat_2=-38 +lat_0=-37 +lon_0=145 +x_0=2500000 +y_0=4500000 +ellps=aust_SA +units=m +no_defs "; //Mentor: VICT
Proj4js.defs["EPSG:4203"] = "+proj=longlat +ellps=aust_SA +no_defs "; //Mentor: LL-AGD84-Grid
Proj4js.defs["EPSG:4283"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: LL-GDA94
Proj4js.defs["EPSG:4176"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Antarctic98.LL
Proj4js.defs["EPSG:20248"] = "+proj=utm +zone=48 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-48-GRID
Proj4js.defs["EPSG:20258"] = "+proj=utm +zone=58 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG66-58-GRID
Proj4js.defs["EPSG:20348"] = "+proj=utm +zone=48 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG84-48-GRID
Proj4js.defs["EPSG:20358"] = "+proj=utm +zone=58 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: AMG84-58-GRID
Proj4js.defs["EPSG:4708"] = "+proj=longlat +ellps=aust_SA +towgs84=-491,-22,435,0,0,0,0 +no_defs "; //Mentor: CocosIsl1965.LL
Proj4js.defs["EPSG:3107"] = "+proj=lcc +lat_1=-28 +lat_2=-36 +lat_0=-32 +lon_0=135 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GDA94.SA/Lambert
Proj4js.defs["EPSG:3111"] = "+proj=lcc +lat_1=-36 +lat_2=-38 +lat_0=-37 +lon_0=145 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GDA94.VictoriaGrid
Proj4js.defs["EPSG:3112"] = "+proj=lcc +lat_1=-18 +lat_2=-36 +lat_0=0 +lon_0=134 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GDA94.Geoscience/Lmbrt
Proj4js.defs["EPSG:3113"] = "+proj=tmerc +lat_0=-28 +lon_0=153 +k=0.99999 +x_0=50000 +y_0=100000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GDA94.Brisbane
Proj4js.defs["EPSG:31251"] = "+proj=tmerc +lat_0=0 +lon_0=28 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.AUT-West/GK
Proj4js.defs["EPSG:31252"] = "+proj=tmerc +lat_0=0 +lon_0=31 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.AUT-Central/GK
Proj4js.defs["EPSG:31253"] = "+proj=tmerc +lat_0=0 +lon_0=34 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.AUT-East/GK
Proj4js.defs["EPSG:31254"] = "+proj=tmerc +lat_0=0 +lon_0=10.33333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.AUT-West/GK
Proj4js.defs["EPSG:31255"] = "+proj=tmerc +lat_0=0 +lon_0=13.33333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.AUT-Central/GK
Proj4js.defs["EPSG:31256"] = "+proj=tmerc +lat_0=0 +lon_0=16.33333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.AUT-East/GK
Proj4js.defs["EPSG:31257"] = "+proj=tmerc +lat_0=0 +lon_0=10.33333333333333 +k=1 +x_0=150000 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.M28/GKa
Proj4js.defs["EPSG:31258"] = "+proj=tmerc +lat_0=0 +lon_0=13.33333333333333 +k=1 +x_0=450000 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.M31/GKa
Proj4js.defs["EPSG:31259"] = "+proj=tmerc +lat_0=0 +lon_0=16.33333333333333 +k=1 +x_0=750000 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.M34/GKa
Proj4js.defs["EPSG:31281"] = "+proj=tmerc +lat_0=0 +lon_0=28 +k=1 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.AUT-West
Proj4js.defs["EPSG:31282"] = "+proj=tmerc +lat_0=0 +lon_0=31 +k=1 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.AUT-Central
Proj4js.defs["EPSG:31283"] = "+proj=tmerc +lat_0=0 +lon_0=34 +k=1 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.AUT-East
Proj4js.defs["EPSG:31284"] = "+proj=tmerc +lat_0=0 +lon_0=10.33333333333333 +k=1 +x_0=150000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.M28
Proj4js.defs["EPSG:31285"] = "+proj=tmerc +lat_0=0 +lon_0=13.33333333333333 +k=1 +x_0=450000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.M31
Proj4js.defs["EPSG:31286"] = "+proj=tmerc +lat_0=0 +lon_0=16.33333333333333 +k=1 +x_0=750000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI-AT.M34
Proj4js.defs["EPSG:31288"] = "+proj=tmerc +lat_0=0 +lon_0=28 +k=1 +x_0=150000 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.M28
Proj4js.defs["EPSG:31289"] = "+proj=tmerc +lat_0=0 +lon_0=31 +k=1 +x_0=450000 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.M31
Proj4js.defs["EPSG:31290"] = "+proj=tmerc +lat_0=0 +lon_0=34 +k=1 +x_0=750000 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: MGI-AT/F.M34
Proj4js.defs["EPSG:2936"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=44.53333333333333 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A2
Proj4js.defs["EPSG:2937"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=47.53333333333333 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A3
Proj4js.defs["EPSG:2938"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=50.53333333333333 +k=1 +x_0=4300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A4
Proj4js.defs["EPSG:28408"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=8500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-8
Proj4js.defs["EPSG:28409"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=9500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-9
Proj4js.defs["EPSG:2318"] = "+proj=lcc +lat_1=17 +lat_2=33 +lat_0=25.08951 +lon_0=48 +x_0=0 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: AINELABD.AramcoLambert
Proj4js.defs["EPSG:20499"] = "+proj=utm +zone=39 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.BahrainGrid
Proj4js.defs["EPSG:4204"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: AinElAbd.LL
Proj4js.defs["EPSG:20437"] = "+proj=utm +zone=37 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.UTM-37N
Proj4js.defs["EPSG:20438"] = "+proj=utm +zone=38 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.UTM-38N
Proj4js.defs["EPSG:20436"] = "+proj=utm +zone=36 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.UTM-36N
Proj4js.defs["EPSG:4682"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Gulshan303.LL
Proj4js.defs["EPSG:3106"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=0.9996 +x_0=500000 +y_0=0 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Glshn303.Bangladesh/TM
Proj4js.defs["EPSG:28406"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=6500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-6
Proj4js.defs["EPSG:21500"] = "+proj=lcc +lat_1=49.83333333333334 +lat_2=51.16666666666666 +lat_0=90 +lon_0=0 +x_0=150000 +y_0=5400000 +ellps=intl +pm=brussels +units=m +no_defs "; //Mentor: BELGE-50/a
Proj4js.defs["EPSG:31370"] = "+proj=lcc +lat_1=51.16666723333333 +lat_2=49.8333339 +lat_0=90 +lon_0=4.367486666666666 +x_0=150000.013 +y_0=5400088.438 +ellps=intl +towgs84=106.869,-52.2978,103.724,-0.33657,0.456955,-1.84218,1 +units=m +no_defs "; //Mentor: Belge72a.Lambert72A
Proj4js.defs["EPSG:4313"] = "+proj=longlat +ellps=intl +towgs84=106.869,-52.2978,103.724,-0.33657,0.456955,-1.84218,1 +no_defs "; //Mentor: Belge72a.LL
Proj4js.defs["EPSG:4216"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Bermuda.LL
Proj4js.defs["EPSG:3770"] = "+proj=tmerc +lat_0=32 +lon_0=-64.75 +k=1 +x_0=550000 +y_0=100000 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: BNG2000
Proj4js.defs["EPSG:24879"] = "+proj=utm +zone=19 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-19S
Proj4js.defs["EPSG:24880"] = "+proj=utm +zone=20 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-20S
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19S
Proj4js.defs["EPSG:29190"] = "+proj=utm +zone=20 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-20S
Proj4js.defs["EPSG:29871"] = "+proj=omerc +lat_0=4 +lonc=115 +alpha=53.31582047222222 +k=0.99984 +x_0=590476.8714630401 +y_0=442857.653094361 +ellps=evrstSS +to_meter=20.11676512155263 +no_defs "; //Mentor: TMBLI-B.RSOBorneo.ch
Proj4js.defs["EPSG:29872"] = "+proj=omerc +lat_0=4 +lonc=115 +alpha=53.31582047222222 +k=0.99984 +x_0=590476.8727431979 +y_0=442857.6545573985 +ellps=evrstSS +to_meter=0.3047994715386762 +no_defs "; //Mentor: TMBLI-B.RSOBorneo.ft
Proj4js.defs["EPSG:29873"] = "+proj=omerc +lat_0=4 +lonc=115 +alpha=53.31582047222222 +k=0.99984 +x_0=590476.87 +y_0=442857.65 +ellps=evrstSS +units=m +no_defs "; //Mentor: TMBLI-B.RSOBorneo.m
Proj4js.defs["EPSG:4312"] = "+proj=longlat +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +no_defs "; //Mentor: MGI-AT.LL
Proj4js.defs["EPSG:20934"] = "+proj=utm +zone=34 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-34S/01
Proj4js.defs["EPSG:20935"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-35S/01
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19S
Proj4js.defs["EPSG:29190"] = "+proj=utm +zone=20 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-20S
Proj4js.defs["EPSG:29191"] = "+proj=utm +zone=21 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-21S
Proj4js.defs["EPSG:29192"] = "+proj=utm +zone=22 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-22S
Proj4js.defs["EPSG:29193"] = "+proj=utm +zone=23 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-23S
Proj4js.defs["EPSG:29194"] = "+proj=utm +zone=24 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-24S
Proj4js.defs["EPSG:29195"] = "+proj=utm +zone=25 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-25S
Proj4js.defs["EPSG:4208"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Aratu_1.LL
Proj4js.defs["EPSG:20822"] = "+proj=utm +zone=22 +south +ellps=intl +units=m +no_defs "; //Mentor: Aratu_1.UTM-22S
Proj4js.defs["EPSG:20823"] = "+proj=utm +zone=23 +south +ellps=intl +units=m +no_defs "; //Mentor: Aratu_1.UTM-23S
Proj4js.defs["EPSG:20824"] = "+proj=utm +zone=24 +south +ellps=intl +units=m +no_defs "; //Mentor: Aratu_1.UTM-24S
Proj4js.defs["EPSG:4225"] = "+proj=longlat +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +no_defs "; //Mentor: Corrego.LL
Proj4js.defs["EPSG:22523"] = "+proj=utm +zone=23 +south +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +units=m +no_defs "; //Mentor: Corrego.UTM-23S
Proj4js.defs["EPSG:22524"] = "+proj=utm +zone=24 +south +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +units=m +no_defs "; //Mentor: Corrego.UTM-24S
Proj4js.defs["EPSG:29101"] = "+proj=poly +lat_0=0 +lon_0=-54 +x_0=5000000 +y_0=10000000 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD1969.BzPolyconic/01
Proj4js.defs["EPSG:2309"] = "+proj=tmerc +lat_0=0 +lon_0=116 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-116SE
Proj4js.defs["EPSG:2310"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-132SE
Proj4js.defs["EPSG:22521"] = "+proj=utm +zone=21 +south +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +units=m +no_defs "; //Mentor: Corrego.UTM-21S
Proj4js.defs["EPSG:22522"] = "+proj=utm +zone=22 +south +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +units=m +no_defs "; //Mentor: Corrego.UTM-22S
Proj4js.defs["EPSG:28404"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=4500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: BUL-34
Proj4js.defs["EPSG:28405"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=5500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: BUL-35
Proj4js.defs["EPSG:2215"] = "+proj=utm +zone=32 +a=6378249.2 +b=6356515 +towgs84=-70.9,-151.8,-41.4,0,0,0,0 +units=m +no_defs "; //Mentor: Manoca1962.UTM-32N
Proj4js.defs["EPSG:4193"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-70.9,-151.8,-41.4,0,0,0,0 +no_defs "; //Mentor: Manoca1962.LL
Proj4js.defs["EPSG:4192"] = "+proj=longlat +ellps=intl +towgs84=-206.1,-174.7,-87.7,0,0,0,0 +no_defs "; //Mentor: Douala1948.LL
Proj4js.defs["EPSG:3119"] = "+proj=tmerc +lat_0=0 +lon_0=10.5 +k=0.999 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=-206.1,-174.7,-87.7,0,0,0,0 +units=m +no_defs "; //Mentor: Douala1948.AEF-West
Proj4js.defs["EPSG:32083"] = "+proj=tmerc +lat_0=0 +lon_0=-58.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: CANQ27-M3
Proj4js.defs["EPSG:32084"] = "+proj=tmerc +lat_0=0 +lon_0=-61.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: CANQ27-M4
Proj4js.defs["EPSG:32085"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: CANQ27-M5
Proj4js.defs["EPSG:32086"] = "+proj=tmerc +lat_0=0 +lon_0=-67.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: CANQ27-M6
Proj4js.defs["EPSG:32181"] = "+proj=tmerc +lat_0=0 +lon_0=-53 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M1
Proj4js.defs["EPSG:32182"] = "+proj=tmerc +lat_0=0 +lon_0=-56 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MTM83-2
Proj4js.defs["EPSG:32183"] = "+proj=tmerc +lat_0=0 +lon_0=-58.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M3
Proj4js.defs["EPSG:32184"] = "+proj=tmerc +lat_0=0 +lon_0=-61.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M4
Proj4js.defs["EPSG:32185"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M5
Proj4js.defs["EPSG:32186"] = "+proj=tmerc +lat_0=0 +lon_0=-67.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M6
Proj4js.defs["EPSG:32187"] = "+proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M7
Proj4js.defs["EPSG:32194"] = "+proj=tmerc +lat_0=0 +lon_0=-87 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M14
Proj4js.defs["EPSG:32081"] = "+proj=tmerc +lat_0=0 +lon_0=-53 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.MTM-1
Proj4js.defs["EPSG:32082"] = "+proj=tmerc +lat_0=0 +lon_0=-56 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.MTM-2
Proj4js.defs["EPSG:32098"] = "+proj=lcc +lat_1=60 +lat_2=46 +lat_0=44 +lon_0=-68.5 +x_0=0 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.QuebecLambert
Proj4js.defs["EPSG:32198"] = "+proj=lcc +lat_1=60 +lat_2=46 +lat_0=44 +lon_0=-68.5 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.QuebecLambert
Proj4js.defs["EPSG:32180"] = "+proj=tmerc +lat_0=0 +lon_0=-55.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.SCoPQ-2
Proj4js.defs["EPSG:2945"] = "+proj=tmerc +lat_0=0 +lon_0=-58.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-3
Proj4js.defs["EPSG:2946"] = "+proj=tmerc +lat_0=0 +lon_0=-61.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-4
Proj4js.defs["EPSG:2947"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-5
Proj4js.defs["EPSG:2948"] = "+proj=tmerc +lat_0=0 +lon_0=-67.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-6
Proj4js.defs["EPSG:2949"] = "+proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-7
Proj4js.defs["EPSG:2950"] = "+proj=tmerc +lat_0=0 +lon_0=-73.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-8
Proj4js.defs["EPSG:2951"] = "+proj=tmerc +lat_0=0 +lon_0=-76.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-9
Proj4js.defs["EPSG:2952"] = "+proj=tmerc +lat_0=0 +lon_0=-79.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-10
Proj4js.defs["EPSG:2955"] = "+proj=utm +zone=11 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-11N
Proj4js.defs["EPSG:2956"] = "+proj=utm +zone=12 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-12N
Proj4js.defs["EPSG:2957"] = "+proj=utm +zone=13 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-13N
Proj4js.defs["EPSG:2958"] = "+proj=utm +zone=17 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-17N
Proj4js.defs["EPSG:2959"] = "+proj=utm +zone=18 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-18N
Proj4js.defs["EPSG:2960"] = "+proj=utm +zone=19 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-19N
Proj4js.defs["EPSG:2961"] = "+proj=utm +zone=20 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-20N
Proj4js.defs["EPSG:2962"] = "+proj=utm +zone=21 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-21N
Proj4js.defs["EPSG:2944"] = "+proj=tmerc +lat_0=0 +lon_0=-55.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.SCoPQ-2
Proj4js.defs["EPSG:3154"] = "+proj=utm +zone=7 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-7N
Proj4js.defs["EPSG:3155"] = "+proj=utm +zone=8 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-8N
Proj4js.defs["EPSG:3156"] = "+proj=utm +zone=9 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-9N
Proj4js.defs["EPSG:3157"] = "+proj=utm +zone=10 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-10N
Proj4js.defs["EPSG:3158"] = "+proj=utm +zone=14 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-14N
Proj4js.defs["EPSG:3159"] = "+proj=utm +zone=15 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-15N
Proj4js.defs["EPSG:3160"] = "+proj=utm +zone=16 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-16N
Proj4js.defs["EPSG:3161"] = "+proj=lcc +lat_1=44.5 +lat_2=53.5 +lat_0=0 +lon_0=-85 +x_0=930000 +y_0=6430000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.Ontario/MNR
Proj4js.defs["EPSG:3162"] = "+proj=lcc +lat_1=44.5 +lat_2=53.5 +lat_0=0 +lon_0=-85 +x_0=930000 +y_0=6430000 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.Ontario/MNR
Proj4js.defs["EPSG:3401"] = "+proj=tmerc +lat_0=0 +lon_0=-115 +k=0.9992 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANA83-10TM115
Proj4js.defs["EPSG:3400"] = "+proj=tmerc +lat_0=0 +lon_0=-115 +k=0.9992 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANA83-10TM115-EAP
Proj4js.defs["EPSG:2200"] = "+proj=sterea +lat_0=46.5 +lon_0=-66.5 +k=0.999912 +x_0=300000 +y_0=800000 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: CANST-NB-ATS
Proj4js.defs["EPSG:2953"] = "+proj=sterea +lat_0=46.5 +lon_0=-66.5 +k=0.999912 +x_0=2500000 +y_0=7500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: CANST-NB83-CSRS
Proj4js.defs["EPSG:32188"] = "+proj=tmerc +lat_0=0 +lon_0=-73.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-8
Proj4js.defs["EPSG:32189"] = "+proj=tmerc +lat_0=0 +lon_0=-76.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-9
Proj4js.defs["EPSG:32190"] = "+proj=tmerc +lat_0=0 +lon_0=-79.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-10
Proj4js.defs["EPSG:32191"] = "+proj=tmerc +lat_0=0 +lon_0=-82.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-11
Proj4js.defs["EPSG:32192"] = "+proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-12
Proj4js.defs["EPSG:32193"] = "+proj=tmerc +lat_0=0 +lon_0=-84 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-13
Proj4js.defs["EPSG:32195"] = "+proj=tmerc +lat_0=0 +lon_0=-90 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-15
Proj4js.defs["EPSG:32196"] = "+proj=tmerc +lat_0=0 +lon_0=-93 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-16
Proj4js.defs["EPSG:32197"] = "+proj=tmerc +lat_0=0 +lon_0=-96 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CAN83-17
Proj4js.defs["EPSG:2290"] = "+proj=sterea +lat_0=47.25 +lon_0=-63 +k=0.999912 +x_0=700000 +y_0=400000 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: CANST-PEI-ATS
Proj4js.defs["EPSG:2954"] = "+proj=sterea +lat_0=47.25 +lon_0=-63 +k=0.999912 +x_0=400000 +y_0=800000 +ellps=GRS80 +units=m +no_defs "; //Mentor: CANST-PEI-CSRS
Proj4js.defs["EPSG:32183"] = "+proj=tmerc +lat_0=0 +lon_0=-58.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M3
Proj4js.defs["EPSG:32184"] = "+proj=tmerc +lat_0=0 +lon_0=-61.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M4
Proj4js.defs["EPSG:32185"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M5
Proj4js.defs["EPSG:32186"] = "+proj=tmerc +lat_0=0 +lon_0=-67.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M6
Proj4js.defs["EPSG:32187"] = "+proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M7
Proj4js.defs["EPSG:32198"] = "+proj=lcc +lat_1=60 +lat_2=46 +lat_0=44 +lon_0=-68.5 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.QuebecLambert
Proj4js.defs["EPSG:3797"] = "+proj=lcc +lat_1=50 +lat_2=46 +lat_0=44 +lon_0=-70 +x_0=800000 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.MTQ/Lambert
Proj4js.defs["EPSG:3798"] = "+proj=lcc +lat_1=50 +lat_2=46 +lat_0=44 +lon_0=-70 +x_0=800000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.MTQ/Lambert
Proj4js.defs["EPSG:3799"] = "+proj=lcc +lat_1=50 +lat_2=46 +lat_0=44 +lon_0=-70 +x_0=800000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTQ/Lambert
Proj4js.defs["EPSG:2219"] = "+proj=utm +zone=19 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: ATS77.UTM-19N
Proj4js.defs["EPSG:2220"] = "+proj=utm +zone=20 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: ATS77.UTM-20N
Proj4js.defs["EPSG:2947"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-5
Proj4js.defs["EPSG:2294"] = "+proj=tmerc +lat_0=0 +lon_0=-61.5 +k=0.9999 +x_0=4500000 +y_0=0 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: NovaScotia77-4
Proj4js.defs["EPSG:2295"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=5500000 +y_0=0 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: NovaScotia77-5
Proj4js.defs["EPSG:3005"] = "+proj=aea +lat_1=50 +lat_2=58.5 +lat_0=45 +lon_0=-126 +x_0=1000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.BC/Abers
Proj4js.defs["EPSG:3153"] = "+proj=aea +lat_1=50 +lat_2=58.5 +lat_0=45 +lon_0=-126 +x_0=1000000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.BC/Albers
Proj4js.defs["EPSG:4723"] = "+proj=longlat +ellps=clrk66 +towgs84=67.8,106.1,138.8,0,0,0,0 +no_defs "; //Mentor: GrandCayman59.LL
Proj4js.defs["EPSG:4726"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: LittleCayman61.LL
Proj4js.defs["EPSG:24877"] = "+proj=utm +zone=17 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-17S
Proj4js.defs["EPSG:24878"] = "+proj=utm +zone=18 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-18S
Proj4js.defs["EPSG:24879"] = "+proj=utm +zone=19 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-19S
Proj4js.defs["EPSG:29187"] = "+proj=utm +zone=17 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-17S
Proj4js.defs["EPSG:29188"] = "+proj=utm +zone=18 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-18S
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19S
Proj4js.defs["EPSG:4719"] = "+proj=longlat +ellps=intl +towgs84=211,147,111,0,0,0,0 +no_defs "; //Mentor: EASTER.LL
Proj4js.defs["EPSG:2084"] = "+proj=utm +zone=19 +south +ellps=intl +units=m +no_defs "; //Mentor: Hito63a.UTM-19S
Proj4js.defs["EPSG:4254"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: HitoXVIII63a.LL
Proj4js.defs["EPSG:28413"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=13500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-13-P
Proj4js.defs["EPSG:28414"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=14500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-14-P
Proj4js.defs["EPSG:28415"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=15500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-15-P
Proj4js.defs["EPSG:28416"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=16500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-16-P
Proj4js.defs["EPSG:28417"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=17500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-17-P
Proj4js.defs["EPSG:28418"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=18500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-18-P
Proj4js.defs["EPSG:28419"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=19500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-19-P
Proj4js.defs["EPSG:28420"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=20500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-20-P
Proj4js.defs["EPSG:28421"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=21500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-21-P
Proj4js.defs["EPSG:28422"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=22500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-22-P
Proj4js.defs["EPSG:28423"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=23500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CH-23-P
Proj4js.defs["EPSG:21458"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK/CM-105E
Proj4js.defs["EPSG:21459"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK/CM-111E
Proj4js.defs["EPSG:21460"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK/CM-117E
Proj4js.defs["EPSG:21461"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK/CM-123E
Proj4js.defs["EPSG:21462"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK/CM-129E
Proj4js.defs["EPSG:21463"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK/CM-135E
Proj4js.defs["EPSG:21453"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK/CM-75E
Proj4js.defs["EPSG:21454"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK/CM-81E
Proj4js.defs["EPSG:21455"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK/CM-87E
Proj4js.defs["EPSG:21456"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK/CM-93E
Proj4js.defs["EPSG:21457"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK/CM-99E
Proj4js.defs["EPSG:21413"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=13500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-13
Proj4js.defs["EPSG:21414"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=14500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-14
Proj4js.defs["EPSG:21415"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=15500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-15
Proj4js.defs["EPSG:21416"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=16500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-16
Proj4js.defs["EPSG:21417"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=17500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-17
Proj4js.defs["EPSG:21418"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=18500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-18
Proj4js.defs["EPSG:21419"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=19500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-19
Proj4js.defs["EPSG:21420"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=20500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-20
Proj4js.defs["EPSG:21421"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=21500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-21
Proj4js.defs["EPSG:21422"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=22500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-22
Proj4js.defs["EPSG:21423"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=23500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK-23
Proj4js.defs["EPSG:2431"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-102E
Proj4js.defs["EPSG:2432"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-105E
Proj4js.defs["EPSG:2433"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-108E
Proj4js.defs["EPSG:2434"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-111E
Proj4js.defs["EPSG:2435"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-114E
Proj4js.defs["EPSG:2436"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-117E
Proj4js.defs["EPSG:2437"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-120E
Proj4js.defs["EPSG:2438"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-123E
Proj4js.defs["EPSG:2439"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-126E
Proj4js.defs["EPSG:2440"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-129E
Proj4js.defs["EPSG:2441"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-132E
Proj4js.defs["EPSG:2442"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-135E
Proj4js.defs["EPSG:2422"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-75E
Proj4js.defs["EPSG:2423"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-78E
Proj4js.defs["EPSG:2424"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-81E
Proj4js.defs["EPSG:2425"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-84E
Proj4js.defs["EPSG:2426"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-87E
Proj4js.defs["EPSG:2427"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-90E
Proj4js.defs["EPSG:2428"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-93E
Proj4js.defs["EPSG:2429"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-96E
Proj4js.defs["EPSG:2430"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Bjing54/a.GK3d/CM-99E
Proj4js.defs["EPSG:2401"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=25500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-25
Proj4js.defs["EPSG:2402"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=26500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-26
Proj4js.defs["EPSG:2403"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=27500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-27
Proj4js.defs["EPSG:2404"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=28500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-28
Proj4js.defs["EPSG:2405"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=29500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-29
Proj4js.defs["EPSG:2406"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=30500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-30
Proj4js.defs["EPSG:2407"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=31500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-31
Proj4js.defs["EPSG:2408"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=32500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-32
Proj4js.defs["EPSG:2409"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=33500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-33
Proj4js.defs["EPSG:2410"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=34500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-34
Proj4js.defs["EPSG:2411"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=35500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-35
Proj4js.defs["EPSG:2412"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=36500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-36
Proj4js.defs["EPSG:2413"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=37500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-37
Proj4js.defs["EPSG:2414"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=38500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-38
Proj4js.defs["EPSG:2415"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=39500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-39
Proj4js.defs["EPSG:2416"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=40500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-40
Proj4js.defs["EPSG:2417"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=41500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-41
Proj4js.defs["EPSG:2418"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=42500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-42
Proj4js.defs["EPSG:2419"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=43500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-43
Proj4js.defs["EPSG:2420"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=44500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-44
Proj4js.defs["EPSG:2421"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=45500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954/a.GK3d-45
Proj4js.defs["EPSG:4214"] = "+proj=longlat +ellps=krass +no_defs "; //Mentor: Beijing1954/a.LL
Proj4js.defs["EPSG:2343"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-105E
Proj4js.defs["EPSG:2344"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-111E
Proj4js.defs["EPSG:2345"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-117E
Proj4js.defs["EPSG:2346"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-123E
Proj4js.defs["EPSG:2347"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-129E
Proj4js.defs["EPSG:2348"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-135E
Proj4js.defs["EPSG:2338"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-75E
Proj4js.defs["EPSG:2339"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-81E
Proj4js.defs["EPSG:2340"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-87E
Proj4js.defs["EPSG:2341"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-93E
Proj4js.defs["EPSG:2342"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK/CM-99E
Proj4js.defs["EPSG:2327"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=13500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-13
Proj4js.defs["EPSG:2328"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=14500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-14
Proj4js.defs["EPSG:2329"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=15500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-15
Proj4js.defs["EPSG:2330"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=16500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-16
Proj4js.defs["EPSG:2331"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=17500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-17
Proj4js.defs["EPSG:2332"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=18500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-18
Proj4js.defs["EPSG:2333"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=19500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-19
Proj4js.defs["EPSG:2334"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=20500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-20
Proj4js.defs["EPSG:2335"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=21500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-21
Proj4js.defs["EPSG:2336"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=22500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-22
Proj4js.defs["EPSG:2337"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=23500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK-23
Proj4js.defs["EPSG:2379"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-102E
Proj4js.defs["EPSG:2380"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-105E
Proj4js.defs["EPSG:2381"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-108E
Proj4js.defs["EPSG:2382"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-111E
Proj4js.defs["EPSG:2383"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-114E
Proj4js.defs["EPSG:2384"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-117E
Proj4js.defs["EPSG:2385"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-120E
Proj4js.defs["EPSG:2386"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-123E
Proj4js.defs["EPSG:2387"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-126E
Proj4js.defs["EPSG:2388"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-129E
Proj4js.defs["EPSG:2389"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-132E
Proj4js.defs["EPSG:2390"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-135E
Proj4js.defs["EPSG:2370"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-75E
Proj4js.defs["EPSG:2371"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-78E
Proj4js.defs["EPSG:2372"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-81E
Proj4js.defs["EPSG:2373"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-84E
Proj4js.defs["EPSG:2374"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-87E
Proj4js.defs["EPSG:2375"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-90E
Proj4js.defs["EPSG:2376"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-93E
Proj4js.defs["EPSG:2377"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-96E
Proj4js.defs["EPSG:2378"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d/CM-99E
Proj4js.defs["EPSG:2349"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=25500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-25
Proj4js.defs["EPSG:2350"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=26500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-26
Proj4js.defs["EPSG:2351"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=27500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-27
Proj4js.defs["EPSG:2352"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=28500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-28
Proj4js.defs["EPSG:2353"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=29500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-29
Proj4js.defs["EPSG:2354"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=30500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-30
Proj4js.defs["EPSG:2355"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=31500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-31
Proj4js.defs["EPSG:2356"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=32500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-32
Proj4js.defs["EPSG:2357"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=33500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-33
Proj4js.defs["EPSG:2358"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=34500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-34
Proj4js.defs["EPSG:2359"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=35500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-35
Proj4js.defs["EPSG:2360"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=36500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-36
Proj4js.defs["EPSG:2361"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=37500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-37
Proj4js.defs["EPSG:2362"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=38500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-38
Proj4js.defs["EPSG:2363"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=39500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-39
Proj4js.defs["EPSG:2364"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=40500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-40
Proj4js.defs["EPSG:2365"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=41500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-41
Proj4js.defs["EPSG:2366"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=42500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-42
Proj4js.defs["EPSG:2367"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=43500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-43
Proj4js.defs["EPSG:2368"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=44500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-44
Proj4js.defs["EPSG:2369"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=45500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs "; //Mentor: Xian80.GK3d-45
Proj4js.defs["EPSG:4610"] = "+proj=longlat +a=6378140 +b=6356755.288157528 +no_defs "; //Mentor: Xian80.LL
Proj4js.defs["EPSG:4218"] = "+proj=longlat +ellps=intl +towgs84=307,304,-318,0,0,0,0 +no_defs "; //Mentor: Bogota.LL
Proj4js.defs["EPSG:21818"] = "+proj=utm +zone=18 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: Bogota.UTM-18N
Proj4js.defs["EPSG:3114"] = "+proj=tmerc +lat_0=4.596200416666666 +lon_0=-80.07750791666666 +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MAGNA.Columbia-FarWest
Proj4js.defs["EPSG:3115"] = "+proj=tmerc +lat_0=4.596200416666666 +lon_0=-77.07750791666666 +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MAGNA.Columbia-West
Proj4js.defs["EPSG:3116"] = "+proj=tmerc +lat_0=4.596200416666666 +lon_0=-74.07750791666666 +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MAGNA.Columbia-Bogota
Proj4js.defs["EPSG:3117"] = "+proj=tmerc +lat_0=4.596200416666666 +lon_0=-71.07750791666666 +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MAGNA.Columbia-EastCtrl
Proj4js.defs["EPSG:3118"] = "+proj=tmerc +lat_0=4.596200416666666 +lon_0=-68.07750791666666 +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: MAGNA.Columbia-East
Proj4js.defs["EPSG:21896"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-77.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: BOGOTA.Colombia-W
Proj4js.defs["EPSG:21897"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-74.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: BOGOTA.Colombia-Bogota
Proj4js.defs["EPSG:21898"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-71.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: BOGOTA.Colombia-EC
Proj4js.defs["EPSG:21899"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-68.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: BOGOTA.ColombiaE
Proj4js.defs["EPSG:4282"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: PointeNoire60.LL
Proj4js.defs["EPSG:28232"] = "+proj=utm +zone=32 +south +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: PointeNoire60.UTM-32S
Proj4js.defs["EPSG:4701"] = "+proj=longlat +ellps=clrk80 +towgs84=-79.9,-158,-168.9,0,0,0,0 +no_defs "; //Mentor: CongoBelge55.LL
Proj4js.defs["EPSG:31265"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-5
Proj4js.defs["EPSG:31266"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=1 +x_0=6500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-6
Proj4js.defs["EPSG:31267"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=7500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-7
Proj4js.defs["EPSG:31268"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=8500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-8
Proj4js.defs["EPSG:4312"] = "+proj=longlat +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +no_defs "; //Mentor: MGI-AT.LL
Proj4js.defs["EPSG:3795"] = "+proj=lcc +lat_1=23 +lat_2=21.7 +lat_0=22.35 +lon_0=-81 +x_0=500000 +y_0=280296.016 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.CubaNorte/1
Proj4js.defs["EPSG:3796"] = "+proj=lcc +lat_1=21.3 +lat_2=20.13333333333333 +lat_0=20.71666666666667 +lon_0=-76.83333333333333 +x_0=500000 +y_0=229126.939 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.CubaSur/1
Proj4js.defs["EPSG:2065"] = "+proj=krovak +lat_0=49.5 +lon_0=42.5 +alpha=30.28813972222222 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: CzechJTSK/5.Krovak
Proj4js.defs["EPSG:4156"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: CzechJTSK/5.LL
Proj4js.defs["EPSG:2198"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=900000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.Kp2K-Bornholm
Proj4js.defs["EPSG:2196"] = "+proj=tmerc +lat_0=0 +lon_0=9.5 +k=0.99995 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.Kp2K-Jutland
Proj4js.defs["EPSG:4713"] = "+proj=longlat +ellps=clrk80 +towgs84=-79,-129,145,0,0,0,0 +no_defs "; //Mentor: AyabelleLH.LL
Proj4js.defs["EPSG:24817"] = "+proj=utm +zone=17 +ellps=intl +units=m +no_defs "; //Mentor: SA-17N
Proj4js.defs["EPSG:24818"] = "+proj=utm +zone=18 +ellps=intl +units=m +no_defs "; //Mentor: SA-18N
Proj4js.defs["EPSG:24877"] = "+proj=utm +zone=17 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-17S
Proj4js.defs["EPSG:24878"] = "+proj=utm +zone=18 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-18S
Proj4js.defs["EPSG:29168"] = "+proj=utm +zone=18 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-18N
Proj4js.defs["EPSG:29187"] = "+proj=utm +zone=17 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-17S
Proj4js.defs["EPSG:29188"] = "+proj=utm +zone=18 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-18S
Proj4js.defs["EPSG:22991"] = "+proj=tmerc +lat_0=30 +lon_0=35 +k=1 +x_0=300000 +y_0=1100000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.Blue
Proj4js.defs["EPSG:22994"] = "+proj=tmerc +lat_0=30 +lon_0=27 +k=1 +x_0=700000 +y_0=1200000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.ExPurple
Proj4js.defs["EPSG:4229"] = "+proj=longlat +ellps=helmert +no_defs "; //Mentor: Old-Egyp.LL
Proj4js.defs["EPSG:22993"] = "+proj=tmerc +lat_0=30 +lon_0=27 +k=1 +x_0=700000 +y_0=200000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.Purple
Proj4js.defs["EPSG:22992"] = "+proj=tmerc +lat_0=30 +lon_0=31 +k=1 +x_0=615000 +y_0=810000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.Red
Proj4js.defs["EPSG:4258"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: LL-ETRF89
Proj4js.defs["EPSG:2157"] = "+proj=tmerc +lat_0=53.5 +lon_0=-8 +k=0.99982 +x_0=600000 +y_0=750000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: IRENET95.IrishTM
Proj4js.defs["EPSG:4173"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IRENET95.LL
Proj4js.defs["EPSG:2158"] = "+proj=utm +zone=29 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: IRENET95.UTM-29N
Proj4js.defs["EPSG:29902"] = "+proj=tmerc +lat_0=53.5 +lon_0=-8 +k=1.000035 +x_0=200000 +y_0=250000 +a=6377340.189 +b=6356034.447938534 +units=m +no_defs "; //Mentor: TM1965/a.IrishGrid
Proj4js.defs["EPSG:4299"] = "+proj=longlat +a=6377340.189 +b=6356034.447938534 +no_defs "; //Mentor: TM1965/a.LL
Proj4js.defs["EPSG:4188"] = "+proj=longlat +ellps=airy +towgs84=482.5,-130.6,564.6,-1.042,-0.214,-0.631,8.15 +no_defs "; //Mentor: OSNI52.LL
Proj4js.defs["EPSG:29901"] = "+proj=tmerc +lat_0=53.5 +lon_0=-8 +k=1 +x_0=200000 +y_0=250000 +ellps=airy +towgs84=482.5,-130.6,564.6,-1.042,-0.214,-0.631,8.15 +units=m +no_defs "; //Mentor: OSNI52.IrishNtlGrid
Proj4js.defs["EPSG:4262"] = "+proj=longlat +ellps=bessel +towgs84=639,405,60,0,0,0,0 +no_defs "; //Mentor: Massawa.LL
Proj4js.defs["EPSG:26237"] = "+proj=utm +zone=37 +ellps=bessel +towgs84=639,405,60,0,0,0,0 +units=m +no_defs "; //Mentor: Massawa.UTM-37N
Proj4js.defs["EPSG:4133"] = "+proj=longlat +ellps=GRS80 +towgs84=0.055,-0.541,-0.185,0.0183,-0.0003,-0.007,-0.014 +no_defs "; //Mentor: Estonia92a.LL
Proj4js.defs["EPSG:3300"] = "+proj=lcc +lat_1=59.33333333333334 +lat_2=58 +lat_0=57.51755393055556 +lon_0=24 +x_0=500000 +y_0=6375000 +ellps=GRS80 +towgs84=0.055,-0.541,-0.185,0.0183,-0.0003,-0.007,-0.014 +units=m +no_defs "; //Mentor: Estonia92a.Estonia
Proj4js.defs["EPSG:3301"] = "+proj=lcc +lat_1=59.33333333333334 +lat_2=58 +lat_0=57.51755393055556 +lon_0=24 +x_0=500000 +y_0=6375000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: Estonia97.Estonia
Proj4js.defs["EPSG:4180"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Estonia97.LL
Proj4js.defs["EPSG:4201"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Adindan.LL
Proj4js.defs["EPSG:20135"] = "+proj=utm +zone=35 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-35N
Proj4js.defs["EPSG:20136"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-36N
Proj4js.defs["EPSG:20137"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-37N
Proj4js.defs["EPSG:20138"] = "+proj=utm +zone=38 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-38N
Proj4js.defs["EPSG:4292"] = "+proj=longlat +ellps=intl +towgs84=-355,21,72,0,0,0,0 +no_defs "; //Mentor: Sapper.LL
Proj4js.defs["EPSG:29220"] = "+proj=utm +zone=20 +south +ellps=intl +towgs84=-355,21,72,0,0,0,0 +units=m +no_defs "; //Mentor: Sapper.UTM-20S
Proj4js.defs["EPSG:29221"] = "+proj=utm +zone=21 +south +ellps=intl +towgs84=-355,21,72,0,0,0,0 +units=m +no_defs "; //Mentor: Sapper.UTM-21S
Proj4js.defs["EPSG:3460"] = "+proj=tmerc +lat_0=-17 +lon_0=178.75 +k=0.99985 +x_0=2000000 +y_0=4000000 +ellps=WGS72 +units=m +no_defs "; //Mentor: FIJI-MG
Proj4js.defs["EPSG:4720"] = "+proj=longlat +ellps=WGS72 +no_defs "; //Mentor: Fiji1986.LL
Proj4js.defs["EPSG:4721"] = "+proj=longlat +ellps=intl +towgs84=265.025,384.929,-194.046,0,0,0,0 +no_defs "; //Mentor: Fiji56.LL
Proj4js.defs["EPSG:4752"] = "+proj=longlat +a=6378306.3696 +b=6356571.996 +towgs84=51,391,-36,0,0,0,0 +no_defs "; //Mentor: VitiLevu12.LL
Proj4js.defs["EPSG:4748"] = "+proj=longlat +a=6378306.3696 +b=6356571.996 +towgs84=51,391,-36,0,0,0,0 +no_defs "; //Mentor: VanuaLv1915.LL
Proj4js.defs["EPSG:3141"] = "+proj=utm +zone=60 +south +ellps=intl +towgs84=265.025,384.929,-194.046,0,0,0,0 +units=m +no_defs "; //Mentor: Fiji56.UTM-60S
Proj4js.defs["EPSG:3142"] = "+proj=utm +zone=1 +south +ellps=intl +towgs84=265.025,384.929,-194.046,0,0,0,0 +units=m +no_defs "; //Mentor: Fiji56.UTM-1S
Proj4js.defs["EPSG:2391"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: KKJa.Finland-1
Proj4js.defs["EPSG:2392"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=2500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: KKJa.Finland-2
Proj4js.defs["EPSG:2394"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=4500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: KKJa.Finland-4
Proj4js.defs["EPSG:2393"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: KKJa.Finland-UCS
Proj4js.defs["EPSG:4123"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: KKJa.LL
Proj4js.defs["EPSG:3067"] = "+proj=utm +zone=35 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM-35/Fin
Proj4js.defs["EPSG:3126"] = "+proj=tmerc +lat_0=0 +lon_0=19 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-19
Proj4js.defs["EPSG:3127"] = "+proj=tmerc +lat_0=0 +lon_0=20 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-20
Proj4js.defs["EPSG:3128"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-21
Proj4js.defs["EPSG:3129"] = "+proj=tmerc +lat_0=0 +lon_0=22 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-22
Proj4js.defs["EPSG:3130"] = "+proj=tmerc +lat_0=0 +lon_0=23 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-23
Proj4js.defs["EPSG:3131"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-24
Proj4js.defs["EPSG:3132"] = "+proj=tmerc +lat_0=0 +lon_0=25 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-25
Proj4js.defs["EPSG:3133"] = "+proj=tmerc +lat_0=0 +lon_0=26 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-26
Proj4js.defs["EPSG:3134"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-27
Proj4js.defs["EPSG:3135"] = "+proj=tmerc +lat_0=0 +lon_0=28 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-28
Proj4js.defs["EPSG:3136"] = "+proj=tmerc +lat_0=0 +lon_0=29 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-29
Proj4js.defs["EPSG:3137"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-30
Proj4js.defs["EPSG:3138"] = "+proj=tmerc +lat_0=0 +lon_0=31 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-31
Proj4js.defs["EPSG:4807"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +no_defs "; //Mentor: LL-NTF-Grid
Proj4js.defs["EPSG:4171"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: LL-RGF93
Proj4js.defs["EPSG:2154"] = "+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: Lambert93
Proj4js.defs["EPSG:4632"] = "+proj=longlat +ellps=intl +towgs84=-382,-59,-262,0,0,0,0 +no_defs "; //Mentor: Combani1950.LL
Proj4js.defs["EPSG:2980"] = "+proj=utm +zone=38 +south +ellps=intl +towgs84=-382,-59,-262,0,0,0,0 +units=m +no_defs "; //Mentor: Combani1950.UTM-38S
Proj4js.defs["EPSG:2192"] = "+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666667 +k_0=0.99987742 +x_0=600000 +y_0=2200000 +ellps=intl +units=m +no_defs "; //Mentor: ED50.EuroLambert
Proj4js.defs["EPSG:4638"] = "+proj=longlat +ellps=clrk66 +towgs84=30,430,368,0,0,0,0 +no_defs "; //Mentor: Miquelon1950.LL
Proj4js.defs["EPSG:2987"] = "+proj=utm +zone=21 +ellps=clrk66 +towgs84=30,430,368,0,0,0,0 +units=m +no_defs "; //Mentor: Miquelon1950.UTM-21N
Proj4js.defs["EPSG:4639"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: MOP1978a.LL
Proj4js.defs["EPSG:2988"] = "+proj=utm +zone=1 +south +ellps=intl +units=m +no_defs "; //Mentor: MOP1978a.UTM-1S
Proj4js.defs["EPSG:4275"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +no_defs "; //Mentor: NTF.LL
Proj4js.defs["EPSG:4626"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: REUNION.LL
Proj4js.defs["EPSG:3727"] = "+proj=tmerc +lat_0=-21.11666666666667 +lon_0=55.53333333333333 +k=1 +x_0=160000 +y_0=50000 +ellps=intl +units=m +no_defs "; //Mentor: REUNION.Reunion/TM/03
Proj4js.defs["EPSG:4627"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Reunion92.LL
Proj4js.defs["EPSG:2975"] = "+proj=utm +zone=40 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGR92.UTM-40S
Proj4js.defs["EPSG:27561"] = "+proj=lcc +lat_1=49.50000000000001 +lat_0=49.50000000000001 +lon_0=0 +k_0=0.999877341 +x_0=600000 +y_0=200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-1-ClrkIGN
Proj4js.defs["EPSG:27571"] = "+proj=lcc +lat_1=49.50000000000001 +lat_0=49.50000000000001 +lon_0=0 +k_0=0.999877341 +x_0=600000 +y_0=1200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-1C-ClrkIGN
Proj4js.defs["EPSG:27562"] = "+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 +x_0=600000 +y_0=200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-2-ClrkIGN
Proj4js.defs["EPSG:27572"] = "+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-2C-ClrkIGN
Proj4js.defs["EPSG:27563"] = "+proj=lcc +lat_1=44.10000000000001 +lat_0=44.10000000000001 +lon_0=0 +k_0=0.999877499 +x_0=600000 +y_0=200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-3-ClrkIGN
Proj4js.defs["EPSG:27573"] = "+proj=lcc +lat_1=44.10000000000001 +lat_0=44.10000000000001 +lon_0=0 +k_0=0.999877499 +x_0=600000 +y_0=3200000 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-3C-ClrkIGN
Proj4js.defs["EPSG:27564"] = "+proj=lcc +lat_1=42.16500000000001 +lat_0=42.16500000000001 +lon_0=0 +k_0=0.99994471 +x_0=234.358 +y_0=185861.369 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-4-ClrkIGN
Proj4js.defs["EPSG:27574"] = "+proj=lcc +lat_1=42.16500000000001 +lat_0=42.16500000000001 +lon_0=0 +k_0=0.99994471 +x_0=234.358 +y_0=4185861.369 +a=6378249.2 +b=6356515 +towgs84=-168,-60,320,0,0,0,0 +pm=paris +units=m +no_defs "; //Mentor: NTF.Lambert-4C-ClrkIGN
Proj4js.defs["EPSG:4687"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: RGP-Francaise.LL
Proj4js.defs["EPSG:3942"] = "+proj=lcc +lat_1=41.25 +lat_2=42.75 +lat_0=42 +lon_0=3 +x_0=1700000 +y_0=1200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC42
Proj4js.defs["EPSG:3943"] = "+proj=lcc +lat_1=42.25 +lat_2=43.75 +lat_0=43 +lon_0=3 +x_0=1700000 +y_0=2200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC43
Proj4js.defs["EPSG:3944"] = "+proj=lcc +lat_1=43.25 +lat_2=44.75 +lat_0=44 +lon_0=3 +x_0=1700000 +y_0=3200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC44
Proj4js.defs["EPSG:3945"] = "+proj=lcc +lat_1=44.25 +lat_2=45.75 +lat_0=45 +lon_0=3 +x_0=1700000 +y_0=4200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC45
Proj4js.defs["EPSG:3946"] = "+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC46
Proj4js.defs["EPSG:3947"] = "+proj=lcc +lat_1=46.25 +lat_2=47.75 +lat_0=47 +lon_0=3 +x_0=1700000 +y_0=6200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC47
Proj4js.defs["EPSG:3948"] = "+proj=lcc +lat_1=47.25 +lat_2=48.75 +lat_0=48 +lon_0=3 +x_0=1700000 +y_0=7200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC48
Proj4js.defs["EPSG:3949"] = "+proj=lcc +lat_1=48.25 +lat_2=49.75 +lat_0=49 +lon_0=3 +x_0=1700000 +y_0=8200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC49
Proj4js.defs["EPSG:3950"] = "+proj=lcc +lat_1=49.25 +lat_2=50.75 +lat_0=50 +lon_0=3 +x_0=1700000 +y_0=9200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGF93.CC50
Proj4js.defs["EPSG:2978"] = "+proj=utm +zone=7 +south +ellps=intl +units=m +no_defs "; //Mentor: IGN72/NukuHiva.UTM-7S
Proj4js.defs["EPSG:4629"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Tahaa54.LL
Proj4js.defs["EPSG:2977"] = "+proj=utm +zone=5 +south +ellps=intl +units=m +no_defs "; //Mentor: Tahaa54.UTM-5S
Proj4js.defs["EPSG:4628"] = "+proj=longlat +ellps=intl +towgs84=162,117,154,0,0,0,0 +no_defs "; //Mentor: Tahiti52.LL
Proj4js.defs["EPSG:2976"] = "+proj=utm +zone=6 +south +ellps=intl +towgs84=162,117,154,0,0,0,0 +units=m +no_defs "; //Mentor: Tahiti52.UTM-6S
Proj4js.defs["EPSG:4662"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN72/GrandeTerre.LL
Proj4js.defs["EPSG:4688"] = "+proj=longlat +ellps=intl +towgs84=347.103,1078.12,2623.92,-33.8875,70.6773,-9.3943,186.074 +no_defs "; //Mentor: FatuIva/72.LL
Proj4js.defs["EPSG:4689"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN63/Hiva Oa.LL
Proj4js.defs["EPSG:4691"] = "+proj=longlat +ellps=intl +towgs84=215.525,149.593,176.229,-3.2624,-1.692,-1.1571,10.4773 +no_defs "; //Mentor: Moorea87.LL
Proj4js.defs["EPSG:4692"] = "+proj=longlat +ellps=intl +towgs84=217.037,86.959,23.956,0,0,0,0 +no_defs "; //Mentor: Maupiti1983.LL
Proj4js.defs["EPSG:4687"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: RGP-Francaise.LL
Proj4js.defs["EPSG:3296"] = "+proj=utm +zone=5 +south +ellps=GRS80 +units=m +no_defs "; //Mentor: RGP-Francaise.UTM-5S
Proj4js.defs["EPSG:3297"] = "+proj=utm +zone=6 +south +ellps=GRS80 +units=m +no_defs "; //Mentor: RGP-Francaise.UTM-6S
Proj4js.defs["EPSG:3298"] = "+proj=utm +zone=7 +south +ellps=GRS80 +units=m +no_defs "; //Mentor: RGP-Francaise.UTM-7S
Proj4js.defs["EPSG:3299"] = "+proj=utm +zone=8 +south +ellps=GRS80 +units=m +no_defs "; //Mentor: RGP-Francaise.UTM-8S
Proj4js.defs["EPSG:4266"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: Mporaloko_1.LL
Proj4js.defs["EPSG:26632"] = "+proj=utm +zone=32 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Mporaloko_1.UTM-32N
Proj4js.defs["EPSG:26692"] = "+proj=utm +zone=32 +south +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Mporaloko_1.UTM-32S
Proj4js.defs["EPSG:2935"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=41.53333333333333 +k=1 +x_0=1300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A1
Proj4js.defs["EPSG:2936"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=44.53333333333333 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A2
Proj4js.defs["EPSG:2937"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=47.53333333333333 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A3
Proj4js.defs["EPSG:28407"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=7500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-7
Proj4js.defs["EPSG:28408"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=8500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-8
Proj4js.defs["EPSG:31466"] = "+proj=tmerc +lat_0=0 +lon_0=6 +k=1 +x_0=2500000 +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs "; //Mentor: DHDN/2.Gauss3d-2
Proj4js.defs["EPSG:31467"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs "; //Mentor: DHDN/2.Gauss3d-3
Proj4js.defs["EPSG:31468"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=4500000 +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs "; //Mentor: DHDN/2.Gauss3d-4
Proj4js.defs["EPSG:31469"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs "; //Mentor: DHDN/2.Gauss3d-5
Proj4js.defs["EPSG:4314"] = "+proj=longlat +ellps=bessel +datum=potsdam +no_defs "; //Mentor: DHDN/2.LL
Proj4js.defs["EPSG:3068"] = "+proj=cass +lat_0=52.41864827777778 +lon_0=13.62720366666667 +x_0=40000 +y_0=10000 +ellps=bessel +datum=potsdam +units=m +no_defs "; //Mentor: DHDN/2.Berlin/Cassini
Proj4js.defs["EPSG:3396"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: DHDN/BeTA.Gauss3d-3
Proj4js.defs["EPSG:3397"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=4500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: DHDN/BeTA.Gauss3d-4
Proj4js.defs["EPSG:3399"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: DHDN/BeTA.Gauss3d-5
Proj4js.defs["EPSG:3398"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=4500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RD83.Gauss3d-4
Proj4js.defs["EPSG:3398"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=4500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RD83/BeTA.Gauss3d-4
Proj4js.defs["EPSG:2136"] = "+proj=tmerc +lat_0=4.666666666666667 +lon_0=-1 +k=0.99975 +x_0=274319.7391633579 +y_0=0 +a=6378300 +b=6356751.689189189 +towgs84=-199,32,322,0,0,0,0 +to_meter=0.3047997101815088 +no_defs "; //Mentor: Accra1929.GhanaNational
Proj4js.defs["EPSG:4168"] = "+proj=longlat +a=6378300 +b=6356751.689189189 +towgs84=-199,32,322,0,0,0,0 +no_defs "; //Mentor: Accra1929.LL
Proj4js.defs["EPSG:2137"] = "+proj=tmerc +lat_0=0 +lon_0=-1 +k=0.9996 +x_0=500000 +y_0=0 +a=6378300 +b=6356751.689189189 +towgs84=-199,32,322,0,0,0,0 +units=m +no_defs "; //Mentor: Accra1929.TM-1NW
Proj4js.defs["EPSG:25000"] = "+proj=tmerc +lat_0=4.666666666666667 +lon_0=-1 +k=0.99975 +x_0=274319.51 +y_0=0 +ellps=clrk80 +towgs84=-130,29,364,0,0,0,0 +units=m +no_defs "; //Mentor: Leigon_1.GhanaMetreGrid
Proj4js.defs["EPSG:4250"] = "+proj=longlat +ellps=clrk80 +towgs84=-130,29,364,0,0,0,0 +no_defs "; //Mentor: Leigon_1.LL
Proj4js.defs["EPSG:4277"] = "+proj=longlat +ellps=airy +datum=OSGB36 +no_defs "; //Mentor: OSGB.LL
Proj4js.defs["EPSG:27700"] = "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs "; //Mentor: BritishNatGrid
Proj4js.defs["EPSG:27700"] = "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs "; //Mentor: BritishNatGrid
Proj4js.defs["EPSG:4121"] = "+proj=longlat +ellps=GRS80 +towgs84=-199.87,74.79,246.62,0,0,0,0 +no_defs "; //Mentor: GGRS87.LL
Proj4js.defs["EPSG:2100"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9996 +x_0=500000 +y_0=0 +ellps=GRS80 +towgs84=-199.87,74.79,246.62,0,0,0,0 +units=m +no_defs "; //Mentor: GreekGRS87.GreekGrid
Proj4js.defs["EPSG:4196"] = "+proj=longlat +ellps=intl +towgs84=-45,417,-3.5,0,0,0.814,-0.6 +no_defs "; //Mentor: Ammassalik58a.LL
Proj4js.defs["EPSG:4194"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Qornoq1927.LL
Proj4js.defs["EPSG:2216"] = "+proj=utm +zone=22 +ellps=intl +units=m +no_defs "; //Mentor: Qornoq1927.UTM-22N
Proj4js.defs["EPSG:2217"] = "+proj=utm +zone=23 +ellps=intl +units=m +no_defs "; //Mentor: Qornoq1927.UTM-23N
Proj4js.defs["EPSG:4195"] = "+proj=longlat +ellps=intl +towgs84=105,326,-102.5,0,0,0.814,-0.6 +no_defs "; //Mentor: Scoresbysund52a.LL
Proj4js.defs["EPSG:4747"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Greenland1996.LL
Proj4js.defs["EPSG:3178"] = "+proj=utm +zone=18 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-18N
Proj4js.defs["EPSG:3179"] = "+proj=utm +zone=19 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-19N
Proj4js.defs["EPSG:3180"] = "+proj=utm +zone=20 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-20N
Proj4js.defs["EPSG:3181"] = "+proj=utm +zone=21 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-21N
Proj4js.defs["EPSG:3182"] = "+proj=utm +zone=22 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-22N
Proj4js.defs["EPSG:3183"] = "+proj=utm +zone=23 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-23N
Proj4js.defs["EPSG:3184"] = "+proj=utm +zone=24 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-24N
Proj4js.defs["EPSG:3185"] = "+proj=utm +zone=25 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-25N
Proj4js.defs["EPSG:3186"] = "+proj=utm +zone=26 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-26N
Proj4js.defs["EPSG:3187"] = "+proj=utm +zone=27 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-27N
Proj4js.defs["EPSG:3188"] = "+proj=utm +zone=28 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-28N
Proj4js.defs["EPSG:3189"] = "+proj=utm +zone=29 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GR96.UTM-29N
Proj4js.defs["EPSG:2003"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +towgs84=72,213.7,93,0,0,0,0 +units=m +no_defs "; //Mentor: Grenada1953.BWIgrid
Proj4js.defs["EPSG:4603"] = "+proj=longlat +ellps=clrk80 +towgs84=72,213.7,93,0,0,0,0 +no_defs "; //Mentor: Grenada1953.LL
Proj4js.defs["EPSG:32061"] = "+proj=lcc +lat_1=16.81666666666667 +lat_0=16.81666666666667 +lon_0=-90.33333333333333 +k_0=0.99992226 +x_0=500000 +y_0=292209.579 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.GuatemalaN
Proj4js.defs["EPSG:32062"] = "+proj=lcc +lat_1=14.9 +lat_0=14.9 +lon_0=-90.33333333333333 +k_0=0.99989906 +x_0=500000 +y_0=325992.681 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.GuatemalaS
Proj4js.defs["EPSG:4315"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-23,259,-9,0,0,0,0 +no_defs "; //Mentor: Conakry1905.LL
Proj4js.defs["EPSG:31528"] = "+proj=utm +zone=28 +a=6378249.2 +b=6356515 +towgs84=-23,259,-9,0,0,0,0 +units=m +no_defs "; //Mentor: Conakry1905.UTM-28N
Proj4js.defs["EPSG:31529"] = "+proj=utm +zone=29 +a=6378249.2 +b=6356515 +towgs84=-23,259,-9,0,0,0,0 +units=m +no_defs "; //Mentor: Conakry1905.UTM-29N
Proj4js.defs["EPSG:4155"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-83,37,124,0,0,0,0 +no_defs "; //Mentor: Dabola1981.LL
Proj4js.defs["EPSG:3461"] = "+proj=utm +zone=28 +a=6378249.2 +b=6356515 +towgs84=-83,37,124,0,0,0,0 +units=m +no_defs "; //Mentor: Dabola1981.UTM-28N/01
Proj4js.defs["EPSG:3462"] = "+proj=utm +zone=29 +a=6378249.2 +b=6356515 +towgs84=-83,37,124,0,0,0,0 +units=m +no_defs "; //Mentor: Dabola1981.UTM-29N/01
Proj4js.defs["EPSG:4165"] = "+proj=longlat +ellps=intl +towgs84=-173,253,27,0,0,0,0 +no_defs "; //Mentor: Bissau_1.LL
Proj4js.defs["EPSG:2095"] = "+proj=utm +zone=28 +ellps=intl +towgs84=-173,253,27,0,0,0,0 +units=m +no_defs "; //Mentor: Bissau_1.UTM-28N
Proj4js.defs["EPSG:24820"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: SA-20N
Proj4js.defs["EPSG:24821"] = "+proj=utm +zone=21 +ellps=intl +units=m +no_defs "; //Mentor: SA-21N
Proj4js.defs["EPSG:29170"] = "+proj=utm +zone=20 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-20N
Proj4js.defs["EPSG:29171"] = "+proj=utm +zone=21 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-21N
Proj4js.defs["EPSG:4623"] = "+proj=longlat +ellps=intl +towgs84=-186,230,110,0,0,0,0 +no_defs "; //Mentor: CSG1967.LL
Proj4js.defs["EPSG:2971"] = "+proj=utm +zone=22 +ellps=intl +towgs84=-186,230,110,0,0,0,0 +units=m +no_defs "; //Mentor: CSG1967.UTM-22N
Proj4js.defs["EPSG:4624"] = "+proj=longlat +ellps=GRS80 +towgs84=2,2,-2,0,0,0,0 +no_defs "; //Mentor: Guyane95a.LL
Proj4js.defs["EPSG:2972"] = "+proj=utm +zone=22 +ellps=GRS80 +towgs84=2,2,-2,0,0,0,0 +units=m +no_defs "; //Mentor: RGFG95a.UTM-22N
Proj4js.defs["EPSG:32649"] = "+proj=utm +zone=49 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-49N
Proj4js.defs["EPSG:32650"] = "+proj=utm +zone=50 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-50N
Proj4js.defs["EPSG:4738"] = "+proj=longlat +a=6378293.645208759 +b=6356617.987679838 +no_defs "; //Mentor: HONGKONG.LL
Proj4js.defs["EPSG:2326"] = "+proj=tmerc +lat_0=22.31213333333334 +lon_0=114.1785555555556 +k=1 +x_0=836694.05 +y_0=819069.8 +ellps=intl +towgs84=-162.619,-276.959,-161.764,0.067753,-2.24365,-1.15883,-1.09425 +units=m +no_defs "; //Mentor: HongKong80a.GridSystem
Proj4js.defs["EPSG:4611"] = "+proj=longlat +ellps=intl +towgs84=-162.619,-276.959,-161.764,0.067753,-2.24365,-1.15883,-1.09425 +no_defs "; //Mentor: HongKong80a.LL
Proj4js.defs["EPSG:4739"] = "+proj=longlat +ellps=intl +towgs84=-156,-271,-189,0,0,0,0 +no_defs "; //Mentor: HongKong63/1967.LL
Proj4js.defs["EPSG:23700"] = "+proj=somerc +lat_0=47.14439372222222 +lon_0=19.04857177777778 +k_0=0.99993 +x_0=650000 +y_0=200000 +ellps=GRS67 +units=m +no_defs "; //Mentor: HD72/7P.EOV
Proj4js.defs["EPSG:4237"] = "+proj=longlat +ellps=GRS67 +no_defs "; //Mentor: HD72/7P.LL
Proj4js.defs["EPSG:4658"] = "+proj=longlat +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +no_defs "; //Mentor: HJORSEY.LL
Proj4js.defs["EPSG:3057"] = "+proj=lcc +lat_1=64.25 +lat_2=65.75 +lat_0=65 +lon_0=-19 +x_0=500000 +y_0=500000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: ISN93.IcelandGrid
Proj4js.defs["EPSG:4659"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IslandsNet1993.LL
Proj4js.defs["EPSG:4657"] = "+proj=longlat +a=6377019.27 +b=6355762.5391 +towgs84=-28,199,5,0,0,0,0 +no_defs "; //Mentor: Reykjavik.LL
Proj4js.defs["EPSG:3054"] = "+proj=utm +zone=26 +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +units=m +no_defs "; //Mentor: Hjorsey.UTM-26N
Proj4js.defs["EPSG:3055"] = "+proj=utm +zone=27 +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +units=m +no_defs "; //Mentor: Hjorsey.UTM-27N
Proj4js.defs["EPSG:3056"] = "+proj=utm +zone=28 +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +units=m +no_defs "; //Mentor: Hjorsey.UTM-28N
Proj4js.defs["EPSG:24370"] = "+proj=lcc +lat_1=39.5 +lat_0=39.5 +lon_0=68 +k_0=0.99846154 +x_0=2153865.73916853 +y_0=2368292.194628102 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-0/a
Proj4js.defs["EPSG:24371"] = "+proj=lcc +lat_1=32.5 +lat_0=32.5 +lon_0=68 +k_0=0.99878641 +x_0=2743195.592233322 +y_0=914398.5307444407 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-I/a
Proj4js.defs["EPSG:24372"] = "+proj=lcc +lat_1=26 +lat_0=26 +lon_0=74 +k_0=0.99878641 +x_0=2743195.592233322 +y_0=914398.5307444407 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-IIA/a
Proj4js.defs["EPSG:24382"] = "+proj=lcc +lat_1=26 +lat_0=26 +lon_0=90 +k_0=0.99878641 +x_0=2743195.592233322 +y_0=914398.5307444407 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-IIB/a
Proj4js.defs["EPSG:24373"] = "+proj=lcc +lat_1=19 +lat_0=19 +lon_0=80 +k_0=0.99878641 +x_0=2743195.592233322 +y_0=914398.5307444407 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-IIIA/a
Proj4js.defs["EPSG:24374"] = "+proj=lcc +lat_1=12 +lat_0=12 +lon_0=80 +k_0=0.99878641 +x_0=2743195.592233322 +y_0=914398.5307444407 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-IVA/a
Proj4js.defs["EPSG:3176"] = "+proj=tmerc +lat_0=0 +lon_0=106 +k=0.9996 +x_0=500000 +y_0=0 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Indian1960/E.TM-106NE
Proj4js.defs["EPSG:3148"] = "+proj=utm +zone=48 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Indian1960/E.UTM-48N
Proj4js.defs["EPSG:3149"] = "+proj=utm +zone=49 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Indian1960/E.UTM-49N
Proj4js.defs["EPSG:4239"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +towgs84=217,823,299,0,0,0,0 +no_defs "; //Mentor: Indian1954.LL
Proj4js.defs["EPSG:23946"] = "+proj=utm +zone=46 +a=6377276.345 +b=6356075.41314024 +towgs84=217,823,299,0,0,0,0 +units=m +no_defs "; //Mentor: Indian1954.UTM-46N
Proj4js.defs["EPSG:23947"] = "+proj=utm +zone=47 +a=6377276.345 +b=6356075.41314024 +towgs84=217,823,299,0,0,0,0 +units=m +no_defs "; //Mentor: Indian1954.UTM-47N
Proj4js.defs["EPSG:23948"] = "+proj=utm +zone=48 +a=6377276.345 +b=6356075.41314024 +towgs84=217,823,299,0,0,0,0 +units=m +no_defs "; //Mentor: Indian1954.UTM-48N
Proj4js.defs["EPSG:4131"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Indian1960/E.LL
Proj4js.defs["EPSG:4240"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Indian75/E.LL
Proj4js.defs["EPSG:24047"] = "+proj=utm +zone=47 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Indian75/E.UTM-47N
Proj4js.defs["EPSG:24048"] = "+proj=utm +zone=48 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Indian75/E.UTM-48N
Proj4js.defs["EPSG:24375"] = "+proj=lcc +lat_1=26 +lat_0=26 +lon_0=90 +k_0=0.99878641 +x_0=2743185.69 +y_0=914395.23 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Kalianpur1937.India-IIb
Proj4js.defs["EPSG:4144"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Kalianpur1937.LL
Proj4js.defs["EPSG:24305"] = "+proj=utm +zone=45 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Kalianpur1937.UTM-45N
Proj4js.defs["EPSG:24306"] = "+proj=utm +zone=46 +a=6377276.345 +b=6356075.41314024 +units=m +no_defs "; //Mentor: Kalianpur1937.UTM-46N
Proj4js.defs["EPSG:24376"] = "+proj=lcc +lat_1=32.5 +lat_0=32.5 +lon_0=68 +k_0=0.99878641 +x_0=2743196.4 +y_0=914398.8 +a=6377301.243 +b=6356100.230165384 +units=m +no_defs "; //Mentor: Kalianpur1962.India-I
Proj4js.defs["EPSG:24377"] = "+proj=lcc +lat_1=26 +lat_0=26 +lon_0=74 +k_0=0.99878641 +x_0=2743196.4 +y_0=914398.8 +a=6377301.243 +b=6356100.230165384 +units=m +no_defs "; //Mentor: Kalianpur1962.India-IIa
Proj4js.defs["EPSG:4145"] = "+proj=longlat +a=6377301.243 +b=6356100.230165384 +no_defs "; //Mentor: Kalianpur1962.LL
Proj4js.defs["EPSG:24311"] = "+proj=utm +zone=41 +a=6377301.243 +b=6356100.230165384 +units=m +no_defs "; //Mentor: Kalianpur1962.UTM-41N
Proj4js.defs["EPSG:24312"] = "+proj=utm +zone=42 +a=6377301.243 +b=6356100.230165384 +units=m +no_defs "; //Mentor: Kalianpur1962.UTM-42N
Proj4js.defs["EPSG:24313"] = "+proj=utm +zone=43 +a=6377301.243 +b=6356100.230165384 +units=m +no_defs "; //Mentor: Kalianpur1962.UTM-43N
Proj4js.defs["EPSG:24378"] = "+proj=lcc +lat_1=32.5 +lat_0=32.5 +lon_0=68 +k_0=0.99878641 +x_0=2743195.5 +y_0=914398.5 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.India-I
Proj4js.defs["EPSG:24379"] = "+proj=lcc +lat_1=26 +lat_0=26 +lon_0=74 +k_0=0.99878641 +x_0=2743195.5 +y_0=914398.5 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.India-IIa
Proj4js.defs["EPSG:24380"] = "+proj=lcc +lat_1=26 +lat_0=26 +lon_0=90 +k_0=0.99878641 +x_0=2743195.5 +y_0=914398.5 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.India-IIb
Proj4js.defs["EPSG:24381"] = "+proj=lcc +lat_1=19 +lat_0=19 +lon_0=80 +k_0=0.99878641 +x_0=2743195.5 +y_0=914398.5 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.India-III
Proj4js.defs["EPSG:24383"] = "+proj=lcc +lat_1=12 +lat_0=12 +lon_0=80 +k_0=0.99878641 +x_0=2743195.5 +y_0=914398.5 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.India-IV
Proj4js.defs["EPSG:4146"] = "+proj=longlat +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +no_defs "; //Mentor: Kalianpur1975.LL
Proj4js.defs["EPSG:24342"] = "+proj=utm +zone=42 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.UTM-42N
Proj4js.defs["EPSG:24343"] = "+proj=utm +zone=43 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.UTM-43N
Proj4js.defs["EPSG:24344"] = "+proj=utm +zone=44 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.UTM-44N
Proj4js.defs["EPSG:24345"] = "+proj=utm +zone=45 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.UTM-45N
Proj4js.defs["EPSG:24346"] = "+proj=utm +zone=46 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.UTM-46N
Proj4js.defs["EPSG:24347"] = "+proj=utm +zone=47 +a=6377299.151 +b=6356098.145120132 +towgs84=295,736,257,0,0,0,0 +units=m +no_defs "; //Mentor: Kalianpur1975.UTM-47N
Proj4js.defs["EPSG:4682"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +no_defs "; //Mentor: Gulshan303.LL
Proj4js.defs["EPSG:4211"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: Batavia_1.LL
Proj4js.defs["EPSG:3001"] = "+proj=merc +lon_0=110 +k=0.997 +x_0=3900000 +y_0=900000 +ellps=bessel +units=m +no_defs "; //Mentor: Batavia_1.NEIEZ/01
Proj4js.defs["EPSG:2308"] = "+proj=tmerc +lat_0=0 +lon_0=109 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=bessel +units=m +no_defs "; //Mentor: Batavia_1.TM-109SE
Proj4js.defs["EPSG:21148"] = "+proj=utm +zone=48 +south +ellps=bessel +units=m +no_defs "; //Mentor: Batavia_1.UTM-48S
Proj4js.defs["EPSG:21149"] = "+proj=utm +zone=49 +south +ellps=bessel +units=m +no_defs "; //Mentor: Batavia_1.UTM-49S
Proj4js.defs["EPSG:21150"] = "+proj=utm +zone=50 +south +ellps=bessel +units=m +no_defs "; //Mentor: Batavia_1.UTM-50S
Proj4js.defs["EPSG:4219"] = "+proj=longlat +ellps=bessel +towgs84=-384,664,-48,0,0,0,0 +no_defs "; //Mentor: BukitRimpah_1.LL
Proj4js.defs["EPSG:4238"] = "+proj=longlat +a=6378160 +b=6356774.50408554 +no_defs "; //Mentor: Indonesian1974.LL
Proj4js.defs["EPSG:23846"] = "+proj=utm +zone=46 +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-46N
Proj4js.defs["EPSG:23847"] = "+proj=utm +zone=47 +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-47N
Proj4js.defs["EPSG:23887"] = "+proj=utm +zone=47 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-47S
Proj4js.defs["EPSG:23848"] = "+proj=utm +zone=48 +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-48N
Proj4js.defs["EPSG:23888"] = "+proj=utm +zone=48 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-48S
Proj4js.defs["EPSG:23849"] = "+proj=utm +zone=49 +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-49N
Proj4js.defs["EPSG:23889"] = "+proj=utm +zone=49 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-49S
Proj4js.defs["EPSG:23850"] = "+proj=utm +zone=50 +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-50N
Proj4js.defs["EPSG:23890"] = "+proj=utm +zone=50 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-50S
Proj4js.defs["EPSG:23851"] = "+proj=utm +zone=51 +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-51N
Proj4js.defs["EPSG:23891"] = "+proj=utm +zone=51 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-51S
Proj4js.defs["EPSG:23852"] = "+proj=utm +zone=52 +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-52N
Proj4js.defs["EPSG:23892"] = "+proj=utm +zone=52 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-52S
Proj4js.defs["EPSG:23893"] = "+proj=utm +zone=53 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-53S
Proj4js.defs["EPSG:23894"] = "+proj=utm +zone=54 +south +a=6378160 +b=6356774.50408554 +units=m +no_defs "; //Mentor: Indonesian1974.UTM-54S
Proj4js.defs["EPSG:4257"] = "+proj=longlat +ellps=bessel +towgs84=-587.8,519.75,145.76,0,0,0,0 +no_defs "; //Mentor: Makassar/E.LL
Proj4js.defs["EPSG:3002"] = "+proj=merc +lon_0=110 +k=0.997 +x_0=3900000 +y_0=900000 +ellps=bessel +towgs84=-587.8,519.75,145.76,0,0,0,0 +units=m +no_defs "; //Mentor: Makassar/E.NEIEZ
Proj4js.defs["EPSG:2933"] = "+proj=utm +zone=50 +south +ellps=bessel +units=m +no_defs "; //Mentor: GunungSegara.UTM-50S
Proj4js.defs["EPSG:4613"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: GunungSegara.LL
Proj4js.defs["EPSG:3000"] = "+proj=merc +lon_0=110 +k=0.997 +x_0=3900000 +y_0=900000 +ellps=bessel +units=m +no_defs "; //Mentor: GunungSegara.NEIEZ/01
Proj4js.defs["EPSG:4755"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Nasional1995.LL
Proj4js.defs["EPSG:23866"] = "+proj=utm +zone=46 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-46N
Proj4js.defs["EPSG:23867"] = "+proj=utm +zone=47 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-47N
Proj4js.defs["EPSG:23868"] = "+proj=utm +zone=48 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-48N
Proj4js.defs["EPSG:23869"] = "+proj=utm +zone=49 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-49N
Proj4js.defs["EPSG:23870"] = "+proj=utm +zone=50 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-50N
Proj4js.defs["EPSG:23871"] = "+proj=utm +zone=51 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-51N
Proj4js.defs["EPSG:23872"] = "+proj=utm +zone=52 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-52N
Proj4js.defs["EPSG:23877"] = "+proj=utm +zone=47 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-47S
Proj4js.defs["EPSG:23878"] = "+proj=utm +zone=48 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-48S
Proj4js.defs["EPSG:23879"] = "+proj=utm +zone=49 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-49S
Proj4js.defs["EPSG:23880"] = "+proj=utm +zone=50 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-50S
Proj4js.defs["EPSG:23881"] = "+proj=utm +zone=51 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-51S
Proj4js.defs["EPSG:23882"] = "+proj=utm +zone=52 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-52S
Proj4js.defs["EPSG:23883"] = "+proj=utm +zone=53 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-53S
Proj4js.defs["EPSG:23884"] = "+proj=utm +zone=54 +south +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: DGN95.UTM-54S
Proj4js.defs["EPSG:2057"] = "+proj=omerc +lat_0=27.51882880555555 +lonc=52.60353916666667 +alpha=0.5716611944444444 +k=0.999895934 +x_0=658377.437 +y_0=3044969.194 +ellps=intl +towgs84=-133.63,-157.5,-158.62,0,0,0,0 +units=m +no_defs "; //Mentor: Rassadiran_1.NakhlTaqi
Proj4js.defs["EPSG:4153"] = "+proj=longlat +ellps=intl +towgs84=-133.63,-157.5,-158.62,0,0,0,0 +no_defs "; //Mentor: Rassadiran_1.LL
Proj4js.defs["EPSG:4693"] = "+proj=longlat +ellps=WGS84 +towgs84=0,-0.15,0.68,0,0,0,0 +no_defs "; //Mentor: Nakhl-eGhanem.LL
Proj4js.defs["EPSG:3200"] = "+proj=lcc +lat_1=32.5 +lat_0=32.5 +lon_0=45 +k_0=0.9987864078000001 +x_0=1500000 +y_0=1166200 +ellps=clrk80 +units=m +no_defs "; //Mentor: Final1958.Iraq
Proj4js.defs["EPSG:4132"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Final1958.LL
Proj4js.defs["EPSG:4667"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IraqKuwait1992.LL
Proj4js.defs["EPSG:4743"] = "+proj=longlat +ellps=clrk80 +towgs84=84.1,-320.1,218.7,0,0,0,0 +no_defs "; //Mentor: Karbala79/P.LL
Proj4js.defs["EPSG:4141"] = "+proj=longlat +ellps=GRS80 +towgs84=-48,55,52,0,0,0,0 +no_defs "; //Mentor: Israel.LL
Proj4js.defs["EPSG:2039"] = "+proj=tmerc +lat_0=31.73439361111111 +lon_0=35.20451694444445 +k=1.0000067 +x_0=219529.584 +y_0=626907.39 +ellps=GRS80 +towgs84=-48,55,52,0,0,0,0 +units=m +no_defs "; //Mentor: Israel.IsraeliGrid
Proj4js.defs["EPSG:3003"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: MonteMario_1.Italy-1
Proj4js.defs["EPSG:3004"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9996 +x_0=2520000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: MonteMario_1.Italy-2
Proj4js.defs["EPSG:4265"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: ROME1940.LL
Proj4js.defs["EPSG:4143"] = "+proj=longlat +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +no_defs "; //Mentor: Abidjan1987.LL
Proj4js.defs["EPSG:2165"] = "+proj=tmerc +lat_0=0 +lon_0=-5 +k=0.9996 +x_0=500000 +y_0=0 +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +units=m +no_defs "; //Mentor: Abidjan1987.TM-5NW
Proj4js.defs["EPSG:2043"] = "+proj=utm +zone=29 +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +units=m +no_defs "; //Mentor: Abidjan1987.UTM-29N
Proj4js.defs["EPSG:2041"] = "+proj=utm +zone=30 +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +units=m +no_defs "; //Mentor: Abidjan1987.UTM-30N
Proj4js.defs["EPSG:4142"] = "+proj=longlat +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +no_defs "; //Mentor: Locodjo1965.LL
Proj4js.defs["EPSG:2164"] = "+proj=tmerc +lat_0=0 +lon_0=-5 +k=0.9996 +x_0=500000 +y_0=0 +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +units=m +no_defs "; //Mentor: Locodjo1965.TM-5NW
Proj4js.defs["EPSG:2040"] = "+proj=utm +zone=30 +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +units=m +no_defs "; //Mentor: Locodjo1965.UTM-30N
Proj4js.defs["EPSG:2042"] = "+proj=utm +zone=29 +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +units=m +no_defs "; //Mentor: Locodjo1965.UTM-29N
Proj4js.defs["EPSG:24200"] = "+proj=lcc +lat_1=18 +lat_0=18 +lon_0=-77 +k_0=1 +x_0=250000 +y_0=150000 +ellps=clrk66 +units=m +no_defs "; //Mentor: Jamaica1969.NtlGrid
Proj4js.defs["EPSG:4242"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Jamaica1969.LL
Proj4js.defs["EPSG:2443"] = "+proj=tmerc +lat_0=33 +lon_0=129.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-01
Proj4js.defs["EPSG:2444"] = "+proj=tmerc +lat_0=33 +lon_0=131 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-02
Proj4js.defs["EPSG:2445"] = "+proj=tmerc +lat_0=36 +lon_0=132.1666666666667 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-03
Proj4js.defs["EPSG:2446"] = "+proj=tmerc +lat_0=33 +lon_0=133.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-04
Proj4js.defs["EPSG:2447"] = "+proj=tmerc +lat_0=36 +lon_0=134.3333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-05
Proj4js.defs["EPSG:2448"] = "+proj=tmerc +lat_0=36 +lon_0=136 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-06
Proj4js.defs["EPSG:2449"] = "+proj=tmerc +lat_0=36 +lon_0=137.1666666666667 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-07
Proj4js.defs["EPSG:2450"] = "+proj=tmerc +lat_0=36 +lon_0=138.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-08
Proj4js.defs["EPSG:2451"] = "+proj=tmerc +lat_0=36 +lon_0=139.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-09
Proj4js.defs["EPSG:2452"] = "+proj=tmerc +lat_0=40 +lon_0=140.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-10
Proj4js.defs["EPSG:2453"] = "+proj=tmerc +lat_0=44 +lon_0=140.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-11
Proj4js.defs["EPSG:2454"] = "+proj=tmerc +lat_0=44 +lon_0=142.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-12
Proj4js.defs["EPSG:2456"] = "+proj=tmerc +lat_0=26 +lon_0=142 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-14
Proj4js.defs["EPSG:2457"] = "+proj=tmerc +lat_0=26 +lon_0=127.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-15
Proj4js.defs["EPSG:2458"] = "+proj=tmerc +lat_0=26 +lon_0=124 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-16
Proj4js.defs["EPSG:2459"] = "+proj=tmerc +lat_0=26 +lon_0=131 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-17
Proj4js.defs["EPSG:2460"] = "+proj=tmerc +lat_0=20 +lon_0=136 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-18
Proj4js.defs["EPSG:2461"] = "+proj=tmerc +lat_0=26 +lon_0=154 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K-19
Proj4js.defs["EPSG:4612"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: LL-JGD2K-7P
Proj4js.defs["EPSG:30161"] = "+proj=tmerc +lat_0=33 +lon_0=129.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-I
Proj4js.defs["EPSG:30162"] = "+proj=tmerc +lat_0=33 +lon_0=131 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-II
Proj4js.defs["EPSG:30163"] = "+proj=tmerc +lat_0=36 +lon_0=132.1666666666667 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-III
Proj4js.defs["EPSG:30164"] = "+proj=tmerc +lat_0=33 +lon_0=133.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-IV
Proj4js.defs["EPSG:30169"] = "+proj=tmerc +lat_0=36 +lon_0=139.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-IX
Proj4js.defs["EPSG:30165"] = "+proj=tmerc +lat_0=36 +lon_0=134.3333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-V
Proj4js.defs["EPSG:30166"] = "+proj=tmerc +lat_0=36 +lon_0=136 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-VI
Proj4js.defs["EPSG:30167"] = "+proj=tmerc +lat_0=36 +lon_0=137.1666666666667 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-VII
Proj4js.defs["EPSG:30168"] = "+proj=tmerc +lat_0=36 +lon_0=138.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-VIII
Proj4js.defs["EPSG:30170"] = "+proj=tmerc +lat_0=40 +lon_0=140.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-X
Proj4js.defs["EPSG:30171"] = "+proj=tmerc +lat_0=44 +lon_0=140.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XI
Proj4js.defs["EPSG:30172"] = "+proj=tmerc +lat_0=44 +lon_0=142.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XII
Proj4js.defs["EPSG:30173"] = "+proj=tmerc +lat_0=44 +lon_0=144.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XIII
Proj4js.defs["EPSG:30174"] = "+proj=tmerc +lat_0=26 +lon_0=142 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XIV
Proj4js.defs["EPSG:30175"] = "+proj=tmerc +lat_0=26 +lon_0=127.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XV
Proj4js.defs["EPSG:30176"] = "+proj=tmerc +lat_0=26 +lon_0=124 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XVI
Proj4js.defs["EPSG:30177"] = "+proj=tmerc +lat_0=26 +lon_0=131 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XVII
Proj4js.defs["EPSG:30178"] = "+proj=tmerc +lat_0=20 +lon_0=136 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XVIII
Proj4js.defs["EPSG:30179"] = "+proj=tmerc +lat_0=26 +lon_0=154 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Tokyo.PlnRctCS-XIX
Proj4js.defs["EPSG:3092"] = "+proj=utm +zone=51 +ellps=bessel +units=m +no_defs "; //Mentor: TOKYO.UTM-51N
Proj4js.defs["EPSG:3093"] = "+proj=utm +zone=52 +ellps=bessel +units=m +no_defs "; //Mentor: TOKYO.UTM-52N
Proj4js.defs["EPSG:3094"] = "+proj=utm +zone=53 +ellps=bessel +units=m +no_defs "; //Mentor: TOKYO.UTM-53N
Proj4js.defs["EPSG:3095"] = "+proj=utm +zone=54 +ellps=bessel +units=m +no_defs "; //Mentor: TOKYO.UTM-54N
Proj4js.defs["EPSG:3096"] = "+proj=utm +zone=55 +ellps=bessel +units=m +no_defs "; //Mentor: TOKYO.UTM-55N
Proj4js.defs["EPSG:4301"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: Tokyo
Proj4js.defs["EPSG:2443"] = "+proj=tmerc +lat_0=33 +lon_0=129.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-I
Proj4js.defs["EPSG:2444"] = "+proj=tmerc +lat_0=33 +lon_0=131 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-II
Proj4js.defs["EPSG:2445"] = "+proj=tmerc +lat_0=36 +lon_0=132.1666666666667 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-III
Proj4js.defs["EPSG:2446"] = "+proj=tmerc +lat_0=33 +lon_0=133.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-IV
Proj4js.defs["EPSG:2447"] = "+proj=tmerc +lat_0=36 +lon_0=134.3333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-V
Proj4js.defs["EPSG:2448"] = "+proj=tmerc +lat_0=36 +lon_0=136 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-VI
Proj4js.defs["EPSG:2449"] = "+proj=tmerc +lat_0=36 +lon_0=137.1666666666667 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-VII
Proj4js.defs["EPSG:2450"] = "+proj=tmerc +lat_0=36 +lon_0=138.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-VIII
Proj4js.defs["EPSG:2451"] = "+proj=tmerc +lat_0=36 +lon_0=139.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-IX
Proj4js.defs["EPSG:2452"] = "+proj=tmerc +lat_0=40 +lon_0=140.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-X
Proj4js.defs["EPSG:2453"] = "+proj=tmerc +lat_0=44 +lon_0=140.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XI
Proj4js.defs["EPSG:2454"] = "+proj=tmerc +lat_0=44 +lon_0=142.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XII
Proj4js.defs["EPSG:2455"] = "+proj=tmerc +lat_0=44 +lon_0=144.25 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XIII
Proj4js.defs["EPSG:2456"] = "+proj=tmerc +lat_0=26 +lon_0=142 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XIV
Proj4js.defs["EPSG:2457"] = "+proj=tmerc +lat_0=26 +lon_0=127.5 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XV
Proj4js.defs["EPSG:2458"] = "+proj=tmerc +lat_0=26 +lon_0=124 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XVI
Proj4js.defs["EPSG:2459"] = "+proj=tmerc +lat_0=26 +lon_0=131 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XVII
Proj4js.defs["EPSG:2460"] = "+proj=tmerc +lat_0=20 +lon_0=136 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XVIII
Proj4js.defs["EPSG:2461"] = "+proj=tmerc +lat_0=26 +lon_0=154 +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2K.PlnRctCS-XIX
Proj4js.defs["EPSG:3097"] = "+proj=utm +zone=51 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2000.UTM-51N
Proj4js.defs["EPSG:3098"] = "+proj=utm +zone=52 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2000.UTM-52N
Proj4js.defs["EPSG:3099"] = "+proj=utm +zone=53 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2000.UTM-53N
Proj4js.defs["EPSG:3100"] = "+proj=utm +zone=54 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2000.UTM-54N
Proj4js.defs["EPSG:3101"] = "+proj=utm +zone=55 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: JGD2000.UTM-55N
Proj4js.defs["EPSG:4709"] = "+proj=longlat +ellps=intl +towgs84=145,75,-272,0,0,0,0 +no_defs "; //Mentor: IwoJima45.LL
Proj4js.defs["EPSG:4711"] = "+proj=longlat +ellps=intl +towgs84=124,-234,-25,0,0,0,0 +no_defs "; //Mentor: Marcus52.LL
Proj4js.defs["EPSG:3066"] = "+proj=tmerc +lat_0=0 +lon_0=37 +k=0.9998 +x_0=500000 +y_0=-3000000 +ellps=intl +units=m +no_defs "; //Mentor: ED50.Jordan/TM
Proj4js.defs["EPSG:2939"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=50.76666666666667 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-K2
Proj4js.defs["EPSG:2940"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=53.76666666666667 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-K3
Proj4js.defs["EPSG:2941"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=56.76666666666667 +k=1 +x_0=4300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-K4
Proj4js.defs["EPSG:28409"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=9500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-9
Proj4js.defs["EPSG:28410"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=10500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-10
Proj4js.defs["EPSG:28411"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=11500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-11
Proj4js.defs["EPSG:28412"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=12500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-12
Proj4js.defs["EPSG:21096"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-36N
Proj4js.defs["EPSG:21036"] = "+proj=utm +zone=36 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-36S
Proj4js.defs["EPSG:21097"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-37N
Proj4js.defs["EPSG:21037"] = "+proj=utm +zone=37 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-37S
Proj4js.defs["EPSG:2311"] = "+proj=tmerc +lat_0=0 +lon_0=6 +k=0.9996 +x_0=500000 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-6NE
Proj4js.defs["EPSG:28412"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=12500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-12
Proj4js.defs["EPSG:4166"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Korean95.LL
Proj4js.defs["EPSG:4737"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Korean2000.LL
Proj4js.defs["EPSG:20438"] = "+proj=utm +zone=38 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.UTM-38N
Proj4js.defs["EPSG:24600"] = "+proj=lcc +lat_1=32.5 +lat_0=32.5 +lon_0=45 +k_0=0.9987864078000001 +x_0=1500000 +y_0=1166200 +ellps=clrk80 +towgs84=-294.7,-200.1,525.5,0,0,0,0 +units=m +no_defs "; //Mentor: KuwaitOil.Lambert
Proj4js.defs["EPSG:4246"] = "+proj=longlat +ellps=clrk80 +towgs84=-294.7,-200.1,525.5,0,0,0,0 +no_defs "; //Mentor: KuwaitOil.LL
Proj4js.defs["EPSG:4319"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: KuwaitUtility.LL
Proj4js.defs["EPSG:4667"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: IraqKuwait1992.LL
Proj4js.defs["EPSG:31901"] = "+proj=tmerc +lat_0=0 +lon_0=48 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: KuwaitUtility.KTM
Proj4js.defs["EPSG:4661"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Latvia1992.LL
Proj4js.defs["EPSG:3059"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9996 +x_0=500000 +y_0=-6000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: Latvia1992.TM
Proj4js.defs["EPSG:4251"] = "+proj=longlat +ellps=clrk80 +towgs84=-90,40,88,0,0,0,0 +no_defs "; //Mentor: Liberia.LL
Proj4js.defs["EPSG:2073"] = "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-10
Proj4js.defs["EPSG:2074"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-11
Proj4js.defs["EPSG:2075"] = "+proj=tmerc +lat_0=0 +lon_0=23 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-12
Proj4js.defs["EPSG:2076"] = "+proj=tmerc +lat_0=0 +lon_0=25 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-13
Proj4js.defs["EPSG:2068"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-5
Proj4js.defs["EPSG:2069"] = "+proj=tmerc +lat_0=0 +lon_0=11 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-6
Proj4js.defs["EPSG:2070"] = "+proj=tmerc +lat_0=0 +lon_0=13 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-7
Proj4js.defs["EPSG:2071"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-8
Proj4js.defs["EPSG:2072"] = "+proj=tmerc +lat_0=0 +lon_0=17 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-9
Proj4js.defs["EPSG:2087"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.TM-12NE
Proj4js.defs["EPSG:2077"] = "+proj=utm +zone=32 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-32N
Proj4js.defs["EPSG:2078"] = "+proj=utm +zone=33 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-33N
Proj4js.defs["EPSG:2079"] = "+proj=utm +zone=34 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-34N
Proj4js.defs["EPSG:2080"] = "+proj=utm +zone=35 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-35N
Proj4js.defs["EPSG:4159"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: ELD1979.LL
Proj4js.defs["EPSG:4754"] = "+proj=longlat +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +no_defs "; //Mentor: Libyan2006_1.LL
Proj4js.defs["EPSG:3177"] = "+proj=tmerc +lat_0=0 +lon_0=17 +k=0.9965000000000001 +x_0=1000000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.Libya/TM
Proj4js.defs["EPSG:3190"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.Libya/TM-5
Proj4js.defs["EPSG:3191"] = "+proj=tmerc +lat_0=0 +lon_0=11 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.Libya/TM-6
Proj4js.defs["EPSG:3192"] = "+proj=tmerc +lat_0=0 +lon_0=13 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.Libya/TM-7
Proj4js.defs["EPSG:3193"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.Libya/TM-8
Proj4js.defs["EPSG:3194"] = "+proj=tmerc +lat_0=0 +lon_0=17 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.Libya/TM-9
Proj4js.defs["EPSG:3195"] = "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: LBY2006.Libya/TM-10
Proj4js.defs["EPSG:3196"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: LBY2006.Libya/TM-11
Proj4js.defs["EPSG:3197"] = "+proj=tmerc +lat_0=0 +lon_0=23 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: LBY2006.Libya/TM-12
Proj4js.defs["EPSG:3198"] = "+proj=tmerc +lat_0=0 +lon_0=25 +k=0.99995 +x_0=200000 +y_0=0 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: LBY2006.Libya/TM-13
Proj4js.defs["EPSG:3199"] = "+proj=utm +zone=32 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.UTM-32N
Proj4js.defs["EPSG:3201"] = "+proj=utm +zone=33 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.UTM-33N
Proj4js.defs["EPSG:3202"] = "+proj=utm +zone=34 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.UTM-34N
Proj4js.defs["EPSG:3203"] = "+proj=utm +zone=35 +ellps=intl +towgs84=-208.406,-109.878,-2.5764,0,0,0,0 +units=m +no_defs "; //Mentor: Libyan2006_1.UTM-35N
Proj4js.defs["EPSG:3346"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9998 +x_0=500000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: Lietuvos1994
Proj4js.defs["EPSG:4669"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Lithuania94.LL
Proj4js.defs["EPSG:2169"] = "+proj=tmerc +lat_0=49.83333333333334 +lon_0=6.166666666666667 +k=1 +x_0=80000 +y_0=100000 +ellps=intl +towgs84=-193,13.7,-39.3,-0.41,-2.933,2.688,0.43 +units=m +no_defs "; //Mentor: Luxembourg30a.Gauss
Proj4js.defs["EPSG:4181"] = "+proj=longlat +ellps=intl +towgs84=-193,13.7,-39.3,-0.41,-2.933,2.688,0.43 +no_defs "; //Mentor: Luxembourg30a.LL
Proj4js.defs["EPSG:29738"] = "+proj=utm +zone=38 +south +ellps=intl +towgs84=-189,-242,-91,0,0,0,0 +units=m +no_defs "; //Mentor: Tananarive1925.UTM-38S
Proj4js.defs["EPSG:29739"] = "+proj=utm +zone=39 +south +ellps=intl +towgs84=-189,-242,-91,0,0,0,0 +units=m +no_defs "; //Mentor: Tananarive1925.UTM-39S
Proj4js.defs["EPSG:4297"] = "+proj=longlat +ellps=intl +towgs84=-189,-242,-91,0,0,0,0 +no_defs "; //Mentor: Tananarive1925.LL
Proj4js.defs["EPSG:20936"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-36S/01
Proj4js.defs["EPSG:3167"] = "+proj=omerc +lat_0=4 +lonc=102.25 +alpha=323.0257905 +k=0.99984 +x_0=40000 +y_0=0 +a=6377295.664 +b=6356094.667915204 +to_meter=20.116756 +no_defs "; //Mentor: Kertau.MalayaRSO/01
Proj4js.defs["EPSG:24547"] = "+proj=utm +zone=47 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: Kertau.UTM-47N
Proj4js.defs["EPSG:24548"] = "+proj=utm +zone=48 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: Kertau.UTM-48N
Proj4js.defs["EPSG:4245"] = "+proj=longlat +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +no_defs "; //Mentor: Kertau48.LL
Proj4js.defs["EPSG:4298"] = "+proj=longlat +ellps=evrstSS +no_defs "; //Mentor: TIMBALAI.LL
Proj4js.defs["EPSG:29871"] = "+proj=omerc +lat_0=4 +lonc=115 +alpha=53.31582047222222 +k=0.99984 +x_0=590476.8714630401 +y_0=442857.653094361 +ellps=evrstSS +to_meter=20.11676512155263 +no_defs "; //Mentor: TMBLI-B.RSOBorneo.ch
Proj4js.defs["EPSG:29872"] = "+proj=omerc +lat_0=4 +lonc=115 +alpha=53.31582047222222 +k=0.99984 +x_0=590476.8727431979 +y_0=442857.6545573985 +ellps=evrstSS +to_meter=0.3047994715386762 +no_defs "; //Mentor: TMBLI-B.RSOBorneo.ft
Proj4js.defs["EPSG:29873"] = "+proj=omerc +lat_0=4 +lonc=115 +alpha=53.31582047222222 +k=0.99984 +x_0=590476.87 +y_0=442857.65 +ellps=evrstSS +units=m +no_defs "; //Mentor: TMBLI-B.RSOBorneo.m
Proj4js.defs["EPSG:29849"] = "+proj=utm +zone=49 +ellps=evrstSS +units=m +no_defs "; //Mentor: TMBLI-B.UTM-49N
Proj4js.defs["EPSG:29850"] = "+proj=utm +zone=50 +ellps=evrstSS +units=m +no_defs "; //Mentor: TMBLI-B.UTM-50N
Proj4js.defs["EPSG:4261"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +no_defs "; //Mentor: Merchich/01
Proj4js.defs["EPSG:26191"] = "+proj=lcc +lat_1=33.3 +lat_0=33.3 +lon_0=-5.4 +k_0=0.999625769 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.NordMaroc/01
Proj4js.defs["EPSG:26194"] = "+proj=lcc +lat_1=26.1 +lat_0=26.1 +lon_0=-5.4 +k_0=0.999616304 +x_0=1200000 +y_0=400000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.SaharaNord/01
Proj4js.defs["EPSG:26195"] = "+proj=lcc +lat_1=22.5 +lat_0=22.5 +lon_0=-5.4 +k_0=0.999616437 +x_0=1500000 +y_0=400000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.SaharaSud/01
Proj4js.defs["EPSG:26192"] = "+proj=lcc +lat_1=29.7 +lat_0=29.7 +lon_0=-5.4 +k_0=0.9996155960000001 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.SudMaroc/01
Proj4js.defs["EPSG:4699"] = "+proj=longlat +ellps=clrk80 +towgs84=-770.1,158.4,-498.2,0,0,0,0 +no_defs "; //Mentor: LePouce34.LL
Proj4js.defs["EPSG:31965"] = "+proj=utm +zone=11 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-11N
Proj4js.defs["EPSG:31966"] = "+proj=utm +zone=12 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-12N
Proj4js.defs["EPSG:31967"] = "+proj=utm +zone=13 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-13N
Proj4js.defs["EPSG:31968"] = "+proj=utm +zone=14 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-14N
Proj4js.defs["EPSG:4130"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,-0,-0,-0,0 +no_defs "; //Mentor: Moznet.LL
Proj4js.defs["EPSG:3036"] = "+proj=utm +zone=36 +south +ellps=WGS84 +towgs84=0,0,0,-0,-0,-0,0 +units=m +no_defs "; //Mentor: Moznet.UTM-36S
Proj4js.defs["EPSG:3037"] = "+proj=utm +zone=37 +south +ellps=WGS84 +towgs84=0,0,0,-0,-0,-0,0 +units=m +no_defs "; //Mentor: Moznet.UTM-37S
Proj4js.defs["EPSG:2736"] = "+proj=utm +zone=36 +south +ellps=clrk66 +units=m +no_defs "; //Mentor: Tete/a.UTM-36S
Proj4js.defs["EPSG:4127"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Tete/a.LL
Proj4js.defs["EPSG:2737"] = "+proj=utm +zone=37 +south +ellps=clrk66 +units=m +no_defs "; //Mentor: Tete/a.UTM-37S
Proj4js.defs["EPSG:24382"] = "+proj=lcc +lat_1=26 +lat_0=26 +lon_0=90 +k_0=0.99878641 +x_0=2743195.592233322 +y_0=914398.5307444407 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-IIB/a
Proj4js.defs["EPSG:4293"] = "+proj=longlat +ellps=bess_nam +no_defs "; //Mentor: Schwarzk.LL
Proj4js.defs["EPSG:29333"] = "+proj=utm +zone=33 +south +ellps=bess_nam +units=m +no_defs "; //Mentor: Schwarzk.UTM-33S
Proj4js.defs["EPSG:28992"] = "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +units=m +no_defs "; //Mentor: Netherlands-RDNew
Proj4js.defs["EPSG:28991"] = "+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=0 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: Netherlands-RDOld
Proj4js.defs["EPSG:4289"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: Amersfoort/a.LL
Proj4js.defs["EPSG:24819"] = "+proj=utm +zone=19 +ellps=intl +units=m +no_defs "; //Mentor: SA-19N
Proj4js.defs["EPSG:29169"] = "+proj=utm +zone=19 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19N
Proj4js.defs["EPSG:4633"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN56/Lifou.LL
Proj4js.defs["EPSG:2981"] = "+proj=utm +zone=58 +south +ellps=intl +units=m +no_defs "; //Mentor: IGN56/Lifou.UTM-58S
Proj4js.defs["EPSG:4644"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Noumea74.LL
Proj4js.defs["EPSG:2998"] = "+proj=utm +zone=58 +south +ellps=intl +units=m +no_defs "; //Mentor: Noumea74.UTM-58S
Proj4js.defs["EPSG:4643"] = "+proj=longlat +ellps=intl +towgs84=-480.26,-438.32,-643.429,16.3119,20.1721,-4.0349,-111.7 +no_defs "; //Mentor: ST71Belep/a.LL
Proj4js.defs["EPSG:2997"] = "+proj=utm +zone=58 +south +ellps=intl +towgs84=-480.26,-438.32,-643.429,16.3119,20.1721,-4.0349,-111.7 +units=m +no_defs "; //Mentor: ST71Belep/a.UTM-58S
Proj4js.defs["EPSG:4642"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: ST84IlePins/a.LL
Proj4js.defs["EPSG:2996"] = "+proj=utm +zone=58 +south +ellps=intl +units=m +no_defs "; //Mentor: ST84IlePins/a.UTM-58S
Proj4js.defs["EPSG:4749"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: RGN-Caledonie/91-93.LL
Proj4js.defs["EPSG:4641"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN53/Mare.LL
Proj4js.defs["EPSG:4662"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: IGN72/GrandeTerre.LL
Proj4js.defs["EPSG:2995"] = "+proj=utm +zone=58 +south +ellps=intl +units=m +no_defs "; //Mentor: IGN53/Mare.UTM-58S
Proj4js.defs["EPSG:3060"] = "+proj=utm +zone=58 +south +ellps=intl +units=m +no_defs "; //Mentor: IGN72/GT.UTM-58S
Proj4js.defs["EPSG:3163"] = "+proj=lcc +lat_1=-20.66666666666667 +lat_2=-22.33333333333333 +lat_0=-21.5 +lon_0=166 +x_0=400000 +y_0=300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGN/91-93.NewCaledonia
Proj4js.defs["EPSG:3164"] = "+proj=utm +zone=58 +south +ellps=WGS84 +towgs84=-56.263,16.136,-22.856,0,0,0,0 +units=m +no_defs "; //Mentor: ST87/Ouvea.UTM-58S
Proj4js.defs["EPSG:3165"] = "+proj=lcc +lat_1=-22.24469175 +lat_2=-22.29469175 +lat_0=-22.26969175 +lon_0=166.44242575 +x_0=0.66 +y_0=1.02 +ellps=intl +units=m +no_defs "; //Mentor: Noumea74.Noumea
Proj4js.defs["EPSG:3166"] = "+proj=lcc +lat_1=-22.24472222222222 +lat_2=-22.29472222222222 +lat_0=-22.26972222222222 +lon_0=166.4425 +x_0=8.313000000000001 +y_0=-2.354 +ellps=intl +units=m +no_defs "; //Mentor: Noumea74.Noumea-2
Proj4js.defs["EPSG:3169"] = "+proj=utm +zone=57 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGN/91-93.UTM-57S
Proj4js.defs["EPSG:3170"] = "+proj=utm +zone=58 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGN/91-93.UTM-58S
Proj4js.defs["EPSG:3171"] = "+proj=utm +zone=59 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RGN/91-93.UTM-59S
Proj4js.defs["EPSG:3172"] = "+proj=utm +zone=59 +south +ellps=intl +units=m +no_defs "; //Mentor: IGN53/Mare.UTM-59S
Proj4js.defs["EPSG:4672"] = "+proj=longlat +ellps=intl +towgs84=175,-38,113,0,0,0,0 +no_defs "; //Mentor: CHATHAM.LL
Proj4js.defs["EPSG:4167"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: NZGD2000.LL
Proj4js.defs["EPSG:2119"] = "+proj=tmerc +lat_0=-42.68888888888888 +lon_0=173.01 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Amuri
Proj4js.defs["EPSG:2106"] = "+proj=tmerc +lat_0=-37.76111111111111 +lon_0=176.4661111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.BayofPlenty
Proj4js.defs["EPSG:2132"] = "+proj=tmerc +lat_0=-46.6 +lon_0=168.3427777777778 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Bluff
Proj4js.defs["EPSG:2117"] = "+proj=tmerc +lat_0=-41.81055555555555 +lon_0=171.5811111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Buller
Proj4js.defs["EPSG:2114"] = "+proj=tmerc +lat_0=-40.71472222222223 +lon_0=172.6719444444444 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Collingwood
Proj4js.defs["EPSG:2125"] = "+proj=tmerc +lat_0=-43.74861111111111 +lon_0=171.3605555555555 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Gawler
Proj4js.defs["EPSG:2118"] = "+proj=tmerc +lat_0=-42.33361111111111 +lon_0=171.5497222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Grey
Proj4js.defs["EPSG:2108"] = "+proj=tmerc +lat_0=-39.65083333333333 +lon_0=176.6736111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.HawkesBay
Proj4js.defs["EPSG:2121"] = "+proj=tmerc +lat_0=-42.88611111111111 +lon_0=170.9797222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Hokitika
Proj4js.defs["EPSG:2123"] = "+proj=tmerc +lat_0=-43.97777777777778 +lon_0=168.6061111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.JacksonsBay
Proj4js.defs["EPSG:2116"] = "+proj=tmerc +lat_0=-41.28972222222222 +lon_0=172.1088888888889 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Karamea
Proj4js.defs["EPSG:2127"] = "+proj=tmerc +lat_0=-44.735 +lon_0=169.4675 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.LindisPeak
Proj4js.defs["EPSG:2120"] = "+proj=tmerc +lat_0=-41.54444444444444 +lon_0=173.8019444444444 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Marlborough
Proj4js.defs["EPSG:2105"] = "+proj=tmerc +lat_0=-36.87972222222222 +lon_0=174.7641666666667 +k=0.9999 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.MountEden
Proj4js.defs["EPSG:2128"] = "+proj=tmerc +lat_0=-45.13277777777778 +lon_0=168.3986111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.MountNicholas
Proj4js.defs["EPSG:2124"] = "+proj=tmerc +lat_0=-43.59055555555556 +lon_0=172.7269444444445 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.MountPleasant
Proj4js.defs["EPSG:2129"] = "+proj=tmerc +lat_0=-45.56361111111111 +lon_0=167.7386111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.MountYork
Proj4js.defs["EPSG:2115"] = "+proj=tmerc +lat_0=-41.27444444444444 +lon_0=173.2991666666667 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Nelson
Proj4js.defs["EPSG:2131"] = "+proj=tmerc +lat_0=-45.86138888888889 +lon_0=170.2825 +k=0.99996 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.NorthTaieri
Proj4js.defs["EPSG:2130"] = "+proj=tmerc +lat_0=-45.81611111111111 +lon_0=170.6283333333333 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.ObservationPnt
Proj4js.defs["EPSG:2122"] = "+proj=tmerc +lat_0=-43.11 +lon_0=170.2608333333333 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Okarito
Proj4js.defs["EPSG:2107"] = "+proj=tmerc +lat_0=-38.62444444444444 +lon_0=177.8855555555556 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.PovertyBay
Proj4js.defs["EPSG:2109"] = "+proj=tmerc +lat_0=-39.13555555555556 +lon_0=174.2277777777778 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Taranaki
Proj4js.defs["EPSG:2126"] = "+proj=tmerc +lat_0=-44.40194444444445 +lon_0=171.0572222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Timaru
Proj4js.defs["EPSG:2110"] = "+proj=tmerc +lat_0=-39.51222222222222 +lon_0=175.64 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Tuhirangi
Proj4js.defs["EPSG:2133"] = "+proj=utm +zone=58 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.UTM-58S
Proj4js.defs["EPSG:2134"] = "+proj=utm +zone=59 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.UTM-59S
Proj4js.defs["EPSG:2135"] = "+proj=utm +zone=60 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.UTM-60S
Proj4js.defs["EPSG:2112"] = "+proj=tmerc +lat_0=-40.92527777777777 +lon_0=175.6472222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Wairarapa
Proj4js.defs["EPSG:2111"] = "+proj=tmerc +lat_0=-40.24194444444444 +lon_0=175.4880555555555 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Wanganui
Proj4js.defs["EPSG:2113"] = "+proj=tmerc +lat_0=-41.3011111111111 +lon_0=174.7763888888889 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZGD2K.Wellington
Proj4js.defs["EPSG:27219"] = "+proj=tmerc +lat_0=-42.68911658333333 +lon_0=173.0101333888889 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Amuri/01
Proj4js.defs["EPSG:27206"] = "+proj=tmerc +lat_0=-37.76124980555556 +lon_0=176.46619725 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.BayOfPlenty/01
Proj4js.defs["EPSG:27232"] = "+proj=tmerc +lat_0=-46.60000961111111 +lon_0=168.342872 +k=1 +x_0=300002.66 +y_0=699999.58 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Bluff/01
Proj4js.defs["EPSG:27217"] = "+proj=tmerc +lat_0=-41.81080286111111 +lon_0=171.5812600555556 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Buller/01
Proj4js.defs["EPSG:27214"] = "+proj=tmerc +lat_0=-40.71475905555556 +lon_0=172.6720465 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Collingwood/01
Proj4js.defs["EPSG:27225"] = "+proj=tmerc +lat_0=-43.74871155555556 +lon_0=171.3607484722222 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Gawler/01
Proj4js.defs["EPSG:27218"] = "+proj=tmerc +lat_0=-42.33369427777778 +lon_0=171.5497713055556 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Grey/01
Proj4js.defs["EPSG:27208"] = "+proj=tmerc +lat_0=-39.65092930555556 +lon_0=176.6736805277778 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.HawkesBay/01
Proj4js.defs["EPSG:27221"] = "+proj=tmerc +lat_0=-42.88632236111111 +lon_0=170.9799935 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Hokitika/01
Proj4js.defs["EPSG:27223"] = "+proj=tmerc +lat_0=-43.97780288888889 +lon_0=168.606267 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.JacksonsBay/01
Proj4js.defs["EPSG:27216"] = "+proj=tmerc +lat_0=-41.28991152777778 +lon_0=172.1090281944444 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Karamea/01
Proj4js.defs["EPSG:27227"] = "+proj=tmerc +lat_0=-44.73526797222222 +lon_0=169.4677550833333 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.LindisPeak/01
Proj4js.defs["EPSG:27220"] = "+proj=tmerc +lat_0=-41.54448666666666 +lon_0=173.8020741111111 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Marlborough/01
Proj4js.defs["EPSG:27205"] = "+proj=tmerc +lat_0=-36.87986527777778 +lon_0=174.7643393611111 +k=0.9999 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.MountEden/01
Proj4js.defs["EPSG:27228"] = "+proj=tmerc +lat_0=-45.13290258333333 +lon_0=168.3986411944444 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.MountNicholas/01
Proj4js.defs["EPSG:27224"] = "+proj=tmerc +lat_0=-43.59063758333333 +lon_0=172.7271935833333 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.MountPleasant/01
Proj4js.defs["EPSG:27229"] = "+proj=tmerc +lat_0=-45.56372616666666 +lon_0=167.7388617777778 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.MountYork/01
Proj4js.defs["EPSG:27215"] = "+proj=tmerc +lat_0=-41.27454472222222 +lon_0=173.2993168055555 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Nelson/01
Proj4js.defs["EPSG:27291"] = "+proj=tmerc +lat_0=-39 +lon_0=175.5 +k=1 +x_0=274319.5243848086 +y_0=365759.3658464114 +ellps=intl +datum=nzgd49 +to_meter=0.9143984146160287 +no_defs "; //Mentor: NZGD49.NorthIslandGrid
Proj4js.defs["EPSG:27231"] = "+proj=tmerc +lat_0=-45.86151336111111 +lon_0=170.2825891111111 +k=0.99996 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.NorthTaieri/01
Proj4js.defs["EPSG:27230"] = "+proj=tmerc +lat_0=-45.81619661111111 +lon_0=170.6285951666667 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.ObservationPnt/1
Proj4js.defs["EPSG:27222"] = "+proj=tmerc +lat_0=-43.11012813888889 +lon_0=170.2609258333333 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Okarito
Proj4js.defs["EPSG:27207"] = "+proj=tmerc +lat_0=-38.62470277777778 +lon_0=177.8856362777778 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.PovertyBay/01
Proj4js.defs["EPSG:27292"] = "+proj=tmerc +lat_0=-44 +lon_0=171.5 +k=1 +x_0=457199.2073080143 +y_0=457199.2073080143 +ellps=intl +datum=nzgd49 +to_meter=0.9143984146160287 +no_defs "; //Mentor: NZGD49.SouthIslandGrid
Proj4js.defs["EPSG:27209"] = "+proj=tmerc +lat_0=-39.13575830555556 +lon_0=174.22801175 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Taranaki/01
Proj4js.defs["EPSG:27226"] = "+proj=tmerc +lat_0=-44.40222036111111 +lon_0=171.0572508333333 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Timaru
Proj4js.defs["EPSG:27210"] = "+proj=tmerc +lat_0=-39.51247038888889 +lon_0=175.6400368055556 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Tuhirangi/01
Proj4js.defs["EPSG:27258"] = "+proj=utm +zone=58 +south +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.UTM-58S
Proj4js.defs["EPSG:27259"] = "+proj=utm +zone=59 +south +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.UTM-59S
Proj4js.defs["EPSG:27260"] = "+proj=utm +zone=60 +south +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.UTM-60S
Proj4js.defs["EPSG:27212"] = "+proj=tmerc +lat_0=-40.92553263888889 +lon_0=175.6473496666667 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Wairarapa/01
Proj4js.defs["EPSG:27211"] = "+proj=tmerc +lat_0=-40.24194713888889 +lon_0=175.4880996111111 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Wanganui/01
Proj4js.defs["EPSG:27213"] = "+proj=tmerc +lat_0=-41.30131963888888 +lon_0=174.7766231111111 +k=1 +x_0=300000 +y_0=700000 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.Wellington/01
Proj4js.defs["EPSG:2193"] = "+proj=tmerc +lat_0=0 +lon_0=173 +k=0.9996 +x_0=1600000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZTM
Proj4js.defs["EPSG:27200"] = "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +datum=nzgd49 +units=m +no_defs "; //Mentor: NZGD49.NewZealandGrid
Proj4js.defs["EPSG:4673"] = "+proj=longlat +ellps=intl +towgs84=174.05,-25.49,112.57,-0,-0,0.554,0.2263 +no_defs "; //Mentor: Chatham1979.LL
Proj4js.defs["EPSG:2197"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=0.99995 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.Kp2K-Zealand
Proj4js.defs["EPSG:4263"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Minna.LL
Proj4js.defs["EPSG:26393"] = "+proj=tmerc +lat_0=4 +lon_0=12.5 +k=0.99975 +x_0=1110369.7 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.NigeriaEast
Proj4js.defs["EPSG:26392"] = "+proj=tmerc +lat_0=4 +lon_0=8.5 +k=0.99975 +x_0=670553.98 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.NigeriaMid
Proj4js.defs["EPSG:26391"] = "+proj=tmerc +lat_0=4 +lon_0=4.5 +k=0.99975 +x_0=230738.26 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.NigeriaWest
Proj4js.defs["EPSG:26331"] = "+proj=utm +zone=31 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.UTM-31N
Proj4js.defs["EPSG:26332"] = "+proj=utm +zone=32 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.UTM-32N
Proj4js.defs["EPSG:27391"] = "+proj=tmerc +lat_0=58 +lon_0=-4.666666666666667 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +pm=oslo +units=m +no_defs "; //Mentor: NGO-I
Proj4js.defs["EPSG:27392"] = "+proj=tmerc +lat_0=58 +lon_0=-2.333333333333333 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +pm=oslo +units=m +no_defs "; //Mentor: NGO-II-MOD
Proj4js.defs["EPSG:27394"] = "+proj=tmerc +lat_0=58 +lon_0=2.5 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +pm=oslo +units=m +no_defs "; //Mentor: NGO-IV-MOD
Proj4js.defs["EPSG:27395"] = "+proj=tmerc +lat_0=58 +lon_0=6.166666666666667 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +pm=oslo +units=m +no_defs "; //Mentor: NGO-V-MOD
Proj4js.defs["EPSG:27396"] = "+proj=tmerc +lat_0=58 +lon_0=10.16666666666667 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +pm=oslo +units=m +no_defs "; //Mentor: NGO-VI-MOD
Proj4js.defs["EPSG:27397"] = "+proj=tmerc +lat_0=58 +lon_0=14.16666666666667 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +pm=oslo +units=m +no_defs "; //Mentor: NGO-VII-MOD
Proj4js.defs["EPSG:27398"] = "+proj=tmerc +lat_0=58 +lon_0=18.33333333333333 +k=1 +x_0=0 +y_0=0 +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +pm=oslo +units=m +no_defs "; //Mentor: NGO-VIII
Proj4js.defs["EPSG:4273"] = "+proj=longlat +a=6377492.018 +b=6356173.508712696 +towgs84=278.3,93,474.5,7.889,0.05,-6.61,6.21 +no_defs "; //Mentor: NGO48a.LL
Proj4js.defs["EPSG:4660"] = "+proj=longlat +ellps=intl +towgs84=982.609,552.753,-540.873,32.3934,-153.257,-96.2266,16.805 +no_defs "; //Mentor: Helle1954.LL
Proj4js.defs["EPSG:3058"] = "+proj=tmerc +lat_0=0 +lon_0=-8.5 +k=1 +x_0=50000 +y_0=-7800000 +ellps=intl +towgs84=982.609,552.753,-540.873,32.3934,-153.257,-96.2266,16.805 +units=m +no_defs "; //Mentor: Helle1954.JanMayen
Proj4js.defs["EPSG:4232"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Fahud_1.LL
Proj4js.defs["EPSG:23239"] = "+proj=utm +zone=39 +ellps=clrk80 +units=m +no_defs "; //Mentor: Fahud_1.UTM-39N
Proj4js.defs["EPSG:23240"] = "+proj=utm +zone=40 +ellps=clrk80 +units=m +no_defs "; //Mentor: Fahud_1.UTM-40N
Proj4js.defs["EPSG:27038"] = "+proj=utm +zone=38 +ellps=clrk80 +units=m +no_defs "; //Mentor: Nahrwan67.UTM-38N
Proj4js.defs["EPSG:27039"] = "+proj=utm +zone=39 +ellps=clrk80 +units=m +no_defs "; //Mentor: Nahrwan67.UTM-39N
Proj4js.defs["EPSG:27040"] = "+proj=utm +zone=40 +ellps=clrk80 +units=m +no_defs "; //Mentor: Nahrwan67.UTM-40N
Proj4js.defs["EPSG:4270"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: NHRWN-O.LL
Proj4js.defs["EPSG:4134"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: PDOSurvey93a.LL
Proj4js.defs["EPSG:3439"] = "+proj=utm +zone=39 +ellps=clrk80 +units=m +no_defs "; //Mentor: PSD93a.UTM-39N
Proj4js.defs["EPSG:3440"] = "+proj=utm +zone=40 +ellps=clrk80 +units=m +no_defs "; //Mentor: PSD93a.UTM-40N
Proj4js.defs["EPSG:27037"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: Nahrwan67.UTM-37N/1
Proj4js.defs["EPSG:24370"] = "+proj=lcc +lat_1=39.5 +lat_0=39.5 +lon_0=68 +k_0=0.99846154 +x_0=2153865.73916853 +y_0=2368292.194628102 +a=6377299.36559538 +b=6356098.359005157 +to_meter=0.9143985307444408 +no_defs "; //Mentor: IND-0/a
Proj4js.defs["EPSG:4281"] = "+proj=longlat +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +no_defs "; //Mentor: Palestine23.LL
Proj4js.defs["EPSG:28191"] = "+proj=cass +lat_0=31.73409694444445 +lon_0=35.21208055555556 +x_0=170251.555 +y_0=126867.909 +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +units=m +no_defs "; //Mentor: Palestine23.Grid
Proj4js.defs["EPSG:28192"] = "+proj=tmerc +lat_0=31.73409694444445 +lon_0=35.21208055555556 +k=1 +x_0=170251.555 +y_0=1126867.909 +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +units=m +no_defs "; //Mentor: Palestine23.Belt
Proj4js.defs["EPSG:28193"] = "+proj=cass +lat_0=31.73409694444445 +lon_0=35.21208055555556 +x_0=170251.555 +y_0=1126867.909 +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +units=m +no_defs "; //Mentor: Palestine23.IsraeliGrd
Proj4js.defs["EPSG:4224"] = "+proj=longlat +ellps=intl +towgs84=-134,229,-29,0,0,0,0 +no_defs "; //Mentor: Chua.LL
Proj4js.defs["EPSG:24877"] = "+proj=utm +zone=17 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-17S
Proj4js.defs["EPSG:24878"] = "+proj=utm +zone=18 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-18S
Proj4js.defs["EPSG:24879"] = "+proj=utm +zone=19 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-19S
Proj4js.defs["EPSG:29187"] = "+proj=utm +zone=17 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-17S
Proj4js.defs["EPSG:29188"] = "+proj=utm +zone=18 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-18S
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19S
Proj4js.defs["EPSG:24892"] = "+proj=tmerc +lat_0=-9.5 +lon_0=-76 +k=0.99932994 +x_0=720000 +y_0=1039979.159 +ellps=intl +units=m +no_defs "; //Mentor: PSAD56.PeruCentral
Proj4js.defs["EPSG:24893"] = "+proj=tmerc +lat_0=-9.5 +lon_0=-70.5 +k=0.99952992 +x_0=1324000 +y_0=1040084.558 +ellps=intl +units=m +no_defs "; //Mentor: PSAD56.PeruEast
Proj4js.defs["EPSG:24891"] = "+proj=tmerc +lat_0=-6 +lon_0=-80.5 +k=0.99983008 +x_0=222000 +y_0=1426834.743 +ellps=intl +units=m +no_defs "; //Mentor: PSAD56.PeruWest
Proj4js.defs["EPSG:4253"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Luzon.LL
Proj4js.defs["EPSG:25391"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: Luzon.Philippines-I
Proj4js.defs["EPSG:25392"] = "+proj=tmerc +lat_0=0 +lon_0=119 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: Luzon.Philippines-II
Proj4js.defs["EPSG:25393"] = "+proj=tmerc +lat_0=0 +lon_0=121 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: Luzon.Philippines-III
Proj4js.defs["EPSG:25394"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: Luzon.Philippines-IV
Proj4js.defs["EPSG:25395"] = "+proj=tmerc +lat_0=0 +lon_0=125 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: Luzon.Philippines-V
Proj4js.defs["EPSG:4683"] = "+proj=longlat +ellps=clrk66 +towgs84=-127.62,-67.24,-47.04,-3.068,4.903,1.578,-1.06 +no_defs "; //Mentor: Philippine1992.LL
Proj4js.defs["EPSG:3121"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +towgs84=-127.62,-67.24,-47.04,-3.068,4.903,1.578,-1.06 +units=m +no_defs "; //Mentor: PRS92.Philippines-1
Proj4js.defs["EPSG:3122"] = "+proj=tmerc +lat_0=0 +lon_0=119 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +towgs84=-127.62,-67.24,-47.04,-3.068,4.903,1.578,-1.06 +units=m +no_defs "; //Mentor: PRS92.Philippines-2
Proj4js.defs["EPSG:3123"] = "+proj=tmerc +lat_0=0 +lon_0=121 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +towgs84=-127.62,-67.24,-47.04,-3.068,4.903,1.578,-1.06 +units=m +no_defs "; //Mentor: PRS92.Philippines-3
Proj4js.defs["EPSG:3124"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +towgs84=-127.62,-67.24,-47.04,-3.068,4.903,1.578,-1.06 +units=m +no_defs "; //Mentor: PRS92.Philippines-4
Proj4js.defs["EPSG:3125"] = "+proj=tmerc +lat_0=0 +lon_0=125 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +towgs84=-127.62,-67.24,-47.04,-3.068,4.903,1.578,-1.06 +units=m +no_defs "; //Mentor: PRS92.Philippines-5
Proj4js.defs["EPSG:2176"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=0.999923 +x_0=5500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-5
Proj4js.defs["EPSG:2177"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=0.999923 +x_0=6500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-6
Proj4js.defs["EPSG:2178"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=0.999923 +x_0=7500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-7
Proj4js.defs["EPSG:2179"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=0.999923 +x_0=8500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-8
Proj4js.defs["EPSG:2180"] = "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS92
Proj4js.defs["EPSG:2172"] = "+proj=sterea +lat_0=53.00194444444445 +lon_0=21.50277777777778 +k=0.9998 +x_0=4603000 +y_0=5806000 +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +units=m +no_defs "; //Mentor: Pulkovo/58a.Poland-II
Proj4js.defs["EPSG:2173"] = "+proj=sterea +lat_0=53.58333333333334 +lon_0=17.00833333333333 +k=0.9998 +x_0=3501000 +y_0=5999000 +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +units=m +no_defs "; //Mentor: Pulkovo/58a.Poland-III
Proj4js.defs["EPSG:2174"] = "+proj=sterea +lat_0=51.67083333333333 +lon_0=16.67222222222222 +k=0.9998 +x_0=3703000 +y_0=5627000 +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +units=m +no_defs "; //Mentor: Pulkovo/58a.Poland-IV
Proj4js.defs["EPSG:2175"] = "+proj=tmerc +lat_0=0 +lon_0=18.95833333333333 +k=0.999983 +x_0=237000 +y_0=-4700000 +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +units=m +no_defs "; //Mentor: Pulkovo/58a.Poland-V
Proj4js.defs["EPSG:3120"] = "+proj=sterea +lat_0=50.625 +lon_0=21.08333333333333 +k=0.9998 +x_0=4637000 +y_0=5467000 +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +units=m +no_defs "; //Mentor: Pulkovo42/58a.Poland-1
Proj4js.defs["EPSG:2189"] = "+proj=utm +zone=26 +ellps=intl +towgs84=-104,167,-38,0,0,0,0 +units=m +no_defs "; //Mentor: F26-GR
Proj4js.defs["EPSG:4183"] = "+proj=longlat +ellps=intl +towgs84=-104,167,-38,0,0,0,0 +no_defs "; //Mentor: AZORES.LL
Proj4js.defs["EPSG:2188"] = "+proj=utm +zone=25 +ellps=intl +units=m +no_defs "; //Mentor: AzoresWest1939.UTM-25N
Proj4js.defs["EPSG:2190"] = "+proj=utm +zone=26 +ellps=intl +towgs84=-203,141,53,0,0,0,0 +units=m +no_defs "; //Mentor: AzoresOrntl40.UTM-26N
Proj4js.defs["EPSG:4274"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Datum73a.LL
Proj4js.defs["EPSG:27493"] = "+proj=tmerc +lat_0=39.66666666666666 +lon_0=-8.131906111111112 +k=1 +x_0=180.598 +y_0=-86.98999999999999 +ellps=intl +units=m +no_defs "; //Mentor: Datum73a.ModPortgGrd
Proj4js.defs["EPSG:27429"] = "+proj=utm +zone=29 +ellps=intl +units=m +no_defs "; //Mentor: Datum73a.UTM-29N
Proj4js.defs["EPSG:4207"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Lisbon37a.LL
Proj4js.defs["EPSG:4615"] = "+proj=longlat +ellps=intl +towgs84=-499,-249,314,0,0,0,0 +no_defs "; //Mentor: MADEIRA.LL
Proj4js.defs["EPSG:4182"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: OBSRV66.LL
Proj4js.defs["EPSG:2942"] = "+proj=utm +zone=28 +ellps=intl +towgs84=-499,-249,314,0,0,0,0 +units=m +no_defs "; //Mentor: PortoSanto36.UTM-28N
Proj4js.defs["EPSG:4184"] = "+proj=longlat +ellps=intl +towgs84=-203,141,53,0,0,0,0 +no_defs "; //Mentor: SAOBRAZ.LL
Proj4js.defs["EPSG:4616"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Selvagem.LL
Proj4js.defs["EPSG:2943"] = "+proj=utm +zone=28 +ellps=intl +units=m +no_defs "; //Mentor: Selvagem.UTM-28N
Proj4js.defs["EPSG:4663"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: PortoSanto95.LL
Proj4js.defs["EPSG:4664"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: AzoresEast1995.LL
Proj4js.defs["EPSG:4665"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: AzoresCntrl1995.LL
Proj4js.defs["EPSG:4666"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: Lisbon1890_1.LL
Proj4js.defs["EPSG:3061"] = "+proj=utm +zone=28 +ellps=intl +units=m +no_defs "; //Mentor: PortoSanto95.UTM-28N
Proj4js.defs["EPSG:3062"] = "+proj=utm +zone=26 +ellps=intl +units=m +no_defs "; //Mentor: AzoresEast1995.UTM-26N
Proj4js.defs["EPSG:3063"] = "+proj=utm +zone=26 +ellps=intl +units=m +no_defs "; //Mentor: AzoresCntrl1995.UTM-26N
Proj4js.defs["EPSG:4286"] = "+proj=longlat +ellps=helmert +no_defs "; //Mentor: Qatar48.LL
Proj4js.defs["EPSG:4285"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Qatar1974.LL
Proj4js.defs["EPSG:28600"] = "+proj=tmerc +lat_0=24.45 +lon_0=51.21666666666667 +k=0.99999 +x_0=200000 +y_0=300000 +ellps=intl +units=m +no_defs "; //Mentor: Qatar1974.NationalGrid
Proj4js.defs["EPSG:4614"] = "+proj=longlat +ellps=intl +towgs84=-119.425,-303.659,-11.0006,1.1643,0.174458,1.09626,3.65706 +no_defs "; //Mentor: QatarNtl95a.LL
Proj4js.defs["EPSG:2932"] = "+proj=tmerc +lat_0=24.45 +lon_0=51.21666666666667 +k=0.99999 +x_0=200000 +y_0=300000 +ellps=intl +towgs84=-119.425,-303.659,-11.0006,1.1643,0.174458,1.09626,3.65706 +units=m +no_defs "; //Mentor: QND95a.QatarNational
Proj4js.defs["EPSG:2099"] = "+proj=cass +lat_0=25.38236111111111 +lon_0=50.76138888888889 +x_0=100000 +y_0=100000 +ellps=helmert +units=m +no_defs "; //Mentor: Qatar48.Qatar Grid
Proj4js.defs["EPSG:22234"] = "+proj=utm +zone=34 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: Cape-1.UTM-34S
Proj4js.defs["EPSG:22235"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: Cape-1.UTM-35S
Proj4js.defs["EPSG:22236"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: Cape-1.UTM-36S
Proj4js.defs["EPSG:4148"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Hartebeesthoek94.LL
Proj4js.defs["EPSG:4316"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: DealulPiscului1933.LL
Proj4js.defs["EPSG:31600"] = "+proj=sterea +lat_0=45.9 +lon_0=25.39246588888889 +k=0.9996667 +x_0=500000 +y_0=500000 +ellps=intl +units=m +no_defs "; //Mentor: Piscului33.Stero
Proj4js.defs["EPSG:20437"] = "+proj=utm +zone=37 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.UTM-37N
Proj4js.defs["EPSG:20438"] = "+proj=utm +zone=38 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.UTM-38N
Proj4js.defs["EPSG:20499"] = "+proj=utm +zone=39 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.BahrainGrid
Proj4js.defs["EPSG:31265"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-5
Proj4js.defs["EPSG:31266"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=1 +x_0=6500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-6
Proj4js.defs["EPSG:31267"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=7500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-7
Proj4js.defs["EPSG:31268"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=8500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-8
Proj4js.defs["EPSG:4312"] = "+proj=longlat +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +no_defs "; //Mentor: MGI-AT.LL
Proj4js.defs["EPSG:4175"] = "+proj=longlat +ellps=clrk80 +towgs84=-88,4,101,0,0,0,0 +no_defs "; //Mentor: SierraLeone1968.LL
Proj4js.defs["EPSG:2161"] = "+proj=utm +zone=28 +ellps=clrk80 +towgs84=-88,4,101,0,0,0,0 +units=m +no_defs "; //Mentor: SierraLeone1968.UTM-28N
Proj4js.defs["EPSG:2162"] = "+proj=utm +zone=29 +ellps=clrk80 +towgs84=-88,4,101,0,0,0,0 +units=m +no_defs "; //Mentor: SierraLeone1968.UTM-29N
Proj4js.defs["EPSG:24500"] = "+proj=cass +lat_0=1.287646666666667 +lon_0=103.8530022222222 +x_0=30000 +y_0=30000 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: Kertau.SingaporeGrid
Proj4js.defs["EPSG:24547"] = "+proj=utm +zone=47 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: Kertau.UTM-47N
Proj4js.defs["EPSG:24548"] = "+proj=utm +zone=48 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: Kertau.UTM-48N
Proj4js.defs["EPSG:4245"] = "+proj=longlat +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +no_defs "; //Mentor: Kertau48.LL
Proj4js.defs["EPSG:2065"] = "+proj=krovak +lat_0=49.5 +lon_0=42.5 +alpha=30.28813972222222 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel +pm=ferro +units=m +no_defs "; //Mentor: CzechJTSK/5.Krovak
Proj4js.defs["EPSG:4156"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: CzechJTSK/5.LL
Proj4js.defs["EPSG:31265"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-5
Proj4js.defs["EPSG:31266"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=1 +x_0=6500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-6
Proj4js.defs["EPSG:31267"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=7500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-7
Proj4js.defs["EPSG:31268"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=8500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-8
Proj4js.defs["EPSG:4312"] = "+proj=longlat +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +no_defs "; //Mentor: MGI-AT.LL
Proj4js.defs["EPSG:4205"] = "+proj=longlat +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +no_defs "; //Mentor: Afgooye.LL
Proj4js.defs["EPSG:20538"] = "+proj=utm +zone=38 +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +units=m +no_defs "; //Mentor: Afgooye.UTM-38N
Proj4js.defs["EPSG:20539"] = "+proj=utm +zone=39 +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +units=m +no_defs "; //Mentor: Afgooye.UTM-39N
Proj4js.defs["EPSG:4728"] = "+proj=longlat +ellps=intl +towgs84=-307,-92,127,0,0,0,0 +no_defs "; //Mentor: CANARY.LL
Proj4js.defs["EPSG:4244"] = "+proj=longlat +a=6377276.345 +b=6356075.41314024 +towgs84=-97,787,86,0,0,0,0 +no_defs "; //Mentor: Kandawala.LL
Proj4js.defs["EPSG:4201"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Adindan.LL
Proj4js.defs["EPSG:20135"] = "+proj=utm +zone=35 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-35N
Proj4js.defs["EPSG:20136"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-36N
Proj4js.defs["EPSG:20137"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-37N
Proj4js.defs["EPSG:20138"] = "+proj=utm +zone=38 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-38N
Proj4js.defs["EPSG:4620"] = "+proj=longlat +ellps=clrk80 +towgs84=-106,-129,165,0,0,0,0 +no_defs "; //Mentor: Point1958.LL
Proj4js.defs["EPSG:24821"] = "+proj=utm +zone=21 +ellps=intl +units=m +no_defs "; //Mentor: SA-21N
Proj4js.defs["EPSG:29171"] = "+proj=utm +zone=21 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-21N
Proj4js.defs["EPSG:4311"] = "+proj=longlat +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +no_defs "; //Mentor: Zanderij.LL
Proj4js.defs["EPSG:31171"] = "+proj=tmerc +lat_0=0 +lon_0=-55.68333333333333 +k=0.9999 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.SurinameTM
Proj4js.defs["EPSG:31170"] = "+proj=tmerc +lat_0=0 +lon_0=-55.68333333333333 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.SurinameTMOld
Proj4js.defs["EPSG:31154"] = "+proj=tmerc +lat_0=0 +lon_0=-54 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.TM-54NW
Proj4js.defs["EPSG:31121"] = "+proj=utm +zone=21 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.UTM-21N
Proj4js.defs["EPSG:3019"] = "+proj=tmerc +lat_0=0 +lon_0=11.30827777777778 +k=1 +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RT90-3/7P.SW-75GONV
Proj4js.defs["EPSG:3020"] = "+proj=tmerc +lat_0=0 +lon_0=13.55827777777778 +k=1 +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RT90-3/7P.SW-5GONV
Proj4js.defs["EPSG:3021"] = "+proj=tmerc +lat_0=0 +lon_0=15.80827777777778 +k=1 +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RT90-3/7P.SW-25GONV
Proj4js.defs["EPSG:3022"] = "+proj=tmerc +lat_0=0 +lon_0=18.05827777777778 +k=1 +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RT90-3/7P.SW-0GONV
Proj4js.defs["EPSG:3023"] = "+proj=tmerc +lat_0=0 +lon_0=20.30827777777778 +k=1 +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RT90-3/7P.SW-25GONO
Proj4js.defs["EPSG:3024"] = "+proj=tmerc +lat_0=0 +lon_0=22.55827777777778 +k=1 +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs "; //Mentor: RT90-3/7P.SW-5GONO
Proj4js.defs["EPSG:4124"] = "+proj=longlat +ellps=bessel +no_defs "; //Mentor: RT90-3/7P.LL
Proj4js.defs["EPSG:4619"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: SWEREF99.LL
Proj4js.defs["EPSG:3006"] = "+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-TM
Proj4js.defs["EPSG:3007"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-12-00
Proj4js.defs["EPSG:3008"] = "+proj=tmerc +lat_0=0 +lon_0=13.5 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-13-30
Proj4js.defs["EPSG:3009"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-15-00
Proj4js.defs["EPSG:3010"] = "+proj=tmerc +lat_0=0 +lon_0=16.5 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-16-30
Proj4js.defs["EPSG:3011"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-18-00
Proj4js.defs["EPSG:3012"] = "+proj=tmerc +lat_0=0 +lon_0=14.25 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-14-15
Proj4js.defs["EPSG:3013"] = "+proj=tmerc +lat_0=0 +lon_0=15.75 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-15-45
Proj4js.defs["EPSG:3014"] = "+proj=tmerc +lat_0=0 +lon_0=17.25 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-17-15
Proj4js.defs["EPSG:3015"] = "+proj=tmerc +lat_0=0 +lon_0=18.75 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-18-45
Proj4js.defs["EPSG:3016"] = "+proj=tmerc +lat_0=0 +lon_0=20.25 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-20-15
Proj4js.defs["EPSG:3017"] = "+proj=tmerc +lat_0=0 +lon_0=21.75 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-21-45
Proj4js.defs["EPSG:3018"] = "+proj=tmerc +lat_0=0 +lon_0=23.25 +k=1 +x_0=150000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF-99-23-15
Proj4js.defs["EPSG:3152"] = "+proj=tmerc +lat_0=0 +lon_0=18.05779 +k=0.99999425 +x_0=100178.1808 +y_0=-6500614.7836 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SWEREF99.ST74
Proj4js.defs["EPSG:21780"] = "+proj=somerc +lat_0=46.95240555555556 +lon_0=0 +k_0=1 +x_0=0 +y_0=0 +ellps=bessel +pm=bern +units=m +no_defs "; //Mentor: CH1903.LV03C/01
Proj4js.defs["EPSG:4149"] = "+proj=longlat +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +no_defs "; //Mentor: CH1903/GSB.LL
Proj4js.defs["EPSG:4150"] = "+proj=longlat +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +no_defs "; //Mentor: CH1903Plus_1.LL
Proj4js.defs["EPSG:2056"] = "+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs "; //Mentor: CH1903Plus_1.LV95/01
Proj4js.defs["EPSG:4151"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: CHTRF95.LL
Proj4js.defs["EPSG:4149"] = "+proj=longlat +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +no_defs "; //Mentor: LLCH1903
Proj4js.defs["EPSG:21781"] = "+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=600000 +y_0=200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs "; //Mentor: CH1903.LV03/01
Proj4js.defs["EPSG:21781"] = "+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=600000 +y_0=200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs "; //Mentor: CH1903/GSB.LV03-M
Proj4js.defs["EPSG:22780"] = "+proj=sterea +lat_0=34.2 +lon_0=39.15 +k=0.9995341 +x_0=0 +y_0=0 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: DeirEzZor_2.Levant
Proj4js.defs["EPSG:4227"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: DeirEzZor_2.LL
Proj4js.defs["EPSG:22770"] = "+proj=lcc +lat_1=34.65 +lat_0=34.65 +lon_0=37.35 +k_0=0.9996256 +x_0=300000 +y_0=300000 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: DeirEzZor_2.Syria
Proj4js.defs["EPSG:28412"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=12500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-12
Proj4js.defs["EPSG:4236"] = "+proj=longlat +ellps=intl +towgs84=-637,-549,-203,0,0,0,0 +no_defs "; //Mentor: HuTzuShan_1.LL
Proj4js.defs["EPSG:3821"] = "+proj=longlat +ellps=aust_SA +no_defs "; //Mentor: TAIWAN1967.LL
Proj4js.defs["EPSG:3824"] = "+proj=longlat +ellps=GRS80 +towgs84=0.0,0.0,0.0,0.0,0.0,0.0,0.0 +no_defs "; //Mentor: TAIWAN1997.LL
Proj4js.defs["EPSG:21035"] = "+proj=utm +zone=35 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-35S
Proj4js.defs["EPSG:21036"] = "+proj=utm +zone=36 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-36S
Proj4js.defs["EPSG:21037"] = "+proj=utm +zone=37 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-37S
Proj4js.defs["EPSG:2311"] = "+proj=tmerc +lat_0=0 +lon_0=6 +k=0.9996 +x_0=500000 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-6NE
Proj4js.defs["EPSG:4158"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: NAPARIMA.LL
Proj4js.defs["EPSG:2067"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: Naparima55.UTM-20N
Proj4js.defs["EPSG:4271"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Naparima1972.LL
Proj4js.defs["EPSG:27120"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: Naparima1972.UTM-20N
Proj4js.defs["EPSG:4302"] = "+proj=longlat +a=6378293.645208759 +b=6356617.987679838 +no_defs "; //Mentor: Trinidad1903.LL
Proj4js.defs["EPSG:30200"] = "+proj=cass +lat_0=10.44166666666667 +lon_0=-61.33333333333334 +x_0=86501.46392051999 +y_0=65379.0134283 +a=6378293.645208759 +b=6356617.987679838 +to_meter=0.201166195164 +no_defs "; //Mentor: Trinidad1903.Trinidad
Proj4js.defs["EPSG:2314"] = "+proj=cass +lat_0=10.44166666666667 +lon_0=-61.33333333333334 +x_0=86501.46392052001 +y_0=65379.0134283 +a=6378293.645208759 +b=6356617.987679838 +to_meter=0.3047972654 +no_defs "; //Mentor: Trndd1903.TrinidadGrid
Proj4js.defs["EPSG:4223"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: Carthage.LL
Proj4js.defs["EPSG:22391"] = "+proj=lcc +lat_1=36 +lat_0=36 +lon_0=9.9 +k_0=0.999625544 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Carthage.NordTunisie
Proj4js.defs["EPSG:22392"] = "+proj=lcc +lat_1=33.3 +lat_0=33.3 +lon_0=9.9 +k_0=0.999625769 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Carthage.SudTunisie
Proj4js.defs["EPSG:2088"] = "+proj=tmerc +lat_0=0 +lon_0=11 +k=0.9996 +x_0=500000 +y_0=0 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Carthage.TM-11NE
Proj4js.defs["EPSG:22332"] = "+proj=utm +zone=32 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Carthage.UTM-32N
Proj4js.defs["EPSG:32635"] = "+proj=utm +zone=35 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-35N
Proj4js.defs["EPSG:32637"] = "+proj=utm +zone=37 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-37N
Proj4js.defs["EPSG:32638"] = "+proj=utm +zone=38 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-38N
Proj4js.defs["EPSG:28409"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=9500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-9
Proj4js.defs["EPSG:28410"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=10500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-10
Proj4js.defs["EPSG:28411"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=11500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-11
Proj4js.defs["EPSG:21095"] = "+proj=utm +zone=35 +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-35N
Proj4js.defs["EPSG:21035"] = "+proj=utm +zone=35 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-35S
Proj4js.defs["EPSG:21096"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-36N
Proj4js.defs["EPSG:21036"] = "+proj=utm +zone=36 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-36S
Proj4js.defs["EPSG:28406"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=6500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-6
Proj4js.defs["EPSG:28407"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=7500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-7
Proj4js.defs["EPSG:26729"] = "+proj=tmerc +lat_0=30.5 +lon_0=-85.83333333333333 +k=0.99996 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AL-E
Proj4js.defs["EPSG:26730"] = "+proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AL-W
Proj4js.defs["EPSG:26929"] = "+proj=tmerc +lat_0=30.5 +lon_0=-85.83333333333333 +k=0.99996 +x_0=200000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AL83-E
Proj4js.defs["EPSG:26930"] = "+proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AL83-W
Proj4js.defs["EPSG:2759"] = "+proj=tmerc +lat_0=30.5 +lon_0=-85.83333333333333 +k=0.99996 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/AL.AL-E
Proj4js.defs["EPSG:2760"] = "+proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/AL.AL-W
Proj4js.defs["EPSG:26731"] = "+proj=omerc +lat_0=57 +lonc=-133.6666666666667 +alpha=323.1301023611111 +k=0.9999 +x_0=5000000.001016002 +y_0=-5000000.001016002 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-1
Proj4js.defs["EPSG:26732"] = "+proj=tmerc +lat_0=54 +lon_0=-142 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-2
Proj4js.defs["EPSG:26733"] = "+proj=tmerc +lat_0=54 +lon_0=-146 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-3
Proj4js.defs["EPSG:26734"] = "+proj=tmerc +lat_0=54 +lon_0=-150 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-4
Proj4js.defs["EPSG:26735"] = "+proj=tmerc +lat_0=54 +lon_0=-154 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-5
Proj4js.defs["EPSG:26736"] = "+proj=tmerc +lat_0=54 +lon_0=-158 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-6
Proj4js.defs["EPSG:26737"] = "+proj=tmerc +lat_0=54 +lon_0=-162 +k=0.9999 +x_0=213360.4267208534 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-7
Proj4js.defs["EPSG:26738"] = "+proj=tmerc +lat_0=54 +lon_0=-166 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-8
Proj4js.defs["EPSG:26739"] = "+proj=tmerc +lat_0=54 +lon_0=-170 +k=0.9999 +x_0=182880.3657607315 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-9
Proj4js.defs["EPSG:26740"] = "+proj=lcc +lat_1=53.83333333333334 +lat_2=51.83333333333334 +lat_0=51 +lon_0=-176 +x_0=914401.8288036576 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AK-10
Proj4js.defs["EPSG:26931"] = "+proj=omerc +lat_0=57 +lonc=-133.6666666666667 +alpha=323.1301023611111 +k=0.9999 +x_0=5000000 +y_0=-5000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-1
Proj4js.defs["EPSG:26932"] = "+proj=tmerc +lat_0=54 +lon_0=-142 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-2
Proj4js.defs["EPSG:26933"] = "+proj=tmerc +lat_0=54 +lon_0=-146 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-3
Proj4js.defs["EPSG:26934"] = "+proj=tmerc +lat_0=54 +lon_0=-150 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-4
Proj4js.defs["EPSG:26935"] = "+proj=tmerc +lat_0=54 +lon_0=-154 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-5
Proj4js.defs["EPSG:26936"] = "+proj=tmerc +lat_0=54 +lon_0=-158 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-6
Proj4js.defs["EPSG:26937"] = "+proj=tmerc +lat_0=54 +lon_0=-162 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-7
Proj4js.defs["EPSG:26938"] = "+proj=tmerc +lat_0=54 +lon_0=-166 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-8
Proj4js.defs["EPSG:26939"] = "+proj=tmerc +lat_0=54 +lon_0=-170 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-9
Proj4js.defs["EPSG:26940"] = "+proj=lcc +lat_1=53.83333333333334 +lat_2=51.83333333333334 +lat_0=51 +lon_0=-176 +x_0=1000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AK83-10
Proj4js.defs["EPSG:26749"] = "+proj=tmerc +lat_0=31 +lon_0=-111.9166666666667 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AZ-C
Proj4js.defs["EPSG:26748"] = "+proj=tmerc +lat_0=31 +lon_0=-110.1666666666667 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AZ-E
Proj4js.defs["EPSG:26750"] = "+proj=tmerc +lat_0=31 +lon_0=-113.75 +k=0.999933333 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AZ-W
Proj4js.defs["EPSG:26949"] = "+proj=tmerc +lat_0=31 +lon_0=-111.9166666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AZ83-C
Proj4js.defs["EPSG:2223"] = "+proj=tmerc +lat_0=31 +lon_0=-111.9166666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: AZ83-CIF
Proj4js.defs["EPSG:26948"] = "+proj=tmerc +lat_0=31 +lon_0=-110.1666666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AZ83-E
Proj4js.defs["EPSG:2222"] = "+proj=tmerc +lat_0=31 +lon_0=-110.1666666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: AZ83-EIF
Proj4js.defs["EPSG:26950"] = "+proj=tmerc +lat_0=31 +lon_0=-113.75 +k=0.999933333 +x_0=213360 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AZ83-W
Proj4js.defs["EPSG:2224"] = "+proj=tmerc +lat_0=31 +lon_0=-113.75 +k=0.999933333 +x_0=213360 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: AZ83-WIF
Proj4js.defs["EPSG:2762"] = "+proj=tmerc +lat_0=31 +lon_0=-111.9166666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/AZ.AZ-C
Proj4js.defs["EPSG:2868"] = "+proj=tmerc +lat_0=31 +lon_0=-111.9166666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/AZ.AZ-CIF
Proj4js.defs["EPSG:2761"] = "+proj=tmerc +lat_0=31 +lon_0=-110.1666666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/AZ.AZ-E
Proj4js.defs["EPSG:2867"] = "+proj=tmerc +lat_0=31 +lon_0=-110.1666666666667 +k=0.9999 +x_0=213360 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/AZ.AZ-EIF
Proj4js.defs["EPSG:2763"] = "+proj=tmerc +lat_0=31 +lon_0=-113.75 +k=0.999933333 +x_0=213360 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/AZ.AZ-W
Proj4js.defs["EPSG:2869"] = "+proj=tmerc +lat_0=31 +lon_0=-113.75 +k=0.999933333 +x_0=213360 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/AZ.AZ-WIF
Proj4js.defs["EPSG:26751"] = "+proj=lcc +lat_1=36.23333333333333 +lat_2=34.93333333333333 +lat_0=34.33333333333334 +lon_0=-92 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AR-N
Proj4js.defs["EPSG:26752"] = "+proj=lcc +lat_1=34.76666666666667 +lat_2=33.3 +lat_0=32.66666666666666 +lon_0=-92 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AR-S
Proj4js.defs["EPSG:26951"] = "+proj=lcc +lat_1=36.23333333333333 +lat_2=34.93333333333333 +lat_0=34.33333333333334 +lon_0=-92 +x_0=400000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AR83-N
Proj4js.defs["EPSG:3433"] = "+proj=lcc +lat_1=36.23333333333333 +lat_2=34.93333333333333 +lat_0=34.33333333333334 +lon_0=-92 +x_0=399999.99998984 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AR83-NF
Proj4js.defs["EPSG:26952"] = "+proj=lcc +lat_1=34.76666666666667 +lat_2=33.3 +lat_0=32.66666666666666 +lon_0=-92 +x_0=400000 +y_0=400000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: AR83-S
Proj4js.defs["EPSG:3434"] = "+proj=lcc +lat_1=34.76666666666667 +lat_2=33.3 +lat_0=32.66666666666666 +lon_0=-92 +x_0=399999.99998984 +y_0=399999.99998984 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: AR83-SF
Proj4js.defs["EPSG:2764"] = "+proj=lcc +lat_1=36.23333333333333 +lat_2=34.93333333333333 +lat_0=34.33333333333334 +lon_0=-92 +x_0=400000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/AR.AR-N
Proj4js.defs["EPSG:3441"] = "+proj=lcc +lat_1=36.23333333333333 +lat_2=34.93333333333333 +lat_0=34.33333333333334 +lon_0=-92 +x_0=399999.99998984 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/AR.AR-NF
Proj4js.defs["EPSG:2765"] = "+proj=lcc +lat_1=34.76666666666667 +lat_2=33.3 +lat_0=32.66666666666666 +lon_0=-92 +x_0=400000 +y_0=400000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/AR.AR-S
Proj4js.defs["EPSG:3442"] = "+proj=lcc +lat_1=34.76666666666667 +lat_2=33.3 +lat_0=32.66666666666666 +lon_0=-92 +x_0=399999.99998984 +y_0=399999.99998984 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/AR.AR-SF
Proj4js.defs["EPSG:26741"] = "+proj=lcc +lat_1=41.66666666666666 +lat_2=40 +lat_0=39.33333333333334 +lon_0=-122 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA-I
Proj4js.defs["EPSG:26742"] = "+proj=lcc +lat_1=39.83333333333334 +lat_2=38.33333333333334 +lat_0=37.66666666666666 +lon_0=-122 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA-II
Proj4js.defs["EPSG:26743"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.06666666666667 +lat_0=36.5 +lon_0=-120.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA-III
Proj4js.defs["EPSG:26744"] = "+proj=lcc +lat_1=37.25 +lat_2=36 +lat_0=35.33333333333334 +lon_0=-119 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA-IV
Proj4js.defs["EPSG:26745"] = "+proj=lcc +lat_1=35.46666666666667 +lat_2=34.03333333333333 +lat_0=33.5 +lon_0=-118 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA-V
Proj4js.defs["EPSG:26746"] = "+proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666 +lon_0=-116.25 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA-VI
Proj4js.defs["EPSG:26799"] = "+proj=lcc +lat_1=34.41666666666666 +lat_2=33.86666666666667 +lat_0=34.13333333333333 +lon_0=-118.3333333333333 +x_0=1276106.450596901 +y_0=1268253.006858014 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA-VII
Proj4js.defs["EPSG:26941"] = "+proj=lcc +lat_1=41.66666666666666 +lat_2=40 +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 +y_0=500000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CA83-I
Proj4js.defs["EPSG:2225"] = "+proj=lcc +lat_1=41.66666666666666 +lat_2=40 +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA83-IF
Proj4js.defs["EPSG:26942"] = "+proj=lcc +lat_1=39.83333333333334 +lat_2=38.33333333333334 +lat_0=37.66666666666666 +lon_0=-122 +x_0=2000000 +y_0=500000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CA83-II
Proj4js.defs["EPSG:2226"] = "+proj=lcc +lat_1=39.83333333333334 +lat_2=38.33333333333334 +lat_0=37.66666666666666 +lon_0=-122 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA83-IIF
Proj4js.defs["EPSG:26943"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.06666666666667 +lat_0=36.5 +lon_0=-120.5 +x_0=2000000 +y_0=500000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CA83-III
Proj4js.defs["EPSG:2227"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.06666666666667 +lat_0=36.5 +lon_0=-120.5 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA83IIIF
Proj4js.defs["EPSG:26944"] = "+proj=lcc +lat_1=37.25 +lat_2=36 +lat_0=35.33333333333334 +lon_0=-119 +x_0=2000000 +y_0=500000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CA83-IV
Proj4js.defs["EPSG:2228"] = "+proj=lcc +lat_1=37.25 +lat_2=36 +lat_0=35.33333333333334 +lon_0=-119 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA83-IVF
Proj4js.defs["EPSG:26945"] = "+proj=lcc +lat_1=35.46666666666667 +lat_2=34.03333333333333 +lat_0=33.5 +lon_0=-118 +x_0=2000000 +y_0=500000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CA83-V
Proj4js.defs["EPSG:2229"] = "+proj=lcc +lat_1=35.46666666666667 +lat_2=34.03333333333333 +lat_0=33.5 +lon_0=-118 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA83-VF
Proj4js.defs["EPSG:26946"] = "+proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666 +lon_0=-116.25 +x_0=2000000 +y_0=500000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CA83-VI
Proj4js.defs["EPSG:2230"] = "+proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666 +lon_0=-116.25 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CA83-VIF
Proj4js.defs["EPSG:2766"] = "+proj=lcc +lat_1=41.66666666666666 +lat_2=40 +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 +y_0=500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CA.CA-I
Proj4js.defs["EPSG:2870"] = "+proj=lcc +lat_1=41.66666666666666 +lat_2=40 +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CA.CA-IF
Proj4js.defs["EPSG:2767"] = "+proj=lcc +lat_1=39.83333333333334 +lat_2=38.33333333333334 +lat_0=37.66666666666666 +lon_0=-122 +x_0=2000000 +y_0=500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CA.CA-II
Proj4js.defs["EPSG:2871"] = "+proj=lcc +lat_1=39.83333333333334 +lat_2=38.33333333333334 +lat_0=37.66666666666666 +lon_0=-122 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CA.CA-IIF
Proj4js.defs["EPSG:2768"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.06666666666667 +lat_0=36.5 +lon_0=-120.5 +x_0=2000000 +y_0=500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CA.CA-III
Proj4js.defs["EPSG:2872"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.06666666666667 +lat_0=36.5 +lon_0=-120.5 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CA.CAIIIF
Proj4js.defs["EPSG:2769"] = "+proj=lcc +lat_1=37.25 +lat_2=36 +lat_0=35.33333333333334 +lon_0=-119 +x_0=2000000 +y_0=500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CA.CA-IV
Proj4js.defs["EPSG:2873"] = "+proj=lcc +lat_1=37.25 +lat_2=36 +lat_0=35.33333333333334 +lon_0=-119 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CA.CA-IVF
Proj4js.defs["EPSG:2770"] = "+proj=lcc +lat_1=35.46666666666667 +lat_2=34.03333333333333 +lat_0=33.5 +lon_0=-118 +x_0=2000000 +y_0=500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CA.CA-V
Proj4js.defs["EPSG:2874"] = "+proj=lcc +lat_1=35.46666666666667 +lat_2=34.03333333333333 +lat_0=33.5 +lon_0=-118 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CA.CA-VF
Proj4js.defs["EPSG:2771"] = "+proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666 +lon_0=-116.25 +x_0=2000000 +y_0=500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CA.CAVI
Proj4js.defs["EPSG:2875"] = "+proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666 +lon_0=-116.25 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CA.CAVIF
Proj4js.defs["EPSG:3309"] = "+proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: CA-Teale
Proj4js.defs["EPSG:3310"] = "+proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.CA/Teale
Proj4js.defs["EPSG:3311"] = "+proj=aea +lat_1=34 +lat_2=40.5 +lat_0=0 +lon_0=-120 +x_0=0 +y_0=-4000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CA.CA/Teale
Proj4js.defs["EPSG:26754"] = "+proj=lcc +lat_1=39.75 +lat_2=38.45 +lat_0=37.83333333333334 +lon_0=-105.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CO-C
Proj4js.defs["EPSG:26753"] = "+proj=lcc +lat_1=39.71666666666667 +lat_2=40.78333333333333 +lat_0=39.33333333333334 +lon_0=-105.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CO-N
Proj4js.defs["EPSG:26755"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.23333333333333 +lat_0=36.66666666666666 +lon_0=-105.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CO-S
Proj4js.defs["EPSG:26954"] = "+proj=lcc +lat_1=39.75 +lat_2=38.45 +lat_0=37.83333333333334 +lon_0=-105.5 +x_0=914401.8289 +y_0=304800.6096 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CO83-C
Proj4js.defs["EPSG:2232"] = "+proj=lcc +lat_1=39.75 +lat_2=38.45 +lat_0=37.83333333333334 +lon_0=-105.5 +x_0=914401.8288036576 +y_0=304800.6096012192 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CO83-CF
Proj4js.defs["EPSG:26953"] = "+proj=lcc +lat_1=40.78333333333333 +lat_2=39.71666666666667 +lat_0=39.33333333333334 +lon_0=-105.5 +x_0=914401.8289 +y_0=304800.6096 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CO83-N
Proj4js.defs["EPSG:2231"] = "+proj=lcc +lat_1=40.78333333333333 +lat_2=39.71666666666667 +lat_0=39.33333333333334 +lon_0=-105.5 +x_0=914401.8288036576 +y_0=304800.6096012192 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CO83-NF
Proj4js.defs["EPSG:26955"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.23333333333333 +lat_0=36.66666666666666 +lon_0=-105.5 +x_0=914401.8289 +y_0=304800.6096 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CO83-S
Proj4js.defs["EPSG:2233"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.23333333333333 +lat_0=36.66666666666666 +lon_0=-105.5 +x_0=914401.8288036576 +y_0=304800.6096012192 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CO83-SF
Proj4js.defs["EPSG:2773"] = "+proj=lcc +lat_1=39.75 +lat_2=38.45 +lat_0=37.83333333333334 +lon_0=-105.5 +x_0=914401.8289 +y_0=304800.6096 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CO.CO-C
Proj4js.defs["EPSG:2877"] = "+proj=lcc +lat_1=39.75 +lat_2=38.45 +lat_0=37.83333333333334 +lon_0=-105.5 +x_0=914401.8288036576 +y_0=304800.6096012192 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CO.COCF
Proj4js.defs["EPSG:2772"] = "+proj=lcc +lat_1=40.78333333333333 +lat_2=39.71666666666667 +lat_0=39.33333333333334 +lon_0=-105.5 +x_0=914401.8289 +y_0=304800.6096 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CO.CO-N
Proj4js.defs["EPSG:2876"] = "+proj=lcc +lat_1=40.78333333333333 +lat_2=39.71666666666667 +lat_0=39.33333333333334 +lon_0=-105.5 +x_0=914401.8288036576 +y_0=304800.6096012192 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CO.CO-NF
Proj4js.defs["EPSG:2774"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.23333333333333 +lat_0=36.66666666666666 +lon_0=-105.5 +x_0=914401.8289 +y_0=304800.6096 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/CO.CO-S
Proj4js.defs["EPSG:2878"] = "+proj=lcc +lat_1=38.43333333333333 +lat_2=37.23333333333333 +lat_0=36.66666666666666 +lon_0=-105.5 +x_0=914401.8288036576 +y_0=304800.6096012192 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/CO.CO-SF-MOD
Proj4js.defs["EPSG:26756"] = "+proj=lcc +lat_1=41.86666666666667 +lat_2=41.2 +lat_0=40.83333333333334 +lon_0=-72.75 +x_0=182880.3657607315 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CT
Proj4js.defs["EPSG:26956"] = "+proj=lcc +lat_1=41.86666666666667 +lat_2=41.2 +lat_0=40.83333333333334 +lon_0=-72.75 +x_0=304800.6096 +y_0=152400.3048 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CT83
Proj4js.defs["EPSG:2234"] = "+proj=lcc +lat_1=41.86666666666667 +lat_2=41.2 +lat_0=40.83333333333334 +lon_0=-72.75 +x_0=304800.6096012192 +y_0=152400.3048006096 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: CT83F
Proj4js.defs["EPSG:2775"] = "+proj=lcc +lat_1=41.86666666666667 +lat_2=41.2 +lat_0=40.83333333333334 +lon_0=-72.75 +x_0=304800.6096 +y_0=152400.3048 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NE.CT
Proj4js.defs["EPSG:2879"] = "+proj=lcc +lat_1=41.86666666666667 +lat_2=41.2 +lat_0=40.83333333333334 +lon_0=-72.75 +x_0=304800.6096012192 +y_0=152400.3048006096 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NE.CTF
Proj4js.defs["EPSG:26757"] = "+proj=tmerc +lat_0=38 +lon_0=-75.41666666666667 +k=0.999995 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: DE
Proj4js.defs["EPSG:26957"] = "+proj=tmerc +lat_0=38 +lon_0=-75.41666666666667 +k=0.999995 +x_0=200000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: DE83
Proj4js.defs["EPSG:2235"] = "+proj=tmerc +lat_0=38 +lon_0=-75.41666666666667 +k=0.999995 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: DE83F
Proj4js.defs["EPSG:2776"] = "+proj=tmerc +lat_0=38 +lon_0=-75.41666666666667 +k=0.999995 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MD.DE
Proj4js.defs["EPSG:2880"] = "+proj=tmerc +lat_0=38 +lon_0=-75.41666666666667 +k=0.999995 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MD.DEF
Proj4js.defs["EPSG:26758"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-81 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: FL-E
Proj4js.defs["EPSG:26760"] = "+proj=lcc +lat_1=30.75 +lat_2=29.58333333333333 +lat_0=29 +lon_0=-84.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: FL-N
Proj4js.defs["EPSG:26759"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-82 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: FL-W
Proj4js.defs["EPSG:26958"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-81 +k=0.999941177 +x_0=200000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: FL83-E
Proj4js.defs["EPSG:2236"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-81 +k=0.999941177 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: FL83-EF
Proj4js.defs["EPSG:26960"] = "+proj=lcc +lat_1=30.75 +lat_2=29.58333333333333 +lat_0=29 +lon_0=-84.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: FL83-N
Proj4js.defs["EPSG:2238"] = "+proj=lcc +lat_1=30.75 +lat_2=29.58333333333333 +lat_0=29 +lon_0=-84.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: FL83-NF
Proj4js.defs["EPSG:26959"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-82 +k=0.999941177 +x_0=200000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: FL83-W
Proj4js.defs["EPSG:2237"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-82 +k=0.999941177 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: FL83-WF
Proj4js.defs["EPSG:2777"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-81 +k=0.999941177 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/FL.FL-E
Proj4js.defs["EPSG:2881"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-81 +k=0.999941177 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/FL.FL-EF
Proj4js.defs["EPSG:2779"] = "+proj=lcc +lat_1=30.75 +lat_2=29.58333333333333 +lat_0=29 +lon_0=-84.5 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/FL.FL-N
Proj4js.defs["EPSG:2883"] = "+proj=lcc +lat_1=30.75 +lat_2=29.58333333333333 +lat_0=29 +lon_0=-84.5 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/FL.FL-NF
Proj4js.defs["EPSG:2778"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-82 +k=0.999941177 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/FL.FL-W
Proj4js.defs["EPSG:2882"] = "+proj=tmerc +lat_0=24.33333333333333 +lon_0=-82 +k=0.999941177 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/FL.FL-WF
Proj4js.defs["EPSG:4717"] = "+proj=longlat +ellps=clrk66 +towgs84=-2,151,181,0,0,0,0 +no_defs "; //Mentor: CANAVRL.LL
Proj4js.defs["EPSG:3086"] = "+proj=aea +lat_1=24 +lat_2=31.5 +lat_0=24 +lon_0=-84 +x_0=400000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.FloridaGDL/Albers
Proj4js.defs["EPSG:3087"] = "+proj=aea +lat_1=24 +lat_2=31.5 +lat_0=24 +lon_0=-84 +x_0=400000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/FL.FloridaGDL/Albr
Proj4js.defs["EPSG:26766"] = "+proj=tmerc +lat_0=30 +lon_0=-82.16666666666667 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: GA-E
Proj4js.defs["EPSG:26767"] = "+proj=tmerc +lat_0=30 +lon_0=-84.16666666666667 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: GA-W
Proj4js.defs["EPSG:26966"] = "+proj=tmerc +lat_0=30 +lon_0=-82.16666666666667 +k=0.9999 +x_0=200000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: GA83-E
Proj4js.defs["EPSG:2239"] = "+proj=tmerc +lat_0=30 +lon_0=-82.16666666666667 +k=0.9999 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: GA83-EF
Proj4js.defs["EPSG:26967"] = "+proj=tmerc +lat_0=30 +lon_0=-84.16666666666667 +k=0.9999 +x_0=700000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: GA83-W
Proj4js.defs["EPSG:2240"] = "+proj=tmerc +lat_0=30 +lon_0=-84.16666666666667 +k=0.9999 +x_0=699999.9998983998 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: GA83-WF
Proj4js.defs["EPSG:2780"] = "+proj=tmerc +lat_0=30 +lon_0=-82.16666666666667 +k=0.9999 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/GA.GA-E
Proj4js.defs["EPSG:2884"] = "+proj=tmerc +lat_0=30 +lon_0=-82.16666666666667 +k=0.9999 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/GA.GA-EF
Proj4js.defs["EPSG:2781"] = "+proj=tmerc +lat_0=30 +lon_0=-84.16666666666667 +k=0.9999 +x_0=700000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/GA.GA-W
Proj4js.defs["EPSG:2885"] = "+proj=tmerc +lat_0=30 +lon_0=-84.16666666666667 +k=0.9999 +x_0=699999.9998983998 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/GA.GA-WF
Proj4js.defs["EPSG:32164"] = "+proj=tmerc +lat_0=0 +lon_0=-99 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-14N
Proj4js.defs["EPSG:32165"] = "+proj=tmerc +lat_0=0 +lon_0=-93 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-15NF
Proj4js.defs["EPSG:32166"] = "+proj=tmerc +lat_0=0 +lon_0=-87 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-16NF
Proj4js.defs["EPSG:32167"] = "+proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-17NF
Proj4js.defs["EPSG:32664"] = "+proj=tmerc +lat_0=0 +lon_0=-99 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=WGS84 +datum=WGS84 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WGS84.BLM-14NF
Proj4js.defs["EPSG:32665"] = "+proj=tmerc +lat_0=0 +lon_0=-93 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=WGS84 +datum=WGS84 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WGS84.BLM-15NF
Proj4js.defs["EPSG:32666"] = "+proj=tmerc +lat_0=0 +lon_0=-87 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=WGS84 +datum=WGS84 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WGS84.BLM-16NF
Proj4js.defs["EPSG:32667"] = "+proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=WGS84 +datum=WGS84 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WGS84.BLM-17NF
Proj4js.defs["EPSG:26961"] = "+proj=tmerc +lat_0=18.83333333333333 +lon_0=-155.5 +k=0.999966667 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: HI83-1
Proj4js.defs["EPSG:26962"] = "+proj=tmerc +lat_0=20.33333333333333 +lon_0=-156.6666666666667 +k=0.999966667 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: HI83-2
Proj4js.defs["EPSG:26963"] = "+proj=tmerc +lat_0=21.16666666666667 +lon_0=-158 +k=0.99999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: HI83-3
Proj4js.defs["EPSG:3759"] = "+proj=tmerc +lat_0=21.16666666666667 +lon_0=-158 +k=0.99999 +x_0=500000.00001016 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HI83-3F
Proj4js.defs["EPSG:26964"] = "+proj=tmerc +lat_0=21.83333333333333 +lon_0=-159.5 +k=0.99999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: HI83-4
Proj4js.defs["EPSG:26965"] = "+proj=tmerc +lat_0=21.66666666666667 +lon_0=-160.1666666666667 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: HI83-5
Proj4js.defs["EPSG:2782"] = "+proj=tmerc +lat_0=18.83333333333333 +lon_0=-155.5 +k=0.999966667 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/HI.HI-1
Proj4js.defs["EPSG:2783"] = "+proj=tmerc +lat_0=20.33333333333333 +lon_0=-156.6666666666667 +k=0.999966667 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/HI.HI-2
Proj4js.defs["EPSG:2784"] = "+proj=tmerc +lat_0=21.16666666666667 +lon_0=-158 +k=0.99999 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/HI.HI-3
Proj4js.defs["EPSG:3760"] = "+proj=tmerc +lat_0=21.16666666666667 +lon_0=-158 +k=0.99999 +x_0=500000.00001016 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/HI.HI-3Fa
Proj4js.defs["EPSG:2785"] = "+proj=tmerc +lat_0=21.83333333333333 +lon_0=-159.5 +k=0.99999 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/HI.HI-4
Proj4js.defs["EPSG:2786"] = "+proj=tmerc +lat_0=21.66666666666667 +lon_0=-160.1666666666667 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/HI.HI-5
Proj4js.defs["EPSG:4135"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: LL-OLDHI
Proj4js.defs["EPSG:3561"] = "+proj=tmerc +lat_0=18.83333333333333 +lon_0=-155.5 +k=0.999966667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OLDHI.Hawaii-1
Proj4js.defs["EPSG:3562"] = "+proj=tmerc +lat_0=20.33333333333333 +lon_0=-156.6666666666667 +k=0.999966667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OLDHI.Hawaii-2
Proj4js.defs["EPSG:3563"] = "+proj=tmerc +lat_0=21.16666666666667 +lon_0=-158 +k=0.99999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OLDHI.Hawaii-3
Proj4js.defs["EPSG:3564"] = "+proj=tmerc +lat_0=21.83333333333333 +lon_0=-159.5 +k=0.99999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OLDHI.Hawaii-4
Proj4js.defs["EPSG:3565"] = "+proj=tmerc +lat_0=21.66666666666667 +lon_0=-160.1666666666667 +k=1 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OLDHI.Hawaii-5
Proj4js.defs["EPSG:26769"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-114 +k=0.9999473679999999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ID-C
Proj4js.defs["EPSG:26768"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-112.1666666666667 +k=0.9999473679999999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ID-E
Proj4js.defs["EPSG:26770"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-115.75 +k=0.999933333 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ID-W
Proj4js.defs["EPSG:26969"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-114 +k=0.9999473679999999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ID83-C
Proj4js.defs["EPSG:2242"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-114 +k=0.9999473679999999 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ID83-CF
Proj4js.defs["EPSG:26968"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-112.1666666666667 +k=0.9999473679999999 +x_0=200000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ID83-E
Proj4js.defs["EPSG:2241"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-112.1666666666667 +k=0.9999473679999999 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ID83-EF
Proj4js.defs["EPSG:26970"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-115.75 +k=0.999933333 +x_0=800000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ID83-W
Proj4js.defs["EPSG:2243"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-115.75 +k=0.999933333 +x_0=800000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ID83-WF
Proj4js.defs["EPSG:2788"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-114 +k=0.9999473679999999 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MT.ID-C
Proj4js.defs["EPSG:2887"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-114 +k=0.9999473679999999 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MT.ID-CF
Proj4js.defs["EPSG:2787"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-112.1666666666667 +k=0.9999473679999999 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MT.ID-E
Proj4js.defs["EPSG:2886"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-112.1666666666667 +k=0.9999473679999999 +x_0=200000.0001016002 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MT.ID-EF
Proj4js.defs["EPSG:2789"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-115.75 +k=0.999933333 +x_0=800000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MT.ID-W
Proj4js.defs["EPSG:2888"] = "+proj=tmerc +lat_0=41.66666666666666 +lon_0=-115.75 +k=0.999933333 +x_0=800000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MT.ID-WF
Proj4js.defs["EPSG:26771"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-88.33333333333333 +k=0.9999749999999999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IL-E
Proj4js.defs["EPSG:26772"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-90.16666666666667 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IL-W
Proj4js.defs["EPSG:26971"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-88.33333333333333 +k=0.9999749999999999 +x_0=300000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: IL83-E
Proj4js.defs["EPSG:3435"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-88.33333333333333 +k=0.9999749999999999 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IL83-EF
Proj4js.defs["EPSG:26972"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-90.16666666666667 +k=0.999941177 +x_0=700000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: IL83-W
Proj4js.defs["EPSG:3436"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-90.16666666666667 +k=0.999941177 +x_0=699999.9999898402 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IL83-WF
Proj4js.defs["EPSG:2790"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-88.33333333333333 +k=0.9999749999999999 +x_0=300000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/IL.IL-E
Proj4js.defs["EPSG:3443"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-88.33333333333333 +k=0.9999749999999999 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/IL.IL-EF
Proj4js.defs["EPSG:2791"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-90.16666666666667 +k=0.999941177 +x_0=700000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/IL.IL-W
Proj4js.defs["EPSG:3444"] = "+proj=tmerc +lat_0=36.66666666666666 +lon_0=-90.16666666666667 +k=0.999941177 +x_0=699999.9999898402 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/IL.IL-WF
Proj4js.defs["EPSG:26773"] = "+proj=tmerc +lat_0=37.5 +lon_0=-85.66666666666667 +k=0.999966667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IN-E
Proj4js.defs["EPSG:26774"] = "+proj=tmerc +lat_0=37.5 +lon_0=-87.08333333333333 +k=0.999966667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IN-W
Proj4js.defs["EPSG:26973"] = "+proj=tmerc +lat_0=37.5 +lon_0=-85.66666666666667 +k=0.999966667 +x_0=100000 +y_0=250000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: IN83-E
Proj4js.defs["EPSG:2965"] = "+proj=tmerc +lat_0=37.5 +lon_0=-85.66666666666667 +k=0.999966667 +x_0=99999.99989839978 +y_0=249999.9998983998 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IN83-EF
Proj4js.defs["EPSG:26974"] = "+proj=tmerc +lat_0=37.5 +lon_0=-87.08333333333333 +k=0.999966667 +x_0=900000 +y_0=250000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: IN83-W
Proj4js.defs["EPSG:2966"] = "+proj=tmerc +lat_0=37.5 +lon_0=-87.08333333333333 +k=0.999966667 +x_0=900000 +y_0=249999.9998983998 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IN83-WF
Proj4js.defs["EPSG:2792"] = "+proj=tmerc +lat_0=37.5 +lon_0=-85.66666666666667 +k=0.999966667 +x_0=100000 +y_0=250000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/IN.IN-E
Proj4js.defs["EPSG:2967"] = "+proj=tmerc +lat_0=37.5 +lon_0=-85.66666666666667 +k=0.999966667 +x_0=99999.99989839978 +y_0=249999.9998983998 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/IN.IN-EF
Proj4js.defs["EPSG:2793"] = "+proj=tmerc +lat_0=37.5 +lon_0=-87.08333333333333 +k=0.999966667 +x_0=900000 +y_0=250000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/IN.IN-W
Proj4js.defs["EPSG:2968"] = "+proj=tmerc +lat_0=37.5 +lon_0=-87.08333333333333 +k=0.999966667 +x_0=900000 +y_0=249999.9998983998 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/IN.IN-WF
Proj4js.defs["EPSG:26775"] = "+proj=lcc +lat_1=43.26666666666667 +lat_2=42.06666666666667 +lat_0=41.5 +lon_0=-93.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IA-N
Proj4js.defs["EPSG:26776"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.61666666666667 +lat_0=40 +lon_0=-93.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IA-S
Proj4js.defs["EPSG:26975"] = "+proj=lcc +lat_1=43.26666666666667 +lat_2=42.06666666666667 +lat_0=41.5 +lon_0=-93.5 +x_0=1500000 +y_0=1000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: IA83-N
Proj4js.defs["EPSG:3417"] = "+proj=lcc +lat_1=43.26666666666667 +lat_2=42.06666666666667 +lat_0=41.5 +lon_0=-93.5 +x_0=1500000 +y_0=999999.9999898402 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IA83-NF
Proj4js.defs["EPSG:26976"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.61666666666667 +lat_0=40 +lon_0=-93.5 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: IA83-S
Proj4js.defs["EPSG:3418"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.61666666666667 +lat_0=40 +lon_0=-93.5 +x_0=500000.00001016 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: IA83-SF
Proj4js.defs["EPSG:2794"] = "+proj=lcc +lat_1=43.26666666666667 +lat_2=42.06666666666667 +lat_0=41.5 +lon_0=-93.5 +x_0=1500000 +y_0=1000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/IA.IA-N
Proj4js.defs["EPSG:3425"] = "+proj=lcc +lat_1=43.26666666666667 +lat_2=42.06666666666667 +lat_0=41.5 +lon_0=-93.5 +x_0=1500000 +y_0=999999.9999898402 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/IA.IA-NF
Proj4js.defs["EPSG:2795"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.61666666666667 +lat_0=40 +lon_0=-93.5 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/IA.IA-S
Proj4js.defs["EPSG:3426"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.61666666666667 +lat_0=40 +lon_0=-93.5 +x_0=500000.00001016 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/IA.IA-SF
Proj4js.defs["EPSG:26777"] = "+proj=lcc +lat_1=39.78333333333333 +lat_2=38.71666666666667 +lat_0=38.33333333333334 +lon_0=-98 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KS-N
Proj4js.defs["EPSG:26778"] = "+proj=lcc +lat_1=38.56666666666667 +lat_2=37.26666666666667 +lat_0=36.66666666666666 +lon_0=-98.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KS-S
Proj4js.defs["EPSG:26977"] = "+proj=lcc +lat_1=39.78333333333333 +lat_2=38.71666666666667 +lat_0=38.33333333333334 +lon_0=-98 +x_0=400000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: KS83-N
Proj4js.defs["EPSG:3419"] = "+proj=lcc +lat_1=39.78333333333333 +lat_2=38.71666666666667 +lat_0=38.33333333333334 +lon_0=-98 +x_0=399999.99998984 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KS83-NF
Proj4js.defs["EPSG:26978"] = "+proj=lcc +lat_1=38.56666666666667 +lat_2=37.26666666666667 +lat_0=36.66666666666666 +lon_0=-98.5 +x_0=400000 +y_0=400000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: KS83-S
Proj4js.defs["EPSG:3420"] = "+proj=lcc +lat_1=38.56666666666667 +lat_2=37.26666666666667 +lat_0=36.66666666666666 +lon_0=-98.5 +x_0=399999.99998984 +y_0=399999.99998984 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KS83-SF
Proj4js.defs["EPSG:2796"] = "+proj=lcc +lat_1=39.78333333333333 +lat_2=38.71666666666667 +lat_0=38.33333333333334 +lon_0=-98 +x_0=400000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/KS.KS-N
Proj4js.defs["EPSG:3427"] = "+proj=lcc +lat_1=39.78333333333333 +lat_2=38.71666666666667 +lat_0=38.33333333333334 +lon_0=-98 +x_0=399999.99998984 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/KS.KS-NF
Proj4js.defs["EPSG:2797"] = "+proj=lcc +lat_1=38.56666666666667 +lat_2=37.26666666666667 +lat_0=36.66666666666666 +lon_0=-98.5 +x_0=400000 +y_0=400000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/KS.KS-S
Proj4js.defs["EPSG:3428"] = "+proj=lcc +lat_1=38.56666666666667 +lat_2=37.26666666666667 +lat_0=36.66666666666666 +lon_0=-98.5 +x_0=399999.99998984 +y_0=399999.99998984 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/KS.KS-SF
Proj4js.defs["EPSG:26779"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=38.96666666666667 +lat_0=37.5 +lon_0=-84.25 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KY-N
Proj4js.defs["EPSG:26780"] = "+proj=lcc +lat_1=36.73333333333333 +lat_2=37.93333333333333 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KY-S
Proj4js.defs["EPSG:2205"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=38.96666666666667 +lat_0=37.5 +lon_0=-84.25 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: KY83-N
Proj4js.defs["EPSG:2246"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=38.96666666666667 +lat_0=37.5 +lon_0=-84.25 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KY83-NF
Proj4js.defs["EPSG:26980"] = "+proj=lcc +lat_1=37.93333333333333 +lat_2=36.73333333333333 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=500000 +y_0=500000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: KY83-S
Proj4js.defs["EPSG:2247"] = "+proj=lcc +lat_1=37.93333333333333 +lat_2=36.73333333333333 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=500000.0001016001 +y_0=500000.0001016001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KY83-SF
Proj4js.defs["EPSG:2798"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=38.96666666666667 +lat_0=37.5 +lon_0=-84.25 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/KY.KY-N
Proj4js.defs["EPSG:2891"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=38.96666666666667 +lat_0=37.5 +lon_0=-84.25 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/KY.KY-NF
Proj4js.defs["EPSG:2799"] = "+proj=lcc +lat_1=37.93333333333333 +lat_2=36.73333333333333 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=500000 +y_0=500000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/KY.KY-S
Proj4js.defs["EPSG:2892"] = "+proj=lcc +lat_1=37.93333333333333 +lat_2=36.73333333333333 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=500000.0001016001 +y_0=500000.0001016001 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/KY.KY-SF
Proj4js.defs["EPSG:3088"] = "+proj=lcc +lat_1=37.08333333333334 +lat_2=38.66666666666666 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=1500000 +y_0=1000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: KY83
Proj4js.defs["EPSG:3089"] = "+proj=lcc +lat_1=37.08333333333334 +lat_2=38.66666666666666 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=1500000 +y_0=999999.9998983998 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KY83F
Proj4js.defs["EPSG:3090"] = "+proj=lcc +lat_1=37.08333333333334 +lat_2=38.66666666666666 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=1500000 +y_0=1000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/KY.KY
Proj4js.defs["EPSG:3091"] = "+proj=lcc +lat_1=37.08333333333334 +lat_2=38.66666666666666 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=1500000 +y_0=999999.9998983998 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/KY.KYF
Proj4js.defs["EPSG:26781"] = "+proj=lcc +lat_1=31.16666666666667 +lat_2=32.66666666666666 +lat_0=30.66666666666667 +lon_0=-92.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: LA-N
Proj4js.defs["EPSG:32099"] = "+proj=lcc +lat_1=27.83333333333333 +lat_2=26.16666666666667 +lat_0=25.66666666666667 +lon_0=-91.33333333333333 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: LA-O
Proj4js.defs["EPSG:26981"] = "+proj=lcc +lat_1=32.66666666666666 +lat_2=31.16666666666667 +lat_0=30.5 +lon_0=-92.5 +x_0=1000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: LA83-N
Proj4js.defs["EPSG:3451"] = "+proj=lcc +lat_1=32.66666666666666 +lat_2=31.16666666666667 +lat_0=30.5 +lon_0=-92.5 +x_0=999999.9999898402 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: LA83-NF
Proj4js.defs["EPSG:32199"] = "+proj=lcc +lat_1=27.83333333333333 +lat_2=26.16666666666667 +lat_0=25.5 +lon_0=-91.33333333333333 +x_0=1000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: LA83-O
Proj4js.defs["EPSG:3453"] = "+proj=lcc +lat_1=27.83333333333333 +lat_2=26.16666666666667 +lat_0=25.5 +lon_0=-91.33333333333333 +x_0=999999.9999898402 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: LA83-OF
Proj4js.defs["EPSG:2800"] = "+proj=lcc +lat_1=32.66666666666666 +lat_2=31.16666666666667 +lat_0=30.5 +lon_0=-92.5 +x_0=1000000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/LA.LA-N
Proj4js.defs["EPSG:3456"] = "+proj=lcc +lat_1=32.66666666666666 +lat_2=31.16666666666667 +lat_0=30.5 +lon_0=-92.5 +x_0=999999.9999898402 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/LA.LA-NF
Proj4js.defs["EPSG:2801"] = "+proj=lcc +lat_1=30.7 +lat_2=29.3 +lat_0=28.5 +lon_0=-91.33333333333333 +x_0=1000000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/LA.LA-S
Proj4js.defs["EPSG:3457"] = "+proj=lcc +lat_1=30.7 +lat_2=29.3 +lat_0=28.5 +lon_0=-91.33333333333333 +x_0=999999.9999898402 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/LA.LA-SF
Proj4js.defs["EPSG:3452"] = "+proj=lcc +lat_1=30.7 +lat_2=29.3 +lat_0=28.5 +lon_0=-91.33333333333333 +x_0=999999.9999898402 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: LA83-SF-MOD
Proj4js.defs["EPSG:26982"] = "+proj=lcc +lat_1=30.7 +lat_2=29.3 +lat_0=28.5 +lon_0=-91.33333333333333 +x_0=1000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: LA83-S-MOD
Proj4js.defs["EPSG:26782"] = "+proj=lcc +lat_1=29.3 +lat_2=30.7 +lat_0=28.66666666666667 +lon_0=-91.33333333333333 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: LA-S-MOD
Proj4js.defs["EPSG:26783"] = "+proj=tmerc +lat_0=43.83333333333334 +lon_0=-68.5 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ME-E
Proj4js.defs["EPSG:26784"] = "+proj=tmerc +lat_0=42.83333333333334 +lon_0=-70.16666666666667 +k=0.999966667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ME-W
Proj4js.defs["EPSG:26983"] = "+proj=tmerc +lat_0=43.66666666666666 +lon_0=-68.5 +k=0.9999 +x_0=300000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ME83-E
Proj4js.defs["EPSG:26847"] = "+proj=tmerc +lat_0=43.66666666666666 +lon_0=-68.5 +k=0.9999 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ME83-EF
Proj4js.defs["EPSG:26984"] = "+proj=tmerc +lat_0=42.83333333333334 +lon_0=-70.16666666666667 +k=0.999966667 +x_0=900000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ME83-W
Proj4js.defs["EPSG:26848"] = "+proj=tmerc +lat_0=42.83333333333334 +lon_0=-70.16666666666667 +k=0.999966667 +x_0=900000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ME83-WF
Proj4js.defs["EPSG:2802"] = "+proj=tmerc +lat_0=43.66666666666666 +lon_0=-68.5 +k=0.9999 +x_0=300000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/ME.ME-E
Proj4js.defs["EPSG:26855"] = "+proj=tmerc +lat_0=43.66666666666666 +lon_0=-68.5 +k=0.9999 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/ME.ME-EF
Proj4js.defs["EPSG:2803"] = "+proj=tmerc +lat_0=42.83333333333334 +lon_0=-70.16666666666667 +k=0.999966667 +x_0=900000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/ME.ME-W
Proj4js.defs["EPSG:26856"] = "+proj=tmerc +lat_0=42.83333333333334 +lon_0=-70.16666666666667 +k=0.999966667 +x_0=900000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/ME.ME-WF
Proj4js.defs["EPSG:3463"] = "+proj=tmerc +lat_0=43.5 +lon_0=-69.125 +k=0.99998 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ME2K-C
Proj4js.defs["EPSG:3072"] = "+proj=tmerc +lat_0=43.83333333333334 +lon_0=-67.875 +k=0.99998 +x_0=700000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ME2K-E
Proj4js.defs["EPSG:3074"] = "+proj=tmerc +lat_0=42.83333333333334 +lon_0=-70.375 +k=0.99998 +x_0=300000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ME2K-W
Proj4js.defs["EPSG:3075"] = "+proj=tmerc +lat_0=43.83333333333334 +lon_0=-67.875 +k=0.99998 +x_0=700000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/ME.ME2K-E
Proj4js.defs["EPSG:3464"] = "+proj=tmerc +lat_0=43.5 +lon_0=-69.125 +k=0.99998 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/ME.ME2K-C/a
Proj4js.defs["EPSG:3077"] = "+proj=tmerc +lat_0=42.83333333333334 +lon_0=-70.375 +k=0.99998 +x_0=300000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/ME.ME2K-W
Proj4js.defs["EPSG:26785"] = "+proj=lcc +lat_1=38.3 +lat_2=39.45 +lat_0=37.83333333333334 +lon_0=-77 +x_0=243840.4876809754 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MD
Proj4js.defs["EPSG:26985"] = "+proj=lcc +lat_1=39.45 +lat_2=38.3 +lat_0=37.66666666666666 +lon_0=-77 +x_0=400000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MD83
Proj4js.defs["EPSG:2248"] = "+proj=lcc +lat_1=39.45 +lat_2=38.3 +lat_0=37.66666666666666 +lon_0=-77 +x_0=399999.9998983998 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MD83F
Proj4js.defs["EPSG:2804"] = "+proj=lcc +lat_1=39.45 +lat_2=38.3 +lat_0=37.66666666666666 +lon_0=-77 +x_0=400000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MD.MD
Proj4js.defs["EPSG:2893"] = "+proj=lcc +lat_1=39.45 +lat_2=38.3 +lat_0=37.66666666666666 +lon_0=-77 +x_0=399999.9998983998 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MD.MDF
Proj4js.defs["EPSG:26786"] = "+proj=lcc +lat_1=41.71666666666667 +lat_2=42.68333333333333 +lat_0=41 +lon_0=-71.5 +x_0=182880.3657607315 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MA
Proj4js.defs["EPSG:26986"] = "+proj=lcc +lat_1=42.68333333333333 +lat_2=41.71666666666667 +lat_0=41 +lon_0=-71.5 +x_0=200000 +y_0=750000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MA83
Proj4js.defs["EPSG:2249"] = "+proj=lcc +lat_1=42.68333333333333 +lat_2=41.71666666666667 +lat_0=41 +lon_0=-71.5 +x_0=200000.0001016002 +y_0=750000 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MA83F
Proj4js.defs["EPSG:26987"] = "+proj=lcc +lat_1=41.48333333333333 +lat_2=41.28333333333333 +lat_0=41 +lon_0=-70.5 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MA83-IS
Proj4js.defs["EPSG:2250"] = "+proj=lcc +lat_1=41.48333333333333 +lat_2=41.28333333333333 +lat_0=41 +lon_0=-70.5 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MA83-ISF
Proj4js.defs["EPSG:2805"] = "+proj=lcc +lat_1=42.68333333333333 +lat_2=41.71666666666667 +lat_0=41 +lon_0=-71.5 +x_0=200000 +y_0=750000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NE.MA
Proj4js.defs["EPSG:2806"] = "+proj=lcc +lat_1=41.48333333333333 +lat_2=41.28333333333333 +lat_0=41 +lon_0=-70.5 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NE.MA-IS
Proj4js.defs["EPSG:2894"] = "+proj=lcc +lat_1=42.68333333333333 +lat_2=41.71666666666667 +lat_0=41 +lon_0=-71.5 +x_0=200000.0001016002 +y_0=750000 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NE.MAF
Proj4js.defs["EPSG:2895"] = "+proj=lcc +lat_1=41.48333333333333 +lat_2=41.28333333333333 +lat_0=41 +lon_0=-70.5 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NE.MA-ISF
Proj4js.defs["EPSG:26787"] = "+proj=lcc +lat_1=41.28333333333333 +lat_2=41.48333333333333 +lat_0=41 +lon_0=-70.5 +x_0=60960.12192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MA27-IS
Proj4js.defs["EPSG:26812"] = "+proj=lcc +lat_1=44.18333333333333 +lat_2=45.7 +lat_0=43.31666666666667 +lon_0=-84.33333333333333 +x_0=609601.2192024384 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MI27-C
Proj4js.defs["EPSG:26811"] = "+proj=lcc +lat_1=45.48333333333333 +lat_2=47.08333333333334 +lat_0=44.78333333333333 +lon_0=-87 +x_0=609601.2192024384 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MI27-N
Proj4js.defs["EPSG:26813"] = "+proj=lcc +lat_1=42.1 +lat_2=43.66666666666666 +lat_0=41.5 +lon_0=-84.33333333333333 +x_0=609601.2192024384 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MI27-S
Proj4js.defs["EPSG:26989"] = "+proj=lcc +lat_1=45.7 +lat_2=44.18333333333333 +lat_0=43.31666666666667 +lon_0=-84.36666666666666 +x_0=6000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MI83-C
Proj4js.defs["EPSG:2252"] = "+proj=lcc +lat_1=45.7 +lat_2=44.18333333333333 +lat_0=43.31666666666667 +lon_0=-84.36666666666666 +x_0=5999999.999976001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: MI83-CIF
Proj4js.defs["EPSG:26988"] = "+proj=lcc +lat_1=47.08333333333334 +lat_2=45.48333333333333 +lat_0=44.78333333333333 +lon_0=-87 +x_0=8000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MI83-N
Proj4js.defs["EPSG:2251"] = "+proj=lcc +lat_1=47.08333333333334 +lat_2=45.48333333333333 +lat_0=44.78333333333333 +lon_0=-87 +x_0=7999999.999968001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: MI83-NIF
Proj4js.defs["EPSG:26990"] = "+proj=lcc +lat_1=43.66666666666666 +lat_2=42.1 +lat_0=41.5 +lon_0=-84.36666666666666 +x_0=4000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MI83-S
Proj4js.defs["EPSG:2253"] = "+proj=lcc +lat_1=43.66666666666666 +lat_2=42.1 +lat_0=41.5 +lon_0=-84.36666666666666 +x_0=3999999.999984 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: MI83-SIF
Proj4js.defs["EPSG:2808"] = "+proj=lcc +lat_1=45.7 +lat_2=44.18333333333333 +lat_0=43.31666666666667 +lon_0=-84.36666666666666 +x_0=6000000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MI.MI-C
Proj4js.defs["EPSG:2897"] = "+proj=lcc +lat_1=45.7 +lat_2=44.18333333333333 +lat_0=43.31666666666667 +lon_0=-84.36666666666666 +x_0=5999999.999976001 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/MI.MI-CIF
Proj4js.defs["EPSG:2807"] = "+proj=lcc +lat_1=47.08333333333334 +lat_2=45.48333333333333 +lat_0=44.78333333333333 +lon_0=-87 +x_0=8000000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MI.MI-N
Proj4js.defs["EPSG:2896"] = "+proj=lcc +lat_1=47.08333333333334 +lat_2=45.48333333333333 +lat_0=44.78333333333333 +lon_0=-87 +x_0=7999999.999968001 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/MI.MI-NIF
Proj4js.defs["EPSG:2809"] = "+proj=lcc +lat_1=43.66666666666666 +lat_2=42.1 +lat_0=41.5 +lon_0=-84.36666666666666 +x_0=4000000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MI.MI-S
Proj4js.defs["EPSG:2898"] = "+proj=lcc +lat_1=43.66666666666666 +lat_2=42.1 +lat_0=41.5 +lon_0=-84.36666666666666 +x_0=3999999.999984 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/MI.MI-SIF
Proj4js.defs["EPSG:3078"] = "+proj=omerc +lat_0=45.30916666666666 +lonc=-86 +alpha=337.25556 +k=0.9996 +x_0=2546731.496 +y_0=-4354009.816 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MI83-OB
Proj4js.defs["EPSG:26801"] = "+proj=tmerc +lat_0=41.5 +lon_0=-83.66666666666667 +k=0.999942857 +x_0=152400.3048006096 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MICHIGAN.East
Proj4js.defs["EPSG:4268"] = "+proj=longlat +a=6378450.047548896 +b=6356826.621488444 +no_defs "; //Mentor: MICHIGAN.LL
Proj4js.defs["EPSG:26802"] = "+proj=tmerc +lat_0=41.5 +lon_0=-85.75 +k=0.999909091 +x_0=152400.3048006096 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MICHIGAN.OldCentral
Proj4js.defs["EPSG:26803"] = "+proj=tmerc +lat_0=41.5 +lon_0=-88.75 +k=0.999909091 +x_0=152400.3048006096 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MICHIGAN.West
Proj4js.defs["EPSG:3079"] = "+proj=omerc +lat_0=45.30916666666666 +lonc=-86 +alpha=337.25556 +k=0.9996 +x_0=2546731.496 +y_0=-4354009.816 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MI.MichiganGeoRef
Proj4js.defs["EPSG:26792"] = "+proj=lcc +lat_1=45.61666666666667 +lat_2=47.05 +lat_0=45 +lon_0=-94.25 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MN-C
Proj4js.defs["EPSG:26791"] = "+proj=lcc +lat_1=47.03333333333333 +lat_2=48.63333333333333 +lat_0=46.5 +lon_0=-93.09999999999999 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MN-N
Proj4js.defs["EPSG:26793"] = "+proj=lcc +lat_1=43.78333333333333 +lat_2=45.21666666666667 +lat_0=43 +lon_0=-94 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MN-S
Proj4js.defs["EPSG:26992"] = "+proj=lcc +lat_1=47.05 +lat_2=45.61666666666667 +lat_0=45 +lon_0=-94.25 +x_0=800000 +y_0=100000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MN83-C
Proj4js.defs["EPSG:26850"] = "+proj=lcc +lat_1=47.05 +lat_2=45.61666666666667 +lat_0=45 +lon_0=-94.25 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MN83-CF
Proj4js.defs["EPSG:26991"] = "+proj=lcc +lat_1=48.63333333333333 +lat_2=47.03333333333333 +lat_0=46.5 +lon_0=-93.09999999999999 +x_0=800000 +y_0=100000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MN83-N
Proj4js.defs["EPSG:26849"] = "+proj=lcc +lat_1=48.63333333333333 +lat_2=47.03333333333333 +lat_0=46.5 +lon_0=-93.09999999999999 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MN83-NF
Proj4js.defs["EPSG:26993"] = "+proj=lcc +lat_1=45.21666666666667 +lat_2=43.78333333333333 +lat_0=43 +lon_0=-94 +x_0=800000 +y_0=100000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MN83-S
Proj4js.defs["EPSG:26851"] = "+proj=lcc +lat_1=45.21666666666667 +lat_2=43.78333333333333 +lat_0=43 +lon_0=-94 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MN83-SF
Proj4js.defs["EPSG:2811"] = "+proj=lcc +lat_1=47.05 +lat_2=45.61666666666667 +lat_0=45 +lon_0=-94.25 +x_0=800000 +y_0=100000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MN.MN-C
Proj4js.defs["EPSG:26858"] = "+proj=lcc +lat_1=47.05 +lat_2=45.61666666666667 +lat_0=45 +lon_0=-94.25 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MN.MN-CF
Proj4js.defs["EPSG:2810"] = "+proj=lcc +lat_1=48.63333333333333 +lat_2=47.03333333333333 +lat_0=46.5 +lon_0=-93.09999999999999 +x_0=800000 +y_0=100000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MN.MN-N
Proj4js.defs["EPSG:26857"] = "+proj=lcc +lat_1=48.63333333333333 +lat_2=47.03333333333333 +lat_0=46.5 +lon_0=-93.09999999999999 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MN.MN-NF
Proj4js.defs["EPSG:2812"] = "+proj=lcc +lat_1=45.21666666666667 +lat_2=43.78333333333333 +lat_0=43 +lon_0=-94 +x_0=800000 +y_0=100000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MN.MN-S
Proj4js.defs["EPSG:26859"] = "+proj=lcc +lat_1=45.21666666666667 +lat_2=43.78333333333333 +lat_0=43 +lon_0=-94 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MN.MN-SF
Proj4js.defs["EPSG:26794"] = "+proj=tmerc +lat_0=29.66666666666667 +lon_0=-88.83333333333333 +k=0.99996 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MS-E
Proj4js.defs["EPSG:26795"] = "+proj=tmerc +lat_0=30.5 +lon_0=-90.33333333333333 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MS-W
Proj4js.defs["EPSG:26994"] = "+proj=tmerc +lat_0=29.5 +lon_0=-88.83333333333333 +k=0.99995 +x_0=300000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MS83-E
Proj4js.defs["EPSG:2254"] = "+proj=tmerc +lat_0=29.5 +lon_0=-88.83333333333333 +k=0.99995 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MS83-EF
Proj4js.defs["EPSG:26995"] = "+proj=tmerc +lat_0=29.5 +lon_0=-90.33333333333333 +k=0.99995 +x_0=700000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MS83-W
Proj4js.defs["EPSG:2255"] = "+proj=tmerc +lat_0=29.5 +lon_0=-90.33333333333333 +k=0.99995 +x_0=699999.9998983998 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MS83-WF
Proj4js.defs["EPSG:2813"] = "+proj=tmerc +lat_0=29.5 +lon_0=-88.83333333333333 +k=0.99995 +x_0=300000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MS.MS-E
Proj4js.defs["EPSG:2899"] = "+proj=tmerc +lat_0=29.5 +lon_0=-88.83333333333333 +k=0.99995 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MS.MS-EF
Proj4js.defs["EPSG:2814"] = "+proj=tmerc +lat_0=29.5 +lon_0=-90.33333333333333 +k=0.99995 +x_0=700000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MS.MS-W
Proj4js.defs["EPSG:2900"] = "+proj=tmerc +lat_0=29.5 +lon_0=-90.33333333333333 +k=0.99995 +x_0=699999.9998983998 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/MS.MS-WF/a
Proj4js.defs["EPSG:26797"] = "+proj=tmerc +lat_0=35.83333333333334 +lon_0=-92.5 +k=0.999933333 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MO-C
Proj4js.defs["EPSG:26796"] = "+proj=tmerc +lat_0=35.83333333333334 +lon_0=-90.5 +k=0.999933333 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MO-E
Proj4js.defs["EPSG:26798"] = "+proj=tmerc +lat_0=36.16666666666666 +lon_0=-94.5 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MO-W
Proj4js.defs["EPSG:26997"] = "+proj=tmerc +lat_0=35.83333333333334 +lon_0=-92.5 +k=0.999933333 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MO83-C
Proj4js.defs["EPSG:26996"] = "+proj=tmerc +lat_0=35.83333333333334 +lon_0=-90.5 +k=0.999933333 +x_0=250000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MO83-E
Proj4js.defs["EPSG:26998"] = "+proj=tmerc +lat_0=36.16666666666666 +lon_0=-94.5 +k=0.999941177 +x_0=850000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MO83-W
Proj4js.defs["EPSG:2816"] = "+proj=tmerc +lat_0=35.83333333333334 +lon_0=-92.5 +k=0.999933333 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MO.MO-C
Proj4js.defs["EPSG:2815"] = "+proj=tmerc +lat_0=35.83333333333334 +lon_0=-90.5 +k=0.999933333 +x_0=250000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MO.MO-E
Proj4js.defs["EPSG:2817"] = "+proj=tmerc +lat_0=36.16666666666666 +lon_0=-94.5 +k=0.999941177 +x_0=850000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MO.MO-W
Proj4js.defs["EPSG:32002"] = "+proj=lcc +lat_1=47.88333333333333 +lat_2=46.45 +lat_0=45.83333333333334 +lon_0=-109.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MT-C
Proj4js.defs["EPSG:32001"] = "+proj=lcc +lat_1=48.71666666666667 +lat_2=47.85 +lat_0=47 +lon_0=-109.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MT-N
Proj4js.defs["EPSG:32003"] = "+proj=lcc +lat_1=46.4 +lat_2=44.86666666666667 +lat_0=44 +lon_0=-109.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MT-S
Proj4js.defs["EPSG:32100"] = "+proj=lcc +lat_1=49 +lat_2=45 +lat_0=44.25 +lon_0=-109.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: MT83
Proj4js.defs["EPSG:2256"] = "+proj=lcc +lat_1=49 +lat_2=45 +lat_0=44.25 +lon_0=-109.5 +x_0=599999.9999976 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: MT83IF
Proj4js.defs["EPSG:2818"] = "+proj=lcc +lat_1=49 +lat_2=45 +lat_0=44.25 +lon_0=-109.5 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/MT.MT
Proj4js.defs["EPSG:2901"] = "+proj=lcc +lat_1=49 +lat_2=45 +lat_0=44.25 +lon_0=-109.5 +x_0=599999.9999976 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/MT.MTIF
Proj4js.defs["EPSG:32005"] = "+proj=lcc +lat_1=41.85 +lat_2=42.81666666666667 +lat_0=41.33333333333334 +lon_0=-100 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NE-N
Proj4js.defs["EPSG:32006"] = "+proj=lcc +lat_1=40.28333333333333 +lat_2=41.71666666666667 +lat_0=39.66666666666666 +lon_0=-99.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NE-S
Proj4js.defs["EPSG:32104"] = "+proj=lcc +lat_1=43 +lat_2=40 +lat_0=39.83333333333334 +lon_0=-100 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NE83
Proj4js.defs["EPSG:26852"] = "+proj=lcc +lat_1=43 +lat_2=40 +lat_0=39.83333333333334 +lon_0=-100 +x_0=500000.00001016 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NE83F
Proj4js.defs["EPSG:2819"] = "+proj=lcc +lat_1=43 +lat_2=40 +lat_0=39.83333333333334 +lon_0=-100 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NB.NB
Proj4js.defs["EPSG:26860"] = "+proj=lcc +lat_1=43 +lat_2=40 +lat_0=39.83333333333334 +lon_0=-100 +x_0=500000.00001016 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NB.NBF
Proj4js.defs["EPSG:32008"] = "+proj=tmerc +lat_0=34.75 +lon_0=-116.6666666666667 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NV-C
Proj4js.defs["EPSG:32007"] = "+proj=tmerc +lat_0=34.75 +lon_0=-115.5833333333333 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NV-E
Proj4js.defs["EPSG:32009"] = "+proj=tmerc +lat_0=34.75 +lon_0=-118.5833333333333 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NV-W
Proj4js.defs["EPSG:32108"] = "+proj=tmerc +lat_0=34.75 +lon_0=-116.6666666666667 +k=0.9999 +x_0=500000 +y_0=6000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NV83-C
Proj4js.defs["EPSG:3422"] = "+proj=tmerc +lat_0=34.75 +lon_0=-116.6666666666667 +k=0.9999 +x_0=500000.00001016 +y_0=6000000 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NV83-CF
Proj4js.defs["EPSG:32107"] = "+proj=tmerc +lat_0=34.75 +lon_0=-115.5833333333333 +k=0.9999 +x_0=200000 +y_0=8000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NV83-E
Proj4js.defs["EPSG:3421"] = "+proj=tmerc +lat_0=34.75 +lon_0=-115.5833333333333 +k=0.9999 +x_0=200000.00001016 +y_0=8000000.000010163 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NV83-EF
Proj4js.defs["EPSG:32109"] = "+proj=tmerc +lat_0=34.75 +lon_0=-118.5833333333333 +k=0.9999 +x_0=800000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NV83-W
Proj4js.defs["EPSG:3423"] = "+proj=tmerc +lat_0=34.75 +lon_0=-118.5833333333333 +k=0.9999 +x_0=800000.0000101599 +y_0=3999999.99998984 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NV83-WF
Proj4js.defs["EPSG:2821"] = "+proj=tmerc +lat_0=34.75 +lon_0=-116.6666666666667 +k=0.9999 +x_0=500000 +y_0=6000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NV.NV-C
Proj4js.defs["EPSG:3430"] = "+proj=tmerc +lat_0=34.75 +lon_0=-116.6666666666667 +k=0.9999 +x_0=500000.00001016 +y_0=6000000 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NV.NV-CF
Proj4js.defs["EPSG:2820"] = "+proj=tmerc +lat_0=34.75 +lon_0=-115.5833333333333 +k=0.9999 +x_0=200000 +y_0=8000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NV.NV-E
Proj4js.defs["EPSG:3429"] = "+proj=tmerc +lat_0=34.75 +lon_0=-115.5833333333333 +k=0.9999 +x_0=200000.00001016 +y_0=8000000.000010163 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NV.NV-EF
Proj4js.defs["EPSG:2822"] = "+proj=tmerc +lat_0=34.75 +lon_0=-118.5833333333333 +k=0.9999 +x_0=800000 +y_0=4000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NV.NV-W
Proj4js.defs["EPSG:3431"] = "+proj=tmerc +lat_0=34.75 +lon_0=-118.5833333333333 +k=0.9999 +x_0=800000.0000101599 +y_0=3999999.99998984 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NV.NV-WF
Proj4js.defs["EPSG:32010"] = "+proj=tmerc +lat_0=42.5 +lon_0=-71.66666666666667 +k=0.999966667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NH
Proj4js.defs["EPSG:32110"] = "+proj=tmerc +lat_0=42.5 +lon_0=-71.66666666666667 +k=0.999966667 +x_0=300000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NH83
Proj4js.defs["EPSG:3437"] = "+proj=tmerc +lat_0=42.5 +lon_0=-71.66666666666667 +k=0.999966667 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NH83F
Proj4js.defs["EPSG:2823"] = "+proj=tmerc +lat_0=42.5 +lon_0=-71.66666666666667 +k=0.999966667 +x_0=300000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NE.NH
Proj4js.defs["EPSG:3445"] = "+proj=tmerc +lat_0=42.5 +lon_0=-71.66666666666667 +k=0.999966667 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NE.NHF
Proj4js.defs["EPSG:32011"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.66666666666667 +k=0.9999749999999999 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NJ
Proj4js.defs["EPSG:32111"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NJ83
Proj4js.defs["EPSG:3424"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NJ83F
Proj4js.defs["EPSG:2824"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NJ.NJ
Proj4js.defs["EPSG:3432"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NJ.NJF
Proj4js.defs["EPSG:32013"] = "+proj=tmerc +lat_0=31 +lon_0=-106.25 +k=0.9999 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NM-C
Proj4js.defs["EPSG:32012"] = "+proj=tmerc +lat_0=31 +lon_0=-104.3333333333333 +k=0.999909091 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NM-E
Proj4js.defs["EPSG:32014"] = "+proj=tmerc +lat_0=31 +lon_0=-107.8333333333333 +k=0.999916667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NM-W
Proj4js.defs["EPSG:32113"] = "+proj=tmerc +lat_0=31 +lon_0=-106.25 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NM83-C
Proj4js.defs["EPSG:2258"] = "+proj=tmerc +lat_0=31 +lon_0=-106.25 +k=0.9999 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NM83-CF
Proj4js.defs["EPSG:32112"] = "+proj=tmerc +lat_0=31 +lon_0=-104.3333333333333 +k=0.999909091 +x_0=165000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NM83-E
Proj4js.defs["EPSG:2257"] = "+proj=tmerc +lat_0=31 +lon_0=-104.3333333333333 +k=0.999909091 +x_0=165000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NM83-EF
Proj4js.defs["EPSG:32114"] = "+proj=tmerc +lat_0=31 +lon_0=-107.8333333333333 +k=0.999916667 +x_0=830000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NM83-W
Proj4js.defs["EPSG:2259"] = "+proj=tmerc +lat_0=31 +lon_0=-107.8333333333333 +k=0.999916667 +x_0=830000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NM83-WF
Proj4js.defs["EPSG:2826"] = "+proj=tmerc +lat_0=31 +lon_0=-106.25 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NM.NM-C
Proj4js.defs["EPSG:2903"] = "+proj=tmerc +lat_0=31 +lon_0=-106.25 +k=0.9999 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NM.NM-CF
Proj4js.defs["EPSG:2825"] = "+proj=tmerc +lat_0=31 +lon_0=-104.3333333333333 +k=0.999909091 +x_0=165000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NM.NM-E
Proj4js.defs["EPSG:2902"] = "+proj=tmerc +lat_0=31 +lon_0=-104.3333333333333 +k=0.999909091 +x_0=165000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NM.NM-EF
Proj4js.defs["EPSG:2827"] = "+proj=tmerc +lat_0=31 +lon_0=-107.8333333333333 +k=0.999916667 +x_0=830000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NM.NM-W
Proj4js.defs["EPSG:2904"] = "+proj=tmerc +lat_0=31 +lon_0=-107.8333333333333 +k=0.999916667 +x_0=830000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NM.NM-WF
Proj4js.defs["EPSG:32016"] = "+proj=tmerc +lat_0=40 +lon_0=-76.58333333333333 +k=0.9999375 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NY-C
Proj4js.defs["EPSG:32015"] = "+proj=tmerc +lat_0=40 +lon_0=-74.33333333333333 +k=0.999966667 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NY-E
Proj4js.defs["EPSG:32017"] = "+proj=tmerc +lat_0=40 +lon_0=-78.58333333333333 +k=0.9999375 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NY-W
Proj4js.defs["EPSG:32116"] = "+proj=tmerc +lat_0=40 +lon_0=-76.58333333333333 +k=0.9999375 +x_0=250000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NY83-C
Proj4js.defs["EPSG:2261"] = "+proj=tmerc +lat_0=40 +lon_0=-76.58333333333333 +k=0.9999375 +x_0=249999.9998983998 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NY83-CF
Proj4js.defs["EPSG:32115"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NY83-E
Proj4js.defs["EPSG:2260"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NY83-EF
Proj4js.defs["EPSG:32118"] = "+proj=lcc +lat_1=41.03333333333333 +lat_2=40.66666666666666 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NY83-LI
Proj4js.defs["EPSG:2263"] = "+proj=lcc +lat_1=41.03333333333333 +lat_2=40.66666666666666 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NY83-LIF
Proj4js.defs["EPSG:32117"] = "+proj=tmerc +lat_0=40 +lon_0=-78.58333333333333 +k=0.9999375 +x_0=350000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NY83-W
Proj4js.defs["EPSG:2262"] = "+proj=tmerc +lat_0=40 +lon_0=-78.58333333333333 +k=0.9999375 +x_0=350000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NY83-WF
Proj4js.defs["EPSG:2829"] = "+proj=tmerc +lat_0=40 +lon_0=-76.58333333333333 +k=0.9999375 +x_0=250000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NY.NY-C
Proj4js.defs["EPSG:2906"] = "+proj=tmerc +lat_0=40 +lon_0=-76.58333333333333 +k=0.9999375 +x_0=249999.9998983998 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NY.NY-CF
Proj4js.defs["EPSG:2828"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NY.NY-E
Proj4js.defs["EPSG:2905"] = "+proj=tmerc +lat_0=38.83333333333334 +lon_0=-74.5 +k=0.9999 +x_0=150000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NY.NY-EF
Proj4js.defs["EPSG:2831"] = "+proj=lcc +lat_1=41.03333333333333 +lat_2=40.66666666666666 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NY.NY-LI
Proj4js.defs["EPSG:2908"] = "+proj=lcc +lat_1=41.03333333333333 +lat_2=40.66666666666666 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000.0000000001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NY.NY-LIF
Proj4js.defs["EPSG:2830"] = "+proj=tmerc +lat_0=40 +lon_0=-78.58333333333333 +k=0.9999375 +x_0=350000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NY.NY-W
Proj4js.defs["EPSG:2907"] = "+proj=tmerc +lat_0=40 +lon_0=-78.58333333333333 +k=0.9999375 +x_0=350000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NY.NY-WF
Proj4js.defs["EPSG:32019"] = "+proj=lcc +lat_1=34.33333333333334 +lat_2=36.16666666666666 +lat_0=33.75 +lon_0=-79 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NC
Proj4js.defs["EPSG:32119"] = "+proj=lcc +lat_1=36.16666666666666 +lat_2=34.33333333333334 +lat_0=33.75 +lon_0=-79 +x_0=609601.22 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NC83
Proj4js.defs["EPSG:2264"] = "+proj=lcc +lat_1=36.16666666666666 +lat_2=34.33333333333334 +lat_0=33.75 +lon_0=-79 +x_0=609601.2192024384 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NC83F
Proj4js.defs["EPSG:3358"] = "+proj=lcc +lat_1=36.16666666666666 +lat_2=34.33333333333334 +lat_0=33.75 +lon_0=-79 +x_0=609601.22 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NC.NC
Proj4js.defs["EPSG:3404"] = "+proj=lcc +lat_1=36.16666666666666 +lat_2=34.33333333333334 +lat_0=33.75 +lon_0=-79 +x_0=609601.2192024384 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NC.NCF
Proj4js.defs["EPSG:32020"] = "+proj=lcc +lat_1=47.43333333333333 +lat_2=48.73333333333333 +lat_0=47 +lon_0=-100.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ND-N
Proj4js.defs["EPSG:32021"] = "+proj=lcc +lat_1=46.18333333333333 +lat_2=47.48333333333333 +lat_0=45.66666666666666 +lon_0=-100.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: ND-S
Proj4js.defs["EPSG:32120"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.43333333333333 +lat_0=47 +lon_0=-100.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ND83-N
Proj4js.defs["EPSG:32121"] = "+proj=lcc +lat_1=47.48333333333333 +lat_2=46.18333333333333 +lat_0=45.66666666666666 +lon_0=-100.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: ND83-S
Proj4js.defs["EPSG:2832"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.43333333333333 +lat_0=47 +lon_0=-100.5 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/ND.ND-N
Proj4js.defs["EPSG:2833"] = "+proj=lcc +lat_1=47.48333333333333 +lat_2=46.18333333333333 +lat_0=45.66666666666666 +lon_0=-100.5 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/ND.ND-S
Proj4js.defs["EPSG:2265"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.43333333333333 +lat_0=47 +lon_0=-100.5 +x_0=599999.9999976 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: NAD83.ND-Nft
Proj4js.defs["EPSG:2266"] = "+proj=lcc +lat_1=47.48333333333333 +lat_2=46.18333333333333 +lat_0=45.66666666666666 +lon_0=-100.5 +x_0=599999.9999976 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: NAD83.ND-Sft
Proj4js.defs["EPSG:2909"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.43333333333333 +lat_0=47 +lon_0=-100.5 +x_0=599999.9999976 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/ND.ND-NIF
Proj4js.defs["EPSG:2910"] = "+proj=lcc +lat_1=47.48333333333333 +lat_2=46.18333333333333 +lat_0=45.66666666666666 +lon_0=-100.5 +x_0=599999.9999976 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/ND.ND-SIF
Proj4js.defs["EPSG:32022"] = "+proj=lcc +lat_1=40.43333333333333 +lat_2=41.7 +lat_0=39.66666666666666 +lon_0=-82.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OH-N
Proj4js.defs["EPSG:32023"] = "+proj=lcc +lat_1=38.73333333333333 +lat_2=40.03333333333333 +lat_0=38 +lon_0=-82.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OH-S
Proj4js.defs["EPSG:32122"] = "+proj=lcc +lat_1=41.7 +lat_2=40.43333333333333 +lat_0=39.66666666666666 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: OH83-N
Proj4js.defs["EPSG:3734"] = "+proj=lcc +lat_1=41.7 +lat_2=40.43333333333333 +lat_0=39.66666666666666 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OH83-NF
Proj4js.defs["EPSG:32123"] = "+proj=lcc +lat_1=40.03333333333333 +lat_2=38.73333333333333 +lat_0=38 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: OH83-S
Proj4js.defs["EPSG:3735"] = "+proj=lcc +lat_1=40.03333333333333 +lat_2=38.73333333333333 +lat_0=38 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OH83-SF
Proj4js.defs["EPSG:2834"] = "+proj=lcc +lat_1=41.7 +lat_2=40.43333333333333 +lat_0=39.66666666666666 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/OH.OH-N
Proj4js.defs["EPSG:3753"] = "+proj=lcc +lat_1=41.7 +lat_2=40.43333333333333 +lat_0=39.66666666666666 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/OH.OH-NF
Proj4js.defs["EPSG:2835"] = "+proj=lcc +lat_1=40.03333333333333 +lat_2=38.73333333333333 +lat_0=38 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/OH.OH-S
Proj4js.defs["EPSG:3754"] = "+proj=lcc +lat_1=40.03333333333333 +lat_2=38.73333333333333 +lat_0=38 +lon_0=-82.5 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/OH.OH-SF
Proj4js.defs["EPSG:32024"] = "+proj=lcc +lat_1=35.56666666666667 +lat_2=36.76666666666667 +lat_0=35 +lon_0=-98 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OK-N
Proj4js.defs["EPSG:32025"] = "+proj=lcc +lat_1=33.93333333333333 +lat_2=35.23333333333333 +lat_0=33.33333333333334 +lon_0=-98 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OK-S
Proj4js.defs["EPSG:32124"] = "+proj=lcc +lat_1=36.76666666666667 +lat_2=35.56666666666667 +lat_0=35 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: OK83-N
Proj4js.defs["EPSG:2267"] = "+proj=lcc +lat_1=36.76666666666667 +lat_2=35.56666666666667 +lat_0=35 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OK83-NF
Proj4js.defs["EPSG:32125"] = "+proj=lcc +lat_1=35.23333333333333 +lat_2=33.93333333333333 +lat_0=33.33333333333334 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: OK83-S
Proj4js.defs["EPSG:2268"] = "+proj=lcc +lat_1=35.23333333333333 +lat_2=33.93333333333333 +lat_0=33.33333333333334 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OK83-SF
Proj4js.defs["EPSG:2836"] = "+proj=lcc +lat_1=36.76666666666667 +lat_2=35.56666666666667 +lat_0=35 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/OK.OK-N
Proj4js.defs["EPSG:2911"] = "+proj=lcc +lat_1=36.76666666666667 +lat_2=35.56666666666667 +lat_0=35 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/OK.OK-NF
Proj4js.defs["EPSG:2837"] = "+proj=lcc +lat_1=35.23333333333333 +lat_2=33.93333333333333 +lat_0=33.33333333333334 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/OK.OK-S
Proj4js.defs["EPSG:2912"] = "+proj=lcc +lat_1=35.23333333333333 +lat_2=33.93333333333333 +lat_0=33.33333333333334 +lon_0=-98 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/OK.OK-SF
Proj4js.defs["EPSG:32026"] = "+proj=lcc +lat_1=44.33333333333334 +lat_2=46 +lat_0=43.66666666666666 +lon_0=-120.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OR-N
Proj4js.defs["EPSG:32027"] = "+proj=lcc +lat_1=42.33333333333334 +lat_2=44 +lat_0=41.66666666666666 +lon_0=-120.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: OR-S
Proj4js.defs["EPSG:32126"] = "+proj=lcc +lat_1=46 +lat_2=44.33333333333334 +lat_0=43.66666666666666 +lon_0=-120.5 +x_0=2500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: OR83-N
Proj4js.defs["EPSG:2269"] = "+proj=lcc +lat_1=46 +lat_2=44.33333333333334 +lat_0=43.66666666666666 +lon_0=-120.5 +x_0=2500000.0001424 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: OR83-NIF
Proj4js.defs["EPSG:32127"] = "+proj=lcc +lat_1=44 +lat_2=42.33333333333334 +lat_0=41.66666666666666 +lon_0=-120.5 +x_0=1500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: OR83-S
Proj4js.defs["EPSG:2270"] = "+proj=lcc +lat_1=44 +lat_2=42.33333333333334 +lat_0=41.66666666666666 +lon_0=-120.5 +x_0=1500000.0001464 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: OR83-SIF
Proj4js.defs["EPSG:2838"] = "+proj=lcc +lat_1=46 +lat_2=44.33333333333334 +lat_0=43.66666666666666 +lon_0=-120.5 +x_0=2500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WO.OR-N
Proj4js.defs["EPSG:2913"] = "+proj=lcc +lat_1=46 +lat_2=44.33333333333334 +lat_0=43.66666666666666 +lon_0=-120.5 +x_0=2500000.0001424 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/WO.OR-NIF
Proj4js.defs["EPSG:2839"] = "+proj=lcc +lat_1=44 +lat_2=42.33333333333334 +lat_0=41.66666666666666 +lon_0=-120.5 +x_0=1500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WO.OR-S
Proj4js.defs["EPSG:2914"] = "+proj=lcc +lat_1=44 +lat_2=42.33333333333334 +lat_0=41.66666666666666 +lon_0=-120.5 +x_0=1500000.0001464 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/WO.OR-SIF
Proj4js.defs["EPSG:2992"] = "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=399999.9999984 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: OR-GIS83
Proj4js.defs["EPSG:2993"] = "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=400000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WO.OregonLambert
Proj4js.defs["EPSG:2994"] = "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=399999.9999984 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/WO.OregonLambert-F
Proj4js.defs["EPSG:2991"] = "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=400000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.OregonLambert
Proj4js.defs["EPSG:32028"] = "+proj=lcc +lat_1=40.88333333333333 +lat_2=41.95 +lat_0=40.16666666666666 +lon_0=-77.75 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: PA-N
Proj4js.defs["EPSG:32128"] = "+proj=lcc +lat_1=41.95 +lat_2=40.88333333333333 +lat_0=40.16666666666666 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: PA83-N
Proj4js.defs["EPSG:2271"] = "+proj=lcc +lat_1=41.95 +lat_2=40.88333333333333 +lat_0=40.16666666666666 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: PA83-NF
Proj4js.defs["EPSG:32129"] = "+proj=lcc +lat_1=40.96666666666667 +lat_2=39.93333333333333 +lat_0=39.33333333333334 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: PA83-S
Proj4js.defs["EPSG:2272"] = "+proj=lcc +lat_1=40.96666666666667 +lat_2=39.93333333333333 +lat_0=39.33333333333334 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: PA83-SF
Proj4js.defs["EPSG:3362"] = "+proj=lcc +lat_1=41.95 +lat_2=40.88333333333333 +lat_0=40.16666666666666 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/PA.PA-N
Proj4js.defs["EPSG:3363"] = "+proj=lcc +lat_1=41.95 +lat_2=40.88333333333333 +lat_0=40.16666666666666 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/PA.PA-NF
Proj4js.defs["EPSG:3364"] = "+proj=lcc +lat_1=40.96666666666667 +lat_2=39.93333333333333 +lat_0=39.33333333333334 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/PA.PA-S
Proj4js.defs["EPSG:3365"] = "+proj=lcc +lat_1=40.96666666666667 +lat_2=39.93333333333333 +lat_0=39.33333333333334 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/PA.PA-SF
Proj4js.defs["EPSG:32030"] = "+proj=tmerc +lat_0=41.08333333333334 +lon_0=-71.5 +k=0.9999938 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: RI
Proj4js.defs["EPSG:32130"] = "+proj=tmerc +lat_0=41.08333333333334 +lon_0=-71.5 +k=0.99999375 +x_0=100000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: RI83
Proj4js.defs["EPSG:3438"] = "+proj=tmerc +lat_0=41.08333333333334 +lon_0=-71.5 +k=0.99999375 +x_0=99999.99998983997 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: RI83F
Proj4js.defs["EPSG:2840"] = "+proj=tmerc +lat_0=41.08333333333334 +lon_0=-71.5 +k=0.99999375 +x_0=100000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NE.RI
Proj4js.defs["EPSG:3446"] = "+proj=tmerc +lat_0=41.08333333333334 +lon_0=-71.5 +k=0.99999375 +x_0=99999.99998983997 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/NE.RIF
Proj4js.defs["EPSG:32031"] = "+proj=lcc +lat_1=33.76666666666667 +lat_2=34.96666666666667 +lat_0=33 +lon_0=-81 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: SC-N
Proj4js.defs["EPSG:32033"] = "+proj=lcc +lat_1=32.33333333333334 +lat_2=33.66666666666666 +lat_0=31.83333333333333 +lon_0=-81 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: SC-S
Proj4js.defs["EPSG:32133"] = "+proj=lcc +lat_1=34.83333333333334 +lat_2=32.5 +lat_0=31.83333333333333 +lon_0=-81 +x_0=609600 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: SC83
Proj4js.defs["EPSG:2273"] = "+proj=lcc +lat_1=34.83333333333334 +lat_2=32.5 +lat_0=31.83333333333333 +lon_0=-81 +x_0=609600 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: SC83IF
Proj4js.defs["EPSG:3360"] = "+proj=lcc +lat_1=34.83333333333334 +lat_2=32.5 +lat_0=31.83333333333333 +lon_0=-81 +x_0=609600 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/SC.SC
Proj4js.defs["EPSG:3361"] = "+proj=lcc +lat_1=34.83333333333334 +lat_2=32.5 +lat_0=31.83333333333333 +lon_0=-81 +x_0=609600 +y_0=0 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/SC.SCIF
Proj4js.defs["EPSG:32034"] = "+proj=lcc +lat_1=44.41666666666666 +lat_2=45.68333333333333 +lat_0=43.83333333333334 +lon_0=-100 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: SD-N
Proj4js.defs["EPSG:32035"] = "+proj=lcc +lat_1=42.83333333333334 +lat_2=44.4 +lat_0=42.33333333333334 +lon_0=-100.3333333333333 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: SD-S
Proj4js.defs["EPSG:32134"] = "+proj=lcc +lat_1=45.68333333333333 +lat_2=44.41666666666666 +lat_0=43.83333333333334 +lon_0=-100 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: SD83-N
Proj4js.defs["EPSG:32135"] = "+proj=lcc +lat_1=44.4 +lat_2=42.83333333333334 +lat_0=42.33333333333334 +lon_0=-100.3333333333333 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: SD83-S
Proj4js.defs["EPSG:3455"] = "+proj=lcc +lat_1=44.4 +lat_2=42.83333333333334 +lat_0=42.33333333333334 +lon_0=-100.3333333333333 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: SD83-SF
Proj4js.defs["EPSG:2841"] = "+proj=lcc +lat_1=45.68333333333333 +lat_2=44.41666666666666 +lat_0=43.83333333333334 +lon_0=-100 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/SD.SD-N
Proj4js.defs["EPSG:3458"] = "+proj=lcc +lat_1=45.68333333333333 +lat_2=44.41666666666666 +lat_0=43.83333333333334 +lon_0=-100 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/SD.SD-NF
Proj4js.defs["EPSG:2842"] = "+proj=lcc +lat_1=44.4 +lat_2=42.83333333333334 +lat_0=42.33333333333334 +lon_0=-100.3333333333333 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/SD.SD-S
Proj4js.defs["EPSG:3459"] = "+proj=lcc +lat_1=44.4 +lat_2=42.83333333333334 +lat_0=42.33333333333334 +lon_0=-100.3333333333333 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/SD.SD-SF
Proj4js.defs["EPSG:2204"] = "+proj=lcc +lat_1=35.25 +lat_2=36.41666666666666 +lat_0=34.66666666666666 +lon_0=-86 +x_0=609601.2192024384 +y_0=30480.06096012192 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TN
Proj4js.defs["EPSG:32136"] = "+proj=lcc +lat_1=36.41666666666666 +lat_2=35.25 +lat_0=34.33333333333334 +lon_0=-86 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: TN83
Proj4js.defs["EPSG:2274"] = "+proj=lcc +lat_1=36.41666666666666 +lat_2=35.25 +lat_0=34.33333333333334 +lon_0=-86 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TN83F
Proj4js.defs["EPSG:2843"] = "+proj=lcc +lat_1=36.41666666666666 +lat_2=35.25 +lat_0=34.33333333333334 +lon_0=-86 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TN.TN
Proj4js.defs["EPSG:2915"] = "+proj=lcc +lat_1=36.41666666666666 +lat_2=35.25 +lat_0=34.33333333333334 +lon_0=-86 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/TN.TNF
Proj4js.defs["EPSG:32039"] = "+proj=lcc +lat_1=30.11666666666667 +lat_2=31.88333333333333 +lat_0=29.66666666666667 +lon_0=-100.3333333333333 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX-C
Proj4js.defs["EPSG:32037"] = "+proj=lcc +lat_1=34.65 +lat_2=36.18333333333333 +lat_0=34 +lon_0=-101.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX-N
Proj4js.defs["EPSG:32038"] = "+proj=lcc +lat_1=32.13333333333333 +lat_2=33.96666666666667 +lat_0=31.66666666666667 +lon_0=-97.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX-NC
Proj4js.defs["EPSG:32041"] = "+proj=lcc +lat_1=26.16666666666667 +lat_2=27.83333333333333 +lat_0=25.66666666666667 +lon_0=-98.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX-S
Proj4js.defs["EPSG:32040"] = "+proj=lcc +lat_1=28.38333333333333 +lat_2=30.28333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX-SC
Proj4js.defs["EPSG:32139"] = "+proj=lcc +lat_1=31.88333333333333 +lat_2=30.11666666666667 +lat_0=29.66666666666667 +lon_0=-100.3333333333333 +x_0=700000 +y_0=3000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: TX83-C
Proj4js.defs["EPSG:2277"] = "+proj=lcc +lat_1=31.88333333333333 +lat_2=30.11666666666667 +lat_0=29.66666666666667 +lon_0=-100.3333333333333 +x_0=699999.9998983998 +y_0=3000000 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX83-CF
Proj4js.defs["EPSG:32137"] = "+proj=lcc +lat_1=36.18333333333333 +lat_2=34.65 +lat_0=34 +lon_0=-101.5 +x_0=200000 +y_0=1000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: TX83-N
Proj4js.defs["EPSG:2275"] = "+proj=lcc +lat_1=36.18333333333333 +lat_2=34.65 +lat_0=34 +lon_0=-101.5 +x_0=200000.0001016002 +y_0=999999.9998983998 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX83-NF
Proj4js.defs["EPSG:32138"] = "+proj=lcc +lat_1=33.96666666666667 +lat_2=32.13333333333333 +lat_0=31.66666666666667 +lon_0=-98.5 +x_0=600000 +y_0=2000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: TX83-NC
Proj4js.defs["EPSG:2276"] = "+proj=lcc +lat_1=33.96666666666667 +lat_2=32.13333333333333 +lat_0=31.66666666666667 +lon_0=-98.5 +x_0=600000 +y_0=2000000.0001016 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX83-NCF
Proj4js.defs["EPSG:32141"] = "+proj=lcc +lat_1=27.83333333333333 +lat_2=26.16666666666667 +lat_0=25.66666666666667 +lon_0=-98.5 +x_0=300000 +y_0=5000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: TX83-S
Proj4js.defs["EPSG:2279"] = "+proj=lcc +lat_1=27.83333333333333 +lat_2=26.16666666666667 +lat_0=25.66666666666667 +lon_0=-98.5 +x_0=300000.0000000001 +y_0=5000000.0001016 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX83-SF
Proj4js.defs["EPSG:32140"] = "+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: TX83-SC
Proj4js.defs["EPSG:2278"] = "+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=3999999.9998984 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: TX83-SCF
Proj4js.defs["EPSG:2846"] = "+proj=lcc +lat_1=31.88333333333333 +lat_2=30.11666666666667 +lat_0=29.66666666666667 +lon_0=-100.3333333333333 +x_0=700000 +y_0=3000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TX.TX-C
Proj4js.defs["EPSG:2918"] = "+proj=lcc +lat_1=31.88333333333333 +lat_2=30.11666666666667 +lat_0=29.66666666666667 +lon_0=-100.3333333333333 +x_0=699999.9998983998 +y_0=3000000 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/TX.TX-CF
Proj4js.defs["EPSG:2844"] = "+proj=lcc +lat_1=36.18333333333333 +lat_2=34.65 +lat_0=34 +lon_0=-101.5 +x_0=200000 +y_0=1000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TX.TX-N
Proj4js.defs["EPSG:2845"] = "+proj=lcc +lat_1=33.96666666666667 +lat_2=32.13333333333333 +lat_0=31.66666666666667 +lon_0=-98.5 +x_0=600000 +y_0=2000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TX.TX-NC
Proj4js.defs["EPSG:2917"] = "+proj=lcc +lat_1=33.96666666666667 +lat_2=32.13333333333333 +lat_0=31.66666666666667 +lon_0=-98.5 +x_0=600000 +y_0=2000000.0001016 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/TX.TX-NCF
Proj4js.defs["EPSG:2916"] = "+proj=lcc +lat_1=36.18333333333333 +lat_2=34.65 +lat_0=34 +lon_0=-101.5 +x_0=200000.0001016002 +y_0=999999.9998983998 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/TX.TX-NF
Proj4js.defs["EPSG:2848"] = "+proj=lcc +lat_1=27.83333333333333 +lat_2=26.16666666666667 +lat_0=25.66666666666667 +lon_0=-98.5 +x_0=300000 +y_0=5000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TX.TX-S
Proj4js.defs["EPSG:2847"] = "+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TX.TX-SC
Proj4js.defs["EPSG:2919"] = "+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=3999999.9998984 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/TX.TX-SCF
Proj4js.defs["EPSG:2920"] = "+proj=lcc +lat_1=27.83333333333333 +lat_2=26.16666666666667 +lat_0=25.66666666666667 +lon_0=-98.5 +x_0=300000.0000000001 +y_0=5000000.0001016 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/TX.TX-SF
Proj4js.defs["EPSG:3080"] = "+proj=lcc +lat_1=27.41666666666667 +lat_2=34.91666666666666 +lat_0=31.16666666666667 +lon_0=-100 +x_0=914400 +y_0=914400 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048 +no_defs "; //Mentor: TX-DOT27
Proj4js.defs["EPSG:3082"] = "+proj=lcc +lat_1=27.5 +lat_2=35 +lat_0=18 +lon_0=-100 +x_0=1500000 +y_0=5000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.Texas/Lambert
Proj4js.defs["EPSG:3083"] = "+proj=aea +lat_1=27.5 +lat_2=35 +lat_0=18 +lon_0=-100 +x_0=1500000 +y_0=6000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.Texas/EqArea
Proj4js.defs["EPSG:3084"] = "+proj=lcc +lat_1=27.5 +lat_2=35 +lat_0=18 +lon_0=-100 +x_0=1500000 +y_0=5000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TX.Texas/Lambert
Proj4js.defs["EPSG:3085"] = "+proj=aea +lat_1=27.5 +lat_2=35 +lat_0=18 +lon_0=-100 +x_0=1500000 +y_0=6000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/TX.Texas/Albers
Proj4js.defs["EPSG:32043"] = "+proj=lcc +lat_1=39.01666666666667 +lat_2=40.65 +lat_0=38.33333333333334 +lon_0=-111.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: UT-C
Proj4js.defs["EPSG:32042"] = "+proj=lcc +lat_1=40.71666666666667 +lat_2=41.78333333333333 +lat_0=40.33333333333334 +lon_0=-111.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: UT-N
Proj4js.defs["EPSG:32044"] = "+proj=lcc +lat_1=37.21666666666667 +lat_2=38.35 +lat_0=36.66666666666666 +lon_0=-111.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: UT-S
Proj4js.defs["EPSG:32143"] = "+proj=lcc +lat_1=40.65 +lat_2=39.01666666666667 +lat_0=38.33333333333334 +lon_0=-111.5 +x_0=500000 +y_0=2000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UT83-C
Proj4js.defs["EPSG:3566"] = "+proj=lcc +lat_1=40.65 +lat_2=39.01666666666667 +lat_0=38.33333333333334 +lon_0=-111.5 +x_0=500000.00001016 +y_0=2000000.00001016 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: UT83-CF
Proj4js.defs["EPSG:2281"] = "+proj=lcc +lat_1=40.65 +lat_2=39.01666666666667 +lat_0=38.33333333333334 +lon_0=-111.5 +x_0=500000.0001504 +y_0=1999999.999992 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: UT83-CIF
Proj4js.defs["EPSG:32142"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.71666666666667 +lat_0=40.33333333333334 +lon_0=-111.5 +x_0=500000 +y_0=1000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UT83-N
Proj4js.defs["EPSG:3560"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.71666666666667 +lat_0=40.33333333333334 +lon_0=-111.5 +x_0=500000.00001016 +y_0=999999.9999898402 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: UT83-NF
Proj4js.defs["EPSG:2280"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.71666666666667 +lat_0=40.33333333333334 +lon_0=-111.5 +x_0=500000.0001504 +y_0=999999.9999960001 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: UT83-NIF
Proj4js.defs["EPSG:32144"] = "+proj=lcc +lat_1=38.35 +lat_2=37.21666666666667 +lat_0=36.66666666666666 +lon_0=-111.5 +x_0=500000 +y_0=3000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UT83-S
Proj4js.defs["EPSG:3567"] = "+proj=lcc +lat_1=38.35 +lat_2=37.21666666666667 +lat_0=36.66666666666666 +lon_0=-111.5 +x_0=500000.00001016 +y_0=3000000 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: UT83-SF
Proj4js.defs["EPSG:2282"] = "+proj=lcc +lat_1=38.35 +lat_2=37.21666666666667 +lat_0=36.66666666666666 +lon_0=-111.5 +x_0=500000.0001504 +y_0=2999999.999988 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048 +no_defs "; //Mentor: UT83-SIF
Proj4js.defs["EPSG:2850"] = "+proj=lcc +lat_1=40.65 +lat_2=39.01666666666667 +lat_0=38.33333333333334 +lon_0=-111.5 +x_0=500000 +y_0=2000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/UT.UT-C
Proj4js.defs["EPSG:3569"] = "+proj=lcc +lat_1=40.65 +lat_2=39.01666666666667 +lat_0=38.33333333333334 +lon_0=-111.5 +x_0=500000.00001016 +y_0=2000000.00001016 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/UT.UT-CF
Proj4js.defs["EPSG:2922"] = "+proj=lcc +lat_1=40.65 +lat_2=39.01666666666667 +lat_0=38.33333333333334 +lon_0=-111.5 +x_0=500000.0001504 +y_0=1999999.999992 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/UT.UT-CIF
Proj4js.defs["EPSG:2849"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.71666666666667 +lat_0=40.33333333333334 +lon_0=-111.5 +x_0=500000 +y_0=1000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/UT.UT-N
Proj4js.defs["EPSG:3568"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.71666666666667 +lat_0=40.33333333333334 +lon_0=-111.5 +x_0=500000.00001016 +y_0=999999.9999898402 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/UT.UT-NF
Proj4js.defs["EPSG:2921"] = "+proj=lcc +lat_1=41.78333333333333 +lat_2=40.71666666666667 +lat_0=40.33333333333334 +lon_0=-111.5 +x_0=500000.0001504 +y_0=999999.9999960001 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/UT.UT-NIF
Proj4js.defs["EPSG:2851"] = "+proj=lcc +lat_1=38.35 +lat_2=37.21666666666667 +lat_0=36.66666666666666 +lon_0=-111.5 +x_0=500000 +y_0=3000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/UT.UT-S
Proj4js.defs["EPSG:3570"] = "+proj=lcc +lat_1=38.35 +lat_2=37.21666666666667 +lat_0=36.66666666666666 +lon_0=-111.5 +x_0=500000.00001016 +y_0=3000000 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/UT.UT-SF
Proj4js.defs["EPSG:2923"] = "+proj=lcc +lat_1=38.35 +lat_2=37.21666666666667 +lat_0=36.66666666666666 +lon_0=-111.5 +x_0=500000.0001504 +y_0=2999999.999988 +ellps=GRS80 +to_meter=0.3048 +no_defs "; //Mentor: HARN/UT.UT-SIF
Proj4js.defs["EPSG:32045"] = "+proj=tmerc +lat_0=42.5 +lon_0=-72.5 +k=0.999964286 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: VT
Proj4js.defs["EPSG:32145"] = "+proj=tmerc +lat_0=42.5 +lon_0=-72.5 +k=0.999964286 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: VT83
Proj4js.defs["EPSG:2852"] = "+proj=tmerc +lat_0=42.5 +lon_0=-72.5 +k=0.999964286 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/NE.VT
Proj4js.defs["EPSG:32046"] = "+proj=lcc +lat_1=38.03333333333333 +lat_2=39.2 +lat_0=37.66666666666666 +lon_0=-78.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: VA-N
Proj4js.defs["EPSG:32047"] = "+proj=lcc +lat_1=36.76666666666667 +lat_2=37.96666666666667 +lat_0=36.33333333333334 +lon_0=-78.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: VA-S
Proj4js.defs["EPSG:32146"] = "+proj=lcc +lat_1=39.2 +lat_2=38.03333333333333 +lat_0=37.66666666666666 +lon_0=-78.5 +x_0=3500000 +y_0=2000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: VA83-N
Proj4js.defs["EPSG:2283"] = "+proj=lcc +lat_1=39.2 +lat_2=38.03333333333333 +lat_0=37.66666666666666 +lon_0=-78.5 +x_0=3500000.0001016 +y_0=2000000.0001016 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: VA83-NF
Proj4js.defs["EPSG:32147"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=36.76666666666667 +lat_0=36.33333333333334 +lon_0=-78.5 +x_0=3500000 +y_0=1000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: VA83-S
Proj4js.defs["EPSG:2284"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=36.76666666666667 +lat_0=36.33333333333334 +lon_0=-78.5 +x_0=3500000.0001016 +y_0=999999.9998983998 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: VA83-SF
Proj4js.defs["EPSG:2853"] = "+proj=lcc +lat_1=39.2 +lat_2=38.03333333333333 +lat_0=37.66666666666666 +lon_0=-78.5 +x_0=3500000 +y_0=2000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/VA.VA-N
Proj4js.defs["EPSG:2924"] = "+proj=lcc +lat_1=39.2 +lat_2=38.03333333333333 +lat_0=37.66666666666666 +lon_0=-78.5 +x_0=3500000.0001016 +y_0=2000000.0001016 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/VA.VA-NF
Proj4js.defs["EPSG:2854"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=36.76666666666667 +lat_0=36.33333333333334 +lon_0=-78.5 +x_0=3500000 +y_0=1000000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/VA.VA-S
Proj4js.defs["EPSG:2925"] = "+proj=lcc +lat_1=37.96666666666667 +lat_2=36.76666666666667 +lat_0=36.33333333333334 +lon_0=-78.5 +x_0=3500000.0001016 +y_0=999999.9998983998 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/VA.VA-SF
Proj4js.defs["EPSG:32048"] = "+proj=lcc +lat_1=47.5 +lat_2=48.73333333333333 +lat_0=47 +lon_0=-120.8333333333333 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WA-N
Proj4js.defs["EPSG:32049"] = "+proj=lcc +lat_1=45.83333333333334 +lat_2=47.33333333333334 +lat_0=45.33333333333334 +lon_0=-120.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WA-S
Proj4js.defs["EPSG:32148"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.5 +lat_0=47 +lon_0=-120.8333333333333 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WA83-N
Proj4js.defs["EPSG:2285"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.5 +lat_0=47 +lon_0=-120.8333333333333 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WA83-NF
Proj4js.defs["EPSG:32149"] = "+proj=lcc +lat_1=47.33333333333334 +lat_2=45.83333333333334 +lat_0=45.33333333333334 +lon_0=-120.5 +x_0=500000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WA83-S
Proj4js.defs["EPSG:2286"] = "+proj=lcc +lat_1=47.33333333333334 +lat_2=45.83333333333334 +lat_0=45.33333333333334 +lon_0=-120.5 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WA83-SF
Proj4js.defs["EPSG:2855"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.5 +lat_0=47 +lon_0=-120.8333333333333 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WO.WA-N
Proj4js.defs["EPSG:2926"] = "+proj=lcc +lat_1=48.73333333333333 +lat_2=47.5 +lat_0=47 +lon_0=-120.8333333333333 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WO.WA-NF
Proj4js.defs["EPSG:2856"] = "+proj=lcc +lat_1=47.33333333333334 +lat_2=45.83333333333334 +lat_0=45.33333333333334 +lon_0=-120.5 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WO.WA-S
Proj4js.defs["EPSG:2927"] = "+proj=lcc +lat_1=47.33333333333334 +lat_2=45.83333333333334 +lat_0=45.33333333333334 +lon_0=-120.5 +x_0=500000.0001016001 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WO.WA-SF
Proj4js.defs["EPSG:32050"] = "+proj=lcc +lat_1=39 +lat_2=40.25 +lat_0=38.5 +lon_0=-79.5 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WV-N
Proj4js.defs["EPSG:32051"] = "+proj=lcc +lat_1=37.48333333333333 +lat_2=38.88333333333333 +lat_0=37 +lon_0=-81 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WV-S
Proj4js.defs["EPSG:32150"] = "+proj=lcc +lat_1=40.25 +lat_2=39 +lat_0=38.5 +lon_0=-79.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WV83-N
Proj4js.defs["EPSG:26853"] = "+proj=lcc +lat_1=40.25 +lat_2=39 +lat_0=38.5 +lon_0=-79.5 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WV83-NF
Proj4js.defs["EPSG:32151"] = "+proj=lcc +lat_1=38.88333333333333 +lat_2=37.48333333333333 +lat_0=37 +lon_0=-81 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WV83-S
Proj4js.defs["EPSG:26854"] = "+proj=lcc +lat_1=38.88333333333333 +lat_2=37.48333333333333 +lat_0=37 +lon_0=-81 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WV83-SF
Proj4js.defs["EPSG:2857"] = "+proj=lcc +lat_1=40.25 +lat_2=39 +lat_0=38.5 +lon_0=-79.5 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WV.WV-N
Proj4js.defs["EPSG:26861"] = "+proj=lcc +lat_1=40.25 +lat_2=39 +lat_0=38.5 +lon_0=-79.5 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WV.WV-NF
Proj4js.defs["EPSG:2858"] = "+proj=lcc +lat_1=38.88333333333333 +lat_2=37.48333333333333 +lat_0=37 +lon_0=-81 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WV.WV-S
Proj4js.defs["EPSG:26862"] = "+proj=lcc +lat_1=38.88333333333333 +lat_2=37.48333333333333 +lat_0=37 +lon_0=-81 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WV.WV-SF
Proj4js.defs["EPSG:3069"] = "+proj=tmerc +lat_0=0 +lon_0=-90 +k=0.9996 +x_0=500000 +y_0=-4500000 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: WI27-TM
Proj4js.defs["EPSG:3070"] = "+proj=tmerc +lat_0=0 +lon_0=-90 +k=0.9996 +x_0=520000 +y_0=-4480000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WI83-TM
Proj4js.defs["EPSG:32053"] = "+proj=lcc +lat_1=44.25 +lat_2=45.5 +lat_0=43.83333333333334 +lon_0=-90 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WI-C
Proj4js.defs["EPSG:32052"] = "+proj=lcc +lat_1=45.56666666666667 +lat_2=46.76666666666667 +lat_0=45.16666666666666 +lon_0=-90 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WI-N
Proj4js.defs["EPSG:32054"] = "+proj=lcc +lat_1=42.73333333333333 +lat_2=44.06666666666667 +lat_0=42 +lon_0=-90 +x_0=609601.2192024384 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WI-S
Proj4js.defs["EPSG:32153"] = "+proj=lcc +lat_1=45.5 +lat_2=44.25 +lat_0=43.83333333333334 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WI83-C
Proj4js.defs["EPSG:2288"] = "+proj=lcc +lat_1=45.5 +lat_2=44.25 +lat_0=43.83333333333334 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WI83-CF
Proj4js.defs["EPSG:32152"] = "+proj=lcc +lat_1=46.76666666666667 +lat_2=45.56666666666667 +lat_0=45.16666666666666 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WI83-N
Proj4js.defs["EPSG:2287"] = "+proj=lcc +lat_1=46.76666666666667 +lat_2=45.56666666666667 +lat_0=45.16666666666666 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WI83-NF
Proj4js.defs["EPSG:32154"] = "+proj=lcc +lat_1=44.06666666666667 +lat_2=42.73333333333333 +lat_0=42 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WI83-S
Proj4js.defs["EPSG:2289"] = "+proj=lcc +lat_1=44.06666666666667 +lat_2=42.73333333333333 +lat_0=42 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WI83-SF
Proj4js.defs["EPSG:2860"] = "+proj=lcc +lat_1=45.5 +lat_2=44.25 +lat_0=43.83333333333334 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WI.WI-C
Proj4js.defs["EPSG:2929"] = "+proj=lcc +lat_1=45.5 +lat_2=44.25 +lat_0=43.83333333333334 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WI.WI-CF
Proj4js.defs["EPSG:2859"] = "+proj=lcc +lat_1=46.76666666666667 +lat_2=45.56666666666667 +lat_0=45.16666666666666 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WI.WI-N
Proj4js.defs["EPSG:2928"] = "+proj=lcc +lat_1=46.76666666666667 +lat_2=45.56666666666667 +lat_0=45.16666666666666 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WI.WI-NF
Proj4js.defs["EPSG:2861"] = "+proj=lcc +lat_1=44.06666666666667 +lat_2=42.73333333333333 +lat_0=42 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WI.WI-S
Proj4js.defs["EPSG:2930"] = "+proj=lcc +lat_1=44.06666666666667 +lat_2=42.73333333333333 +lat_0=42 +lon_0=-90 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WI.WI-SF
Proj4js.defs["EPSG:3071"] = "+proj=tmerc +lat_0=0 +lon_0=-90 +k=0.9996 +x_0=520000 +y_0=-4480000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WI.WisconsinTM
Proj4js.defs["EPSG:32055"] = "+proj=tmerc +lat_0=40.66666666666666 +lon_0=-105.1666666666667 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY-E
Proj4js.defs["EPSG:32056"] = "+proj=tmerc +lat_0=40.66666666666666 +lon_0=-107.3333333333333 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY-EC
Proj4js.defs["EPSG:32058"] = "+proj=tmerc +lat_0=40.66666666666666 +lon_0=-110.0833333333333 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY-W
Proj4js.defs["EPSG:32057"] = "+proj=tmerc +lat_0=40.66666666666666 +lon_0=-108.75 +k=0.999941177 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY-WC
Proj4js.defs["EPSG:32155"] = "+proj=tmerc +lat_0=40.5 +lon_0=-105.1666666666667 +k=0.9999375 +x_0=200000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WY83-E
Proj4js.defs["EPSG:3736"] = "+proj=tmerc +lat_0=40.5 +lon_0=-105.1666666666667 +k=0.9999375 +x_0=200000.00001016 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY83-EF
Proj4js.defs["EPSG:32156"] = "+proj=tmerc +lat_0=40.5 +lon_0=-107.3333333333333 +k=0.9999375 +x_0=400000 +y_0=100000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WY83-EC
Proj4js.defs["EPSG:3737"] = "+proj=tmerc +lat_0=40.5 +lon_0=-107.3333333333333 +k=0.9999375 +x_0=399999.99998984 +y_0=99999.99998983997 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY83-ECF
Proj4js.defs["EPSG:32158"] = "+proj=tmerc +lat_0=40.5 +lon_0=-110.0833333333333 +k=0.9999375 +x_0=800000 +y_0=100000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WY83-W
Proj4js.defs["EPSG:3739"] = "+proj=tmerc +lat_0=40.5 +lon_0=-110.0833333333333 +k=0.9999375 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY83-WF
Proj4js.defs["EPSG:32157"] = "+proj=tmerc +lat_0=40.5 +lon_0=-108.75 +k=0.9999375 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: WY83-WC
Proj4js.defs["EPSG:3738"] = "+proj=tmerc +lat_0=40.5 +lon_0=-108.75 +k=0.9999375 +x_0=600000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: WY83-WCF
Proj4js.defs["EPSG:2862"] = "+proj=tmerc +lat_0=40.5 +lon_0=-105.1666666666667 +k=0.9999375 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WY.WY-E
Proj4js.defs["EPSG:2863"] = "+proj=tmerc +lat_0=40.5 +lon_0=-107.3333333333333 +k=0.9999375 +x_0=400000 +y_0=100000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WY.WY-EC
Proj4js.defs["EPSG:3756"] = "+proj=tmerc +lat_0=40.5 +lon_0=-107.3333333333333 +k=0.9999375 +x_0=399999.99998984 +y_0=99999.99998983997 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WY.WY-ECF
Proj4js.defs["EPSG:3755"] = "+proj=tmerc +lat_0=40.5 +lon_0=-105.1666666666667 +k=0.9999375 +x_0=200000.00001016 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WY.WY-EF
Proj4js.defs["EPSG:2865"] = "+proj=tmerc +lat_0=40.5 +lon_0=-110.0833333333333 +k=0.9999375 +x_0=800000 +y_0=100000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WY.WY-W
Proj4js.defs["EPSG:2864"] = "+proj=tmerc +lat_0=40.5 +lon_0=-108.75 +k=0.9999375 +x_0=600000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/WY.WY-WC
Proj4js.defs["EPSG:3757"] = "+proj=tmerc +lat_0=40.5 +lon_0=-108.75 +k=0.9999375 +x_0=600000 +y_0=0 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WY.WY-WCF
Proj4js.defs["EPSG:3758"] = "+proj=tmerc +lat_0=40.5 +lon_0=-110.0833333333333 +k=0.9999375 +x_0=800000.0000101599 +y_0=99999.99998983997 +ellps=GRS80 +to_meter=0.3048006096012192 +no_defs "; //Mentor: HARN/WY.WY-WF
Proj4js.defs["EPSG:32161"] = "+proj=lcc +lat_1=18.43333333333333 +lat_2=18.03333333333333 +lat_0=17.83333333333333 +lon_0=-66.43333333333334 +x_0=200000 +y_0=200000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: PR83
Proj4js.defs["EPSG:2866"] = "+proj=lcc +lat_1=18.43333333333333 +lat_2=18.03333333333333 +lat_0=17.83333333333333 +lon_0=-66.43333333333334 +x_0=200000 +y_0=200000 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/PV.PRHP
Proj4js.defs["EPSG:2195"] = "+proj=utm +zone=2 +south +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/02.UTM-2S
Proj4js.defs["EPSG:4169"] = "+proj=longlat +ellps=clrk66 +towgs84=-115,118,426,0,0,0,0 +no_defs "; //Mentor: Samoa1962.LL
Proj4js.defs["EPSG:4675"] = "+proj=longlat +ellps=clrk66 +towgs84=-100,-248,259,0,0,0,0 +no_defs "; //Mentor: GUAM63.LL
Proj4js.defs["EPSG:4725"] = "+proj=longlat +ellps=intl +towgs84=189,-79,-202,0,0,0,0 +no_defs "; //Mentor: JHNSTN.LL
Proj4js.defs["EPSG:4727"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: MIDWAY.LL
Proj4js.defs["EPSG:4139"] = "+proj=longlat +ellps=clrk66 +towgs84=11,72,-101,0,0,0,0 +no_defs "; //Mentor: PRVI.LL
Proj4js.defs["EPSG:3991"] = "+proj=lcc +lat_1=18.43333333333333 +lat_2=18.03333333333333 +lat_0=17.83333333333333 +lon_0=-66.43333333333334 +x_0=152400.3048006096 +y_0=0 +ellps=clrk66 +towgs84=11,72,-101,0,0,0,0 +to_meter=0.3048006096012192 +no_defs "; //Mentor: PRVI.PR-1
Proj4js.defs["EPSG:3992"] = "+proj=lcc +lat_1=18.43333333333333 +lat_2=18.03333333333333 +lat_0=17.83333333333333 +lon_0=-66.43333333333334 +x_0=152400.3048006096 +y_0=30480.06096012192 +ellps=clrk66 +towgs84=11,72,-101,0,0,0,0 +to_meter=0.3048006096012192 +no_defs "; //Mentor: PRVI.PR-2
Proj4js.defs["EPSG:3920"] = "+proj=utm +zone=20 +ellps=clrk66 +towgs84=11,72,-101,0,0,0,0 +units=m +no_defs "; //Mentor: PRVI.UTM-20N
Proj4js.defs["EPSG:4735"] = "+proj=longlat +ellps=intl +towgs84=647,1777,-1124,0,0,0,0 +no_defs "; //Mentor: Kusaie51.LL
Proj4js.defs["EPSG:4733"] = "+proj=longlat +ellps=intl +towgs84=276,-57,149,0,0,0,0 +no_defs "; //Mentor: WakeIs1952.LL
Proj4js.defs["EPSG:4707"] = "+proj=longlat +ellps=intl +towgs84=114,-116,-333,0,0,0,0 +no_defs "; //Mentor: Tern61.LL
Proj4js.defs["EPSG:4732"] = "+proj=longlat +a=6378270 +b=6356794.343434343 +towgs84=102,52,-38,0,0,0,0 +no_defs "; //Mentor: Marshalls60.LL
Proj4js.defs["EPSG:3102"] = "+proj=lcc +lat_1=-14.26666666666667 +lat_0=-14.26666666666667 +lon_0=-170 +k_0=1 +x_0=152400.3048006096 +y_0=95169.31165862332 +ellps=clrk66 +towgs84=-115,118,426,0,0,0,0 +to_meter=0.3048006096012192 +no_defs "; //Mentor: Samoa1962.Samoa/Lambert
Proj4js.defs["EPSG:4309"] = "+proj=longlat +ellps=intl +towgs84=-155,171,37,0,0,0,0 +no_defs "; //Mentor: Yacare/E.LL
Proj4js.defs["EPSG:28406"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=6500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-6
Proj4js.defs["EPSG:28407"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=7500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-7
Proj4js.defs["EPSG:28408"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=8500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-8
Proj4js.defs["EPSG:28409"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=9500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-9
Proj4js.defs["EPSG:28410"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=10500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-10
Proj4js.defs["EPSG:28411"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=11500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-11
Proj4js.defs["EPSG:28412"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=12500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-12
Proj4js.defs["EPSG:28424"] = "+proj=tmerc +lat_0=0 +lon_0=141 +k=1 +x_0=24500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-24
Proj4js.defs["EPSG:28425"] = "+proj=tmerc +lat_0=0 +lon_0=147 +k=1 +x_0=25500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-25
Proj4js.defs["EPSG:28426"] = "+proj=tmerc +lat_0=0 +lon_0=153 +k=1 +x_0=26500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-26
Proj4js.defs["EPSG:28427"] = "+proj=tmerc +lat_0=0 +lon_0=159 +k=1 +x_0=27500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-27
Proj4js.defs["EPSG:28428"] = "+proj=tmerc +lat_0=0 +lon_0=165 +k=1 +x_0=28500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-28
Proj4js.defs["EPSG:28429"] = "+proj=tmerc +lat_0=0 +lon_0=171 +k=1 +x_0=29500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-29
Proj4js.defs["EPSG:28430"] = "+proj=tmerc +lat_0=0 +lon_0=177 +k=1 +x_0=30500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-30
Proj4js.defs["EPSG:28431"] = "+proj=tmerc +lat_0=0 +lon_0=-177 +k=1 +x_0=31500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-31
Proj4js.defs["EPSG:28432"] = "+proj=tmerc +lat_0=0 +lon_0=-171 +k=1 +x_0=32500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-32
Proj4js.defs["EPSG:3388"] = "+proj=merc +lon_0=51 +k=1 +x_0=0 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CaspianSea
Proj4js.defs["EPSG:2935"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=41.53333333333333 +k=1 +x_0=1300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A1
Proj4js.defs["EPSG:2936"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=44.53333333333333 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A2
Proj4js.defs["EPSG:2937"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=47.53333333333333 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A3
Proj4js.defs["EPSG:2938"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=50.53333333333333 +k=1 +x_0=4300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-A4
Proj4js.defs["EPSG:2939"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=50.76666666666667 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-K2
Proj4js.defs["EPSG:2940"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=53.76666666666667 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-K3
Proj4js.defs["EPSG:2941"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=56.76666666666667 +k=1 +x_0=4300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.CS63-K4
Proj4js.defs["EPSG:2508"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-105E
Proj4js.defs["EPSG:2509"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-111E
Proj4js.defs["EPSG:2510"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-117E
Proj4js.defs["EPSG:2511"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-123E
Proj4js.defs["EPSG:2512"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-129E
Proj4js.defs["EPSG:2513"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-135E
Proj4js.defs["EPSG:2514"] = "+proj=tmerc +lat_0=0 +lon_0=141 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-141E
Proj4js.defs["EPSG:2515"] = "+proj=tmerc +lat_0=0 +lon_0=147 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-147E
Proj4js.defs["EPSG:2516"] = "+proj=tmerc +lat_0=0 +lon_0=153 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-153E
Proj4js.defs["EPSG:2517"] = "+proj=tmerc +lat_0=0 +lon_0=159 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-159E
Proj4js.defs["EPSG:2518"] = "+proj=tmerc +lat_0=0 +lon_0=165 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-165E
Proj4js.defs["EPSG:2519"] = "+proj=tmerc +lat_0=0 +lon_0=171 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-171E
Proj4js.defs["EPSG:2522"] = "+proj=tmerc +lat_0=0 +lon_0=-171 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-171W
Proj4js.defs["EPSG:2520"] = "+proj=tmerc +lat_0=0 +lon_0=177 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-177E
Proj4js.defs["EPSG:2521"] = "+proj=tmerc +lat_0=0 +lon_0=-177 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-177W
Proj4js.defs["EPSG:2494"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-21E
Proj4js.defs["EPSG:2495"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-27E
Proj4js.defs["EPSG:2496"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-33E
Proj4js.defs["EPSG:2497"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-39E
Proj4js.defs["EPSG:2498"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-45E
Proj4js.defs["EPSG:2499"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-51E
Proj4js.defs["EPSG:2500"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-57E
Proj4js.defs["EPSG:2501"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-63E
Proj4js.defs["EPSG:2502"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-69E
Proj4js.defs["EPSG:2503"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-75E
Proj4js.defs["EPSG:2504"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-81E
Proj4js.defs["EPSG:2505"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-87E
Proj4js.defs["EPSG:2506"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-93E
Proj4js.defs["EPSG:2507"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK/CM-99E
Proj4js.defs["EPSG:2610"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-102E
Proj4js.defs["EPSG:2611"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-105E
Proj4js.defs["EPSG:2612"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-108E
Proj4js.defs["EPSG:2613"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-111E
Proj4js.defs["EPSG:2614"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-114E
Proj4js.defs["EPSG:2615"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-117E
Proj4js.defs["EPSG:2616"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-120E
Proj4js.defs["EPSG:2617"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-123E
Proj4js.defs["EPSG:2618"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-126E
Proj4js.defs["EPSG:2619"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-129E
Proj4js.defs["EPSG:2620"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-132E
Proj4js.defs["EPSG:2621"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-135E
Proj4js.defs["EPSG:2622"] = "+proj=tmerc +lat_0=0 +lon_0=138 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-138E
Proj4js.defs["EPSG:2623"] = "+proj=tmerc +lat_0=0 +lon_0=141 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-141E
Proj4js.defs["EPSG:2624"] = "+proj=tmerc +lat_0=0 +lon_0=144 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-144E
Proj4js.defs["EPSG:2625"] = "+proj=tmerc +lat_0=0 +lon_0=147 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-147E
Proj4js.defs["EPSG:2626"] = "+proj=tmerc +lat_0=0 +lon_0=150 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-150E
Proj4js.defs["EPSG:2627"] = "+proj=tmerc +lat_0=0 +lon_0=153 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-153E
Proj4js.defs["EPSG:2628"] = "+proj=tmerc +lat_0=0 +lon_0=156 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-156E
Proj4js.defs["EPSG:2629"] = "+proj=tmerc +lat_0=0 +lon_0=159 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-159E
Proj4js.defs["EPSG:2630"] = "+proj=tmerc +lat_0=0 +lon_0=162 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-162E
Proj4js.defs["EPSG:2631"] = "+proj=tmerc +lat_0=0 +lon_0=165 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-165E
Proj4js.defs["EPSG:2632"] = "+proj=tmerc +lat_0=0 +lon_0=168 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-168E
Proj4js.defs["EPSG:2640"] = "+proj=tmerc +lat_0=0 +lon_0=-168 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-168W
Proj4js.defs["EPSG:2633"] = "+proj=tmerc +lat_0=0 +lon_0=171 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-171E
Proj4js.defs["EPSG:2639"] = "+proj=tmerc +lat_0=0 +lon_0=-171 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-171W
Proj4js.defs["EPSG:2634"] = "+proj=tmerc +lat_0=0 +lon_0=174 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-174E
Proj4js.defs["EPSG:2638"] = "+proj=tmerc +lat_0=0 +lon_0=-174 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-174W
Proj4js.defs["EPSG:2635"] = "+proj=tmerc +lat_0=0 +lon_0=177 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-177E
Proj4js.defs["EPSG:2637"] = "+proj=tmerc +lat_0=0 +lon_0=-177 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-177W
Proj4js.defs["EPSG:2636"] = "+proj=tmerc +lat_0=0 +lon_0=180 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-180E
Proj4js.defs["EPSG:2582"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-21E
Proj4js.defs["EPSG:2583"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-24E
Proj4js.defs["EPSG:2584"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-27E
Proj4js.defs["EPSG:2585"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-30E
Proj4js.defs["EPSG:2586"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-33E
Proj4js.defs["EPSG:2587"] = "+proj=tmerc +lat_0=0 +lon_0=36 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-36E
Proj4js.defs["EPSG:2588"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-39E
Proj4js.defs["EPSG:2589"] = "+proj=tmerc +lat_0=0 +lon_0=42 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-42E
Proj4js.defs["EPSG:2590"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-45E
Proj4js.defs["EPSG:2591"] = "+proj=tmerc +lat_0=0 +lon_0=48 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-48E
Proj4js.defs["EPSG:2592"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-51E
Proj4js.defs["EPSG:2593"] = "+proj=tmerc +lat_0=0 +lon_0=54 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-54E
Proj4js.defs["EPSG:2594"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-57E
Proj4js.defs["EPSG:2595"] = "+proj=tmerc +lat_0=0 +lon_0=60 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-60E
Proj4js.defs["EPSG:2596"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-63E
Proj4js.defs["EPSG:2597"] = "+proj=tmerc +lat_0=0 +lon_0=66 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-66E
Proj4js.defs["EPSG:2598"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-69E
Proj4js.defs["EPSG:2599"] = "+proj=tmerc +lat_0=0 +lon_0=72 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-72E
Proj4js.defs["EPSG:2601"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-75E
Proj4js.defs["EPSG:2602"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-78E
Proj4js.defs["EPSG:2603"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-81E
Proj4js.defs["EPSG:2604"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-84E
Proj4js.defs["EPSG:2605"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-87E
Proj4js.defs["EPSG:2606"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-90E
Proj4js.defs["EPSG:2607"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-93E
Proj4js.defs["EPSG:2608"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-96E
Proj4js.defs["EPSG:2609"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-99E
Proj4js.defs["EPSG:2526"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=10500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-10
Proj4js.defs["EPSG:2527"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=11500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-11
Proj4js.defs["EPSG:2528"] = "+proj=tmerc +lat_0=0 +lon_0=36 +k=1 +x_0=12500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-12
Proj4js.defs["EPSG:2529"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=13500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-13
Proj4js.defs["EPSG:2530"] = "+proj=tmerc +lat_0=0 +lon_0=42 +k=1 +x_0=14500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-14
Proj4js.defs["EPSG:2531"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=15500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-15
Proj4js.defs["EPSG:2532"] = "+proj=tmerc +lat_0=0 +lon_0=48 +k=1 +x_0=16500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-16
Proj4js.defs["EPSG:2533"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=17500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-17
Proj4js.defs["EPSG:2534"] = "+proj=tmerc +lat_0=0 +lon_0=54 +k=1 +x_0=18500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-18
Proj4js.defs["EPSG:2535"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=19500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-19
Proj4js.defs["EPSG:2536"] = "+proj=tmerc +lat_0=0 +lon_0=60 +k=1 +x_0=20500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-20
Proj4js.defs["EPSG:2537"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=21500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-21
Proj4js.defs["EPSG:2538"] = "+proj=tmerc +lat_0=0 +lon_0=66 +k=1 +x_0=22500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-22
Proj4js.defs["EPSG:2539"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=23500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-23
Proj4js.defs["EPSG:2540"] = "+proj=tmerc +lat_0=0 +lon_0=72 +k=1 +x_0=24500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-24
Proj4js.defs["EPSG:2541"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=25500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-25
Proj4js.defs["EPSG:2542"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=26500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-26
Proj4js.defs["EPSG:2543"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=27500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-27
Proj4js.defs["EPSG:2544"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=28500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-28
Proj4js.defs["EPSG:2545"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=29500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-29
Proj4js.defs["EPSG:2546"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=30500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-30
Proj4js.defs["EPSG:2547"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=31500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-31
Proj4js.defs["EPSG:2548"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=32500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-32
Proj4js.defs["EPSG:2549"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=33500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-33
Proj4js.defs["EPSG:2551"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=34500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-34
Proj4js.defs["EPSG:2552"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=35500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-35
Proj4js.defs["EPSG:2553"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=36500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-36
Proj4js.defs["EPSG:2554"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=37500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-37
Proj4js.defs["EPSG:2555"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=38500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-38
Proj4js.defs["EPSG:2556"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=39500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-39
Proj4js.defs["EPSG:2557"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=40500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-40
Proj4js.defs["EPSG:2558"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=41500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-41
Proj4js.defs["EPSG:2559"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=42500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-42
Proj4js.defs["EPSG:2560"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=43500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-43
Proj4js.defs["EPSG:2561"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=44500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-44
Proj4js.defs["EPSG:2562"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=45500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-45
Proj4js.defs["EPSG:2563"] = "+proj=tmerc +lat_0=0 +lon_0=138 +k=1 +x_0=46500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-46
Proj4js.defs["EPSG:2564"] = "+proj=tmerc +lat_0=0 +lon_0=141 +k=1 +x_0=47500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-47
Proj4js.defs["EPSG:2565"] = "+proj=tmerc +lat_0=0 +lon_0=144 +k=1 +x_0=48500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-48
Proj4js.defs["EPSG:2566"] = "+proj=tmerc +lat_0=0 +lon_0=147 +k=1 +x_0=49500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-49
Proj4js.defs["EPSG:2567"] = "+proj=tmerc +lat_0=0 +lon_0=150 +k=1 +x_0=50500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-50
Proj4js.defs["EPSG:2568"] = "+proj=tmerc +lat_0=0 +lon_0=153 +k=1 +x_0=51500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-51
Proj4js.defs["EPSG:2569"] = "+proj=tmerc +lat_0=0 +lon_0=156 +k=1 +x_0=52500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-52
Proj4js.defs["EPSG:2570"] = "+proj=tmerc +lat_0=0 +lon_0=159 +k=1 +x_0=53500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-53
Proj4js.defs["EPSG:2571"] = "+proj=tmerc +lat_0=0 +lon_0=162 +k=1 +x_0=54500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-54
Proj4js.defs["EPSG:2572"] = "+proj=tmerc +lat_0=0 +lon_0=165 +k=1 +x_0=55500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-55
Proj4js.defs["EPSG:2573"] = "+proj=tmerc +lat_0=0 +lon_0=168 +k=1 +x_0=56500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-56
Proj4js.defs["EPSG:2574"] = "+proj=tmerc +lat_0=0 +lon_0=171 +k=1 +x_0=57500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-57
Proj4js.defs["EPSG:2575"] = "+proj=tmerc +lat_0=0 +lon_0=174 +k=1 +x_0=58500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-58
Proj4js.defs["EPSG:2576"] = "+proj=tmerc +lat_0=0 +lon_0=177 +k=1 +x_0=59500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-59
Proj4js.defs["EPSG:3389"] = "+proj=tmerc +lat_0=0 +lon_0=180 +k=1 +x_0=60500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-60/1
Proj4js.defs["EPSG:2578"] = "+proj=tmerc +lat_0=0 +lon_0=-177 +k=1 +x_0=61500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-61
Proj4js.defs["EPSG:2579"] = "+proj=tmerc +lat_0=0 +lon_0=-174 +k=1 +x_0=62500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-62
Proj4js.defs["EPSG:2580"] = "+proj=tmerc +lat_0=0 +lon_0=-171 +k=1 +x_0=63500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-63
Proj4js.defs["EPSG:2581"] = "+proj=tmerc +lat_0=0 +lon_0=-168 +k=1 +x_0=64500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-64
Proj4js.defs["EPSG:2523"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=7500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-7
Proj4js.defs["EPSG:2524"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=8500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-8
Proj4js.defs["EPSG:2525"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=9500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-9
Proj4js.defs["EPSG:4284"] = "+proj=longlat +ellps=krass +no_defs "; //Mentor: Pulkovo42.LL
Proj4js.defs["EPSG:4179"] = "+proj=longlat +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +no_defs "; //Mentor: Pulkovo42/58a.LL
Proj4js.defs["EPSG:4178"] = "+proj=longlat +ellps=krass +towgs84=24,-123,-94,0.02,-0.25,-0.13,1.1 +no_defs "; //Mentor: Pulkovo42/83a.LL
Proj4js.defs["EPSG:2726"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-102E
Proj4js.defs["EPSG:2727"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-105E
Proj4js.defs["EPSG:2728"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-108E
Proj4js.defs["EPSG:2729"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-111E
Proj4js.defs["EPSG:2730"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-114E
Proj4js.defs["EPSG:2731"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-117E
Proj4js.defs["EPSG:2732"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-120E
Proj4js.defs["EPSG:2733"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-123E
Proj4js.defs["EPSG:2734"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-126E
Proj4js.defs["EPSG:2735"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-129E
Proj4js.defs["EPSG:2738"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-132E
Proj4js.defs["EPSG:2739"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-135E
Proj4js.defs["EPSG:2740"] = "+proj=tmerc +lat_0=0 +lon_0=138 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-138E
Proj4js.defs["EPSG:2741"] = "+proj=tmerc +lat_0=0 +lon_0=141 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-141E
Proj4js.defs["EPSG:2742"] = "+proj=tmerc +lat_0=0 +lon_0=144 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-144E
Proj4js.defs["EPSG:2743"] = "+proj=tmerc +lat_0=0 +lon_0=147 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-147E
Proj4js.defs["EPSG:2744"] = "+proj=tmerc +lat_0=0 +lon_0=150 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-150E
Proj4js.defs["EPSG:2745"] = "+proj=tmerc +lat_0=0 +lon_0=153 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-153E
Proj4js.defs["EPSG:2746"] = "+proj=tmerc +lat_0=0 +lon_0=156 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-156E
Proj4js.defs["EPSG:2747"] = "+proj=tmerc +lat_0=0 +lon_0=159 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-159E
Proj4js.defs["EPSG:2748"] = "+proj=tmerc +lat_0=0 +lon_0=162 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-162E
Proj4js.defs["EPSG:2749"] = "+proj=tmerc +lat_0=0 +lon_0=165 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-165E
Proj4js.defs["EPSG:2750"] = "+proj=tmerc +lat_0=0 +lon_0=168 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-168E
Proj4js.defs["EPSG:2758"] = "+proj=tmerc +lat_0=0 +lon_0=-168 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-168W
Proj4js.defs["EPSG:2751"] = "+proj=tmerc +lat_0=0 +lon_0=171 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-171E
Proj4js.defs["EPSG:2757"] = "+proj=tmerc +lat_0=0 +lon_0=-171 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-171W
Proj4js.defs["EPSG:2752"] = "+proj=tmerc +lat_0=0 +lon_0=174 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-174E
Proj4js.defs["EPSG:2756"] = "+proj=tmerc +lat_0=0 +lon_0=-174 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-174W
Proj4js.defs["EPSG:2753"] = "+proj=tmerc +lat_0=0 +lon_0=177 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-177E
Proj4js.defs["EPSG:2755"] = "+proj=tmerc +lat_0=0 +lon_0=-177 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-177W
Proj4js.defs["EPSG:2754"] = "+proj=tmerc +lat_0=0 +lon_0=180 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-180E
Proj4js.defs["EPSG:2699"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-21E
Proj4js.defs["EPSG:2700"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-24E
Proj4js.defs["EPSG:2701"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-27E
Proj4js.defs["EPSG:2702"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-30E
Proj4js.defs["EPSG:2703"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-33E
Proj4js.defs["EPSG:2704"] = "+proj=tmerc +lat_0=0 +lon_0=36 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-36E
Proj4js.defs["EPSG:2705"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-39E
Proj4js.defs["EPSG:2706"] = "+proj=tmerc +lat_0=0 +lon_0=42 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-42E
Proj4js.defs["EPSG:2707"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-45E
Proj4js.defs["EPSG:2708"] = "+proj=tmerc +lat_0=0 +lon_0=48 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-48E
Proj4js.defs["EPSG:2709"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-51E
Proj4js.defs["EPSG:2710"] = "+proj=tmerc +lat_0=0 +lon_0=54 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-54E
Proj4js.defs["EPSG:2711"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-57E
Proj4js.defs["EPSG:2712"] = "+proj=tmerc +lat_0=0 +lon_0=60 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-60E
Proj4js.defs["EPSG:2713"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-63E
Proj4js.defs["EPSG:2714"] = "+proj=tmerc +lat_0=0 +lon_0=66 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-66E
Proj4js.defs["EPSG:2715"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-69E
Proj4js.defs["EPSG:2716"] = "+proj=tmerc +lat_0=0 +lon_0=72 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-72E
Proj4js.defs["EPSG:2717"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-75E
Proj4js.defs["EPSG:2718"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-78E
Proj4js.defs["EPSG:2719"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-81E
Proj4js.defs["EPSG:2720"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-84E
Proj4js.defs["EPSG:2721"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-87E
Proj4js.defs["EPSG:2722"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-90E
Proj4js.defs["EPSG:2723"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-93E
Proj4js.defs["EPSG:2724"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-96E
Proj4js.defs["EPSG:2725"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d/CM-99E
Proj4js.defs["EPSG:2644"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=10500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-10
Proj4js.defs["EPSG:2645"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=11500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-11
Proj4js.defs["EPSG:2646"] = "+proj=tmerc +lat_0=0 +lon_0=36 +k=1 +x_0=12500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-12
Proj4js.defs["EPSG:2647"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=13500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-13
Proj4js.defs["EPSG:2648"] = "+proj=tmerc +lat_0=0 +lon_0=42 +k=1 +x_0=14500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-14
Proj4js.defs["EPSG:2649"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=15500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-15
Proj4js.defs["EPSG:2650"] = "+proj=tmerc +lat_0=0 +lon_0=48 +k=1 +x_0=16500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-16
Proj4js.defs["EPSG:2651"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=17500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-17
Proj4js.defs["EPSG:2652"] = "+proj=tmerc +lat_0=0 +lon_0=54 +k=1 +x_0=18500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-18
Proj4js.defs["EPSG:2653"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=19500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-19
Proj4js.defs["EPSG:2654"] = "+proj=tmerc +lat_0=0 +lon_0=60 +k=1 +x_0=20500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-20
Proj4js.defs["EPSG:2655"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=21500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-21
Proj4js.defs["EPSG:2656"] = "+proj=tmerc +lat_0=0 +lon_0=66 +k=1 +x_0=22500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-22
Proj4js.defs["EPSG:2657"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=23500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-23
Proj4js.defs["EPSG:2658"] = "+proj=tmerc +lat_0=0 +lon_0=72 +k=1 +x_0=24500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-24
Proj4js.defs["EPSG:2659"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=25500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-25
Proj4js.defs["EPSG:2660"] = "+proj=tmerc +lat_0=0 +lon_0=78 +k=1 +x_0=26500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-26
Proj4js.defs["EPSG:2661"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=27500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-27
Proj4js.defs["EPSG:2662"] = "+proj=tmerc +lat_0=0 +lon_0=84 +k=1 +x_0=28500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-28
Proj4js.defs["EPSG:2663"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=29500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-29
Proj4js.defs["EPSG:2664"] = "+proj=tmerc +lat_0=0 +lon_0=90 +k=1 +x_0=30500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-30
Proj4js.defs["EPSG:2665"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=31500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-31
Proj4js.defs["EPSG:2666"] = "+proj=tmerc +lat_0=0 +lon_0=96 +k=1 +x_0=32500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-32
Proj4js.defs["EPSG:2667"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=33500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-33
Proj4js.defs["EPSG:2668"] = "+proj=tmerc +lat_0=0 +lon_0=102 +k=1 +x_0=34500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-34
Proj4js.defs["EPSG:2669"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=35500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-35
Proj4js.defs["EPSG:2670"] = "+proj=tmerc +lat_0=0 +lon_0=108 +k=1 +x_0=36500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-36
Proj4js.defs["EPSG:2671"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=37500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-37
Proj4js.defs["EPSG:2672"] = "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=38500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-38
Proj4js.defs["EPSG:2673"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=39500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-39
Proj4js.defs["EPSG:2674"] = "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=40500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-40
Proj4js.defs["EPSG:2675"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=41500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-41
Proj4js.defs["EPSG:2676"] = "+proj=tmerc +lat_0=0 +lon_0=126 +k=1 +x_0=42500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-42
Proj4js.defs["EPSG:2677"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=43500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-43
Proj4js.defs["EPSG:2678"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=1 +x_0=44500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-44
Proj4js.defs["EPSG:2679"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=45500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-45
Proj4js.defs["EPSG:2680"] = "+proj=tmerc +lat_0=0 +lon_0=138 +k=1 +x_0=46500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-46
Proj4js.defs["EPSG:2681"] = "+proj=tmerc +lat_0=0 +lon_0=141 +k=1 +x_0=47500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-47
Proj4js.defs["EPSG:2682"] = "+proj=tmerc +lat_0=0 +lon_0=144 +k=1 +x_0=48500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-48
Proj4js.defs["EPSG:2683"] = "+proj=tmerc +lat_0=0 +lon_0=147 +k=1 +x_0=49500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-49
Proj4js.defs["EPSG:2684"] = "+proj=tmerc +lat_0=0 +lon_0=150 +k=1 +x_0=50500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-50
Proj4js.defs["EPSG:2685"] = "+proj=tmerc +lat_0=0 +lon_0=153 +k=1 +x_0=51500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-51
Proj4js.defs["EPSG:2686"] = "+proj=tmerc +lat_0=0 +lon_0=156 +k=1 +x_0=52500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-52
Proj4js.defs["EPSG:2687"] = "+proj=tmerc +lat_0=0 +lon_0=159 +k=1 +x_0=53500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-53
Proj4js.defs["EPSG:2688"] = "+proj=tmerc +lat_0=0 +lon_0=162 +k=1 +x_0=54500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-54
Proj4js.defs["EPSG:2689"] = "+proj=tmerc +lat_0=0 +lon_0=165 +k=1 +x_0=55500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-55
Proj4js.defs["EPSG:2690"] = "+proj=tmerc +lat_0=0 +lon_0=168 +k=1 +x_0=56500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-56
Proj4js.defs["EPSG:2691"] = "+proj=tmerc +lat_0=0 +lon_0=171 +k=1 +x_0=57500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-57
Proj4js.defs["EPSG:2692"] = "+proj=tmerc +lat_0=0 +lon_0=174 +k=1 +x_0=58500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-58
Proj4js.defs["EPSG:2693"] = "+proj=tmerc +lat_0=0 +lon_0=177 +k=1 +x_0=59500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-59
Proj4js.defs["EPSG:3390"] = "+proj=tmerc +lat_0=0 +lon_0=180 +k=1 +x_0=60500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-60/1
Proj4js.defs["EPSG:2695"] = "+proj=tmerc +lat_0=0 +lon_0=-177 +k=1 +x_0=61500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-61
Proj4js.defs["EPSG:2696"] = "+proj=tmerc +lat_0=0 +lon_0=-174 +k=1 +x_0=62500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-62
Proj4js.defs["EPSG:2697"] = "+proj=tmerc +lat_0=0 +lon_0=-171 +k=1 +x_0=63500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-63
Proj4js.defs["EPSG:2698"] = "+proj=tmerc +lat_0=0 +lon_0=-168 +k=1 +x_0=64500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-64
Proj4js.defs["EPSG:2641"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=7500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-7
Proj4js.defs["EPSG:2642"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=8500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-8
Proj4js.defs["EPSG:2643"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=9500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo95.GK3d-9
Proj4js.defs["EPSG:3146"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=1 +x_0=6500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d-6
Proj4js.defs["EPSG:3147"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK3d/CM-18E
Proj4js.defs["EPSG:28410"] = "+proj=tmerc +lat_0=0 +lon_0=57 +k=1 +x_0=10500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-10
Proj4js.defs["EPSG:28411"] = "+proj=tmerc +lat_0=0 +lon_0=63 +k=1 +x_0=11500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-11
Proj4js.defs["EPSG:28412"] = "+proj=tmerc +lat_0=0 +lon_0=69 +k=1 +x_0=12500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK-12
Proj4js.defs["EPSG:4714"] = "+proj=longlat +ellps=intl +towgs84=-127,-769,472,0,0,0,0 +no_defs "; //Mentor: BELLEVUE.LL
Proj4js.defs["EPSG:32618"] = "+proj=utm +zone=18 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-18N
Proj4js.defs["EPSG:32619"] = "+proj=utm +zone=19 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-19N
Proj4js.defs["EPSG:32620"] = "+proj=utm +zone=20 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-20N
Proj4js.defs["EPSG:24818"] = "+proj=utm +zone=18 +ellps=intl +units=m +no_defs "; //Mentor: SA-18N
Proj4js.defs["EPSG:24819"] = "+proj=utm +zone=19 +ellps=intl +units=m +no_defs "; //Mentor: SA-19N
Proj4js.defs["EPSG:24820"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: SA-20N
Proj4js.defs["EPSG:29168"] = "+proj=utm +zone=18 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-18N
Proj4js.defs["EPSG:29169"] = "+proj=utm +zone=19 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19N
Proj4js.defs["EPSG:29170"] = "+proj=utm +zone=20 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-20N
Proj4js.defs["EPSG:4247"] = "+proj=longlat +ellps=intl +towgs84=-273.5,110.6,-357.9,0,0,0,0 +no_defs "; //Mentor: LaCanoa/E.LL
Proj4js.defs["EPSG:24718"] = "+proj=utm +zone=18 +ellps=intl +towgs84=-273.5,110.6,-357.9,0,0,0,0 +units=m +no_defs "; //Mentor: LaCanoa/E.UTM-18N
Proj4js.defs["EPSG:24719"] = "+proj=utm +zone=19 +ellps=intl +towgs84=-273.5,110.6,-357.9,0,0,0,0 +units=m +no_defs "; //Mentor: LaCanoa/E.UTM-19N
Proj4js.defs["EPSG:24720"] = "+proj=utm +zone=20 +ellps=intl +towgs84=-273.5,110.6,-357.9,0,0,0,0 +units=m +no_defs "; //Mentor: LaCanoa/E.UTM-20N
Proj4js.defs["EPSG:2201"] = "+proj=utm +zone=18 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: REGVEN.UTM-18N
Proj4js.defs["EPSG:4189"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: REGVEN.LL
Proj4js.defs["EPSG:2202"] = "+proj=utm +zone=19 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: REGVEN.UTM-19N
Proj4js.defs["EPSG:2203"] = "+proj=utm +zone=20 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: REGVEN.UTM-20N
Proj4js.defs["EPSG:2093"] = "+proj=tmerc +lat_0=0 +lon_0=106 +k=1 +x_0=500000 +y_0=0 +ellps=krass +towgs84=-17.51,-108.32,-62.39,0,0,0,0 +units=m +no_defs "; //Mentor: Hanoi1972.GK-106NE
Proj4js.defs["EPSG:2044"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=18500000 +y_0=0 +ellps=krass +towgs84=-17.51,-108.32,-62.39,0,0,0,0 +units=m +no_defs "; //Mentor: Hanoi1972.GK-18
Proj4js.defs["EPSG:2045"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=19500000 +y_0=0 +ellps=krass +towgs84=-17.51,-108.32,-62.39,0,0,0,0 +units=m +no_defs "; //Mentor: Hanoi1972.GK-19
Proj4js.defs["EPSG:4147"] = "+proj=longlat +ellps=krass +towgs84=-17.51,-108.32,-62.39,0,0,0,0 +no_defs "; //Mentor: Hanoi1972.LL
Proj4js.defs["EPSG:2001"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Antigua1943.BWIgrid
Proj4js.defs["EPSG:4601"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Antigua1943.LL
Proj4js.defs["EPSG:2002"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +towgs84=725,685,536,0,0,0,0 +units=m +no_defs "; //Mentor: Dominica1945.BWIgrid
Proj4js.defs["EPSG:4602"] = "+proj=longlat +ellps=clrk80 +towgs84=725,685,536,0,0,0,0 +no_defs "; //Mentor: Dominica1945.LL
Proj4js.defs["EPSG:4625"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Martinique38.LL
Proj4js.defs["EPSG:2973"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: Martinique38.UTM-20N
Proj4js.defs["EPSG:4621"] = "+proj=longlat +ellps=intl +towgs84=137,248,-430,0,0,0,0 +no_defs "; //Mentor: FortMarigot_1.LL
Proj4js.defs["EPSG:2969"] = "+proj=utm +zone=20 +ellps=intl +towgs84=137,248,-430,0,0,0,0 +units=m +no_defs "; //Mentor: FortMarigot_1.UTM-20N
Proj4js.defs["EPSG:2003"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +towgs84=72,213.7,93,0,0,0,0 +units=m +no_defs "; //Mentor: Grenada1953.BWIgrid
Proj4js.defs["EPSG:4603"] = "+proj=longlat +ellps=clrk80 +towgs84=72,213.7,93,0,0,0,0 +no_defs "; //Mentor: Grenada1953.LL
Proj4js.defs["EPSG:2004"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +towgs84=174,359,365,0,0,0,0 +units=m +no_defs "; //Mentor: Montserrat1958.BWIgrid
Proj4js.defs["EPSG:4604"] = "+proj=longlat +ellps=clrk80 +towgs84=174,359,365,0,0,0,0 +no_defs "; //Mentor: Montserrat1958.LL
Proj4js.defs["EPSG:4158"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: NAPARIMA.LL
Proj4js.defs["EPSG:2067"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: Naparima55.UTM-20N
Proj4js.defs["EPSG:4271"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Naparima1972.LL
Proj4js.defs["EPSG:27120"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: Naparima1972.UTM-20N
Proj4js.defs["EPSG:2970"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: Guadeloupe48.UTM-20N
Proj4js.defs["EPSG:2005"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: StKitts1955.BWIgrid
Proj4js.defs["EPSG:4605"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: StKitts1955.LL
Proj4js.defs["EPSG:2006"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +towgs84=-149,128,296,0,0,0,0 +units=m +no_defs "; //Mentor: StLucia1955.BWIgrid
Proj4js.defs["EPSG:4606"] = "+proj=longlat +ellps=clrk80 +towgs84=-149,128,296,0,0,0,0 +no_defs "; //Mentor: StLucia1955.LL
Proj4js.defs["EPSG:4212"] = "+proj=longlat +ellps=clrk80 +towgs84=31.95,300.99,419.19,0,0,0,0 +no_defs "; //Mentor: Barbados1938.LL
Proj4js.defs["EPSG:4607"] = "+proj=longlat +ellps=clrk80 +towgs84=195.671,332.517,274.607,0,0,0,0 +no_defs "; //Mentor: StVincent1945.LL
Proj4js.defs["EPSG:2007"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +towgs84=195.671,332.517,274.607,0,0,0,0 +units=m +no_defs "; //Mentor: StVincent1945.BWIgrid
Proj4js.defs["EPSG:21291"] = "+proj=tmerc +lat_0=0 +lon_0=-62 +k=0.9995000000000001 +x_0=400000 +y_0=0 +ellps=clrk80 +towgs84=31.95,300.99,419.19,0,0,0,0 +units=m +no_defs "; //Mentor: Barbados1938.BWIgrid
Proj4js.defs["EPSG:21292"] = "+proj=tmerc +lat_0=13.17638888888889 +lon_0=-59.55972222222222 +k=0.9999986 +x_0=30000 +y_0=75000 +ellps=clrk80 +towgs84=31.95,300.99,419.19,0,0,0,0 +units=m +no_defs "; //Mentor: Barbados1938.NtlGrid
Proj4js.defs["EPSG:2395"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=8500000 +y_0=0 +ellps=krass +towgs84=-76,-138,67,0,0,0,0 +units=m +no_defs "; //Mentor: SouthYemen_1.GK-8
Proj4js.defs["EPSG:2396"] = "+proj=tmerc +lat_0=0 +lon_0=51 +k=1 +x_0=9500000 +y_0=0 +ellps=krass +towgs84=-76,-138,67,0,0,0,0 +units=m +no_defs "; //Mentor: SouthYemen_1.GK-9
Proj4js.defs["EPSG:4164"] = "+proj=longlat +ellps=krass +towgs84=-76,-138,67,0,0,0,0 +no_defs "; //Mentor: SouthYemen_1.LL
Proj4js.defs["EPSG:2089"] = "+proj=utm +zone=38 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: Yemen96.UTM-38N
Proj4js.defs["EPSG:2090"] = "+proj=utm +zone=39 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: Yemen96.UTM-39N
Proj4js.defs["EPSG:4163"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: YemenNtl96.LL
Proj4js.defs["EPSG:4318"] = "+proj=longlat +ellps=WGS84 +towgs84=-3.2,-5.7,2.8,0,0,0,0 +no_defs "; //Mentor: NtlGeodeticNet.LL
Proj4js.defs["EPSG:31838"] = "+proj=utm +zone=38 +ellps=WGS84 +towgs84=-3.2,-5.7,2.8,0,0,0,0 +units=m +no_defs "; //Mentor: NGN.UTM-38N
Proj4js.defs["EPSG:31839"] = "+proj=utm +zone=39 +ellps=WGS84 +towgs84=-3.2,-5.7,2.8,0,0,0,0 +units=m +no_defs "; //Mentor: NGN.UTM-39N
Proj4js.defs["EPSG:20934"] = "+proj=utm +zone=34 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-34S/01
Proj4js.defs["EPSG:20935"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-35S/01
Proj4js.defs["EPSG:20936"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-36S/01
Proj4js.defs["EPSG:20935"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-35S/01
Proj4js.defs["EPSG:20936"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-36S/01
Proj4js.defs["EPSG:2136"] = "+proj=tmerc +lat_0=4.666666666666667 +lon_0=-1 +k=0.99975 +x_0=274319.7391633579 +y_0=0 +a=6378300 +b=6356751.689189189 +towgs84=-199,32,322,0,0,0,0 +to_meter=0.3047997101815088 +no_defs "; //Mentor: Accra1929.GhanaNational
Proj4js.defs["EPSG:4168"] = "+proj=longlat +a=6378300 +b=6356751.689189189 +towgs84=-199,32,322,0,0,0,0 +no_defs "; //Mentor: Accra1929.LL
Proj4js.defs["EPSG:2137"] = "+proj=tmerc +lat_0=0 +lon_0=-1 +k=0.9996 +x_0=500000 +y_0=0 +a=6378300 +b=6356751.689189189 +towgs84=-199,32,322,0,0,0,0 +units=m +no_defs "; //Mentor: Accra1929.TM-1NW
Proj4js.defs["EPSG:4143"] = "+proj=longlat +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +no_defs "; //Mentor: Abidjan1987.LL
Proj4js.defs["EPSG:2165"] = "+proj=tmerc +lat_0=0 +lon_0=-5 +k=0.9996 +x_0=500000 +y_0=0 +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +units=m +no_defs "; //Mentor: Abidjan1987.TM-5NW
Proj4js.defs["EPSG:2043"] = "+proj=utm +zone=29 +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +units=m +no_defs "; //Mentor: Abidjan1987.UTM-29N
Proj4js.defs["EPSG:2041"] = "+proj=utm +zone=30 +ellps=clrk80 +towgs84=-124.76,53,466.79,0,0,0,0 +units=m +no_defs "; //Mentor: Abidjan1987.UTM-30N
Proj4js.defs["EPSG:4201"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Adindan.LL
Proj4js.defs["EPSG:20135"] = "+proj=utm +zone=35 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-35N
Proj4js.defs["EPSG:20136"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-36N
Proj4js.defs["EPSG:20137"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-37N
Proj4js.defs["EPSG:20138"] = "+proj=utm +zone=38 +ellps=clrk80 +units=m +no_defs "; //Mentor: Adindan.UTM-38N
Proj4js.defs["EPSG:4205"] = "+proj=longlat +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +no_defs "; //Mentor: Afgooye.LL
Proj4js.defs["EPSG:20538"] = "+proj=utm +zone=38 +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +units=m +no_defs "; //Mentor: Afgooye.UTM-38N
Proj4js.defs["EPSG:20539"] = "+proj=utm +zone=39 +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +units=m +no_defs "; //Mentor: Afgooye.UTM-39N
Proj4js.defs["EPSG:4209"] = "+proj=longlat +a=6378249.145 +b=6356514.966398753 +no_defs "; //Mentor: Arc1950.LL/01
Proj4js.defs["EPSG:20934"] = "+proj=utm +zone=34 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-34S/01
Proj4js.defs["EPSG:20935"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-35S/01
Proj4js.defs["EPSG:20936"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ARC1950.UTM-36S/01
Proj4js.defs["EPSG:4210"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Arc1960.LL
Proj4js.defs["EPSG:21095"] = "+proj=utm +zone=35 +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-35N
Proj4js.defs["EPSG:21035"] = "+proj=utm +zone=35 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-35S
Proj4js.defs["EPSG:21096"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-36N
Proj4js.defs["EPSG:21036"] = "+proj=utm +zone=36 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-36S
Proj4js.defs["EPSG:21097"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-37N
Proj4js.defs["EPSG:21037"] = "+proj=utm +zone=37 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: ARC1960.UTM-37S
Proj4js.defs["EPSG:4220"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Camacupa_1.LL
Proj4js.defs["EPSG:22091"] = "+proj=tmerc +lat_0=0 +lon_0=11.5 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.TM-1130SE
Proj4js.defs["EPSG:22092"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.TM-12SE
Proj4js.defs["EPSG:22032"] = "+proj=utm +zone=32 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.UTM-32S
Proj4js.defs["EPSG:22033"] = "+proj=utm +zone=33 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: Camacupa_1.UTM-33S
Proj4js.defs["EPSG:4222"] = "+proj=longlat +a=6378249.145 +b=6356514.966398753 +no_defs "; //Mentor: Cape-1.LL
Proj4js.defs["EPSG:22234"] = "+proj=utm +zone=34 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: Cape-1.UTM-34S
Proj4js.defs["EPSG:22235"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: Cape-1.UTM-35S
Proj4js.defs["EPSG:22236"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: Cape-1.UTM-36S
Proj4js.defs["EPSG:4155"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-83,37,124,0,0,0,0 +no_defs "; //Mentor: Dabola1981.LL
Proj4js.defs["EPSG:3461"] = "+proj=utm +zone=28 +a=6378249.2 +b=6356515 +towgs84=-83,37,124,0,0,0,0 +units=m +no_defs "; //Mentor: Dabola1981.UTM-28N/01
Proj4js.defs["EPSG:3462"] = "+proj=utm +zone=29 +a=6378249.2 +b=6356515 +towgs84=-83,37,124,0,0,0,0 +units=m +no_defs "; //Mentor: Dabola1981.UTM-29N/01
Proj4js.defs["EPSG:2073"] = "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-10
Proj4js.defs["EPSG:2074"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-11
Proj4js.defs["EPSG:2075"] = "+proj=tmerc +lat_0=0 +lon_0=23 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-12
Proj4js.defs["EPSG:2076"] = "+proj=tmerc +lat_0=0 +lon_0=25 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-13
Proj4js.defs["EPSG:2068"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-5
Proj4js.defs["EPSG:2069"] = "+proj=tmerc +lat_0=0 +lon_0=11 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-6
Proj4js.defs["EPSG:2070"] = "+proj=tmerc +lat_0=0 +lon_0=13 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-7
Proj4js.defs["EPSG:2071"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-8
Proj4js.defs["EPSG:2072"] = "+proj=tmerc +lat_0=0 +lon_0=17 +k=0.9999 +x_0=200000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.Libya-9
Proj4js.defs["EPSG:2087"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.TM-12NE
Proj4js.defs["EPSG:2077"] = "+proj=utm +zone=32 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-32N
Proj4js.defs["EPSG:2078"] = "+proj=utm +zone=33 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-33N
Proj4js.defs["EPSG:2079"] = "+proj=utm +zone=34 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-34N
Proj4js.defs["EPSG:2080"] = "+proj=utm +zone=35 +ellps=intl +units=m +no_defs "; //Mentor: ELD1979.UTM-35N
Proj4js.defs["EPSG:4148"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Hartebeesthoek94.LL
Proj4js.defs["EPSG:25000"] = "+proj=tmerc +lat_0=4.666666666666667 +lon_0=-1 +k=0.99975 +x_0=274319.51 +y_0=0 +ellps=clrk80 +towgs84=-130,29,364,0,0,0,0 +units=m +no_defs "; //Mentor: Leigon_1.GhanaMetreGrid
Proj4js.defs["EPSG:4250"] = "+proj=longlat +ellps=clrk80 +towgs84=-130,29,364,0,0,0,0 +no_defs "; //Mentor: Leigon_1.LL
Proj4js.defs["EPSG:4251"] = "+proj=longlat +ellps=clrk80 +towgs84=-90,40,88,0,0,0,0 +no_defs "; //Mentor: Liberia.LL
Proj4js.defs["EPSG:4142"] = "+proj=longlat +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +no_defs "; //Mentor: Locodjo1965.LL
Proj4js.defs["EPSG:2164"] = "+proj=tmerc +lat_0=0 +lon_0=-5 +k=0.9996 +x_0=500000 +y_0=0 +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +units=m +no_defs "; //Mentor: Locodjo1965.TM-5NW
Proj4js.defs["EPSG:2040"] = "+proj=utm +zone=30 +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +units=m +no_defs "; //Mentor: Locodjo1965.UTM-30N
Proj4js.defs["EPSG:4259"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Malongo1987.LL
Proj4js.defs["EPSG:25932"] = "+proj=utm +zone=32 +south +ellps=intl +units=m +no_defs "; //Mentor: Malongo1987.UTM-32S
Proj4js.defs["EPSG:4193"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-70.9,-151.8,-41.4,0,0,0,0 +no_defs "; //Mentor: Manoca1962.LL
Proj4js.defs["EPSG:2215"] = "+proj=utm +zone=32 +a=6378249.2 +b=6356515 +towgs84=-70.9,-151.8,-41.4,0,0,0,0 +units=m +no_defs "; //Mentor: Manoca1962.UTM-32N
Proj4js.defs["EPSG:2042"] = "+proj=utm +zone=29 +ellps=clrk80 +towgs84=-125,53,467,0,0,0,0 +units=m +no_defs "; //Mentor: Locodjo1965.UTM-29N
Proj4js.defs["EPSG:4261"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +no_defs "; //Mentor: Merchich/01
Proj4js.defs["EPSG:4262"] = "+proj=longlat +ellps=bessel +towgs84=639,405,60,0,0,0,0 +no_defs "; //Mentor: Massawa.LL
Proj4js.defs["EPSG:26237"] = "+proj=utm +zone=37 +ellps=bessel +towgs84=639,405,60,0,0,0,0 +units=m +no_defs "; //Mentor: Massawa.UTM-37N
Proj4js.defs["EPSG:26191"] = "+proj=lcc +lat_1=33.3 +lat_0=33.3 +lon_0=-5.4 +k_0=0.999625769 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.NordMaroc/01
Proj4js.defs["EPSG:26194"] = "+proj=lcc +lat_1=26.1 +lat_0=26.1 +lon_0=-5.4 +k_0=0.999616304 +x_0=1200000 +y_0=400000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.SaharaNord/01
Proj4js.defs["EPSG:26195"] = "+proj=lcc +lat_1=22.5 +lat_0=22.5 +lon_0=-5.4 +k_0=0.999616437 +x_0=1500000 +y_0=400000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.SaharaSud/01
Proj4js.defs["EPSG:26192"] = "+proj=lcc +lat_1=29.7 +lat_0=29.7 +lon_0=-5.4 +k_0=0.9996155960000001 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Merchich.SudMaroc/01
Proj4js.defs["EPSG:4264"] = "+proj=longlat +ellps=intl +towgs84=-252.95,-4.11,-96.38,0,0,0,0 +no_defs "; //Mentor: Mhast/gc.LL
Proj4js.defs["EPSG:26432"] = "+proj=utm +zone=32 +south +ellps=intl +towgs84=-252.95,-4.11,-96.38,0,0,0,0 +units=m +no_defs "; //Mentor: Mhast/gc.UTM-32S
Proj4js.defs["EPSG:4263"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: Minna.LL
Proj4js.defs["EPSG:26393"] = "+proj=tmerc +lat_0=4 +lon_0=12.5 +k=0.99975 +x_0=1110369.7 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.NigeriaEast
Proj4js.defs["EPSG:26392"] = "+proj=tmerc +lat_0=4 +lon_0=8.5 +k=0.99975 +x_0=670553.98 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.NigeriaMid
Proj4js.defs["EPSG:26391"] = "+proj=tmerc +lat_0=4 +lon_0=4.5 +k=0.99975 +x_0=230738.26 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.NigeriaWest
Proj4js.defs["EPSG:26331"] = "+proj=utm +zone=31 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.UTM-31N
Proj4js.defs["EPSG:26332"] = "+proj=utm +zone=32 +ellps=clrk80 +units=m +no_defs "; //Mentor: Minna.UTM-32N
Proj4js.defs["EPSG:4130"] = "+proj=longlat +ellps=WGS84 +towgs84=0,0,0,-0,-0,-0,0 +no_defs "; //Mentor: Moznet.LL
Proj4js.defs["EPSG:3036"] = "+proj=utm +zone=36 +south +ellps=WGS84 +towgs84=0,0,0,-0,-0,-0,0 +units=m +no_defs "; //Mentor: Moznet.UTM-36S
Proj4js.defs["EPSG:3037"] = "+proj=utm +zone=37 +south +ellps=WGS84 +towgs84=0,0,0,-0,-0,-0,0 +units=m +no_defs "; //Mentor: Moznet.UTM-37S
Proj4js.defs["EPSG:4266"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: Mporaloko_1.LL
Proj4js.defs["EPSG:26632"] = "+proj=utm +zone=32 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Mporaloko_1.UTM-32N
Proj4js.defs["EPSG:26692"] = "+proj=utm +zone=32 +south +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: Mporaloko_1.UTM-32S
Proj4js.defs["EPSG:30791"] = "+proj=lcc +lat_1=36 +lat_0=36 +lon_0=2.7 +k_0=0.999625544 +x_0=500135 +y_0=300090 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UnifieN
Proj4js.defs["EPSG:4307"] = "+proj=longlat +ellps=clrk80 +no_defs "; //Mentor: NordSahara1959.LL
Proj4js.defs["EPSG:30792"] = "+proj=lcc +lat_1=33.3 +lat_0=33.3 +lon_0=2.7 +k_0=0.999625769 +x_0=500135 +y_0=300090 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UnifieS
Proj4js.defs["EPSG:30729"] = "+proj=utm +zone=29 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-29N
Proj4js.defs["EPSG:30730"] = "+proj=utm +zone=30 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-30N
Proj4js.defs["EPSG:30731"] = "+proj=utm +zone=31 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-31N
Proj4js.defs["EPSG:30732"] = "+proj=utm +zone=32 +ellps=clrk80 +units=m +no_defs "; //Mentor: NordSahara1959.UTM-32N
Proj4js.defs["EPSG:22991"] = "+proj=tmerc +lat_0=30 +lon_0=35 +k=1 +x_0=300000 +y_0=1100000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.Blue
Proj4js.defs["EPSG:22994"] = "+proj=tmerc +lat_0=30 +lon_0=27 +k=1 +x_0=700000 +y_0=1200000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.ExPurple
Proj4js.defs["EPSG:4229"] = "+proj=longlat +ellps=helmert +no_defs "; //Mentor: Old-Egyp.LL
Proj4js.defs["EPSG:22993"] = "+proj=tmerc +lat_0=30 +lon_0=27 +k=1 +x_0=700000 +y_0=200000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.Purple
Proj4js.defs["EPSG:22992"] = "+proj=tmerc +lat_0=30 +lon_0=31 +k=1 +x_0=615000 +y_0=810000 +ellps=helmert +units=m +no_defs "; //Mentor: Old-Egyp.Red
Proj4js.defs["EPSG:4282"] = "+proj=longlat +a=6378249.2 +b=6356515 +no_defs "; //Mentor: PointeNoire60.LL
Proj4js.defs["EPSG:4620"] = "+proj=longlat +ellps=clrk80 +towgs84=-106,-129,165,0,0,0,0 +no_defs "; //Mentor: Point1958.LL
Proj4js.defs["EPSG:28232"] = "+proj=utm +zone=32 +south +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: PointeNoire60.UTM-32S
Proj4js.defs["EPSG:4293"] = "+proj=longlat +ellps=bess_nam +no_defs "; //Mentor: Schwarzk.LL
Proj4js.defs["EPSG:29333"] = "+proj=utm +zone=33 +south +ellps=bess_nam +units=m +no_defs "; //Mentor: Schwarzk.UTM-33S
Proj4js.defs["EPSG:4175"] = "+proj=longlat +ellps=clrk80 +towgs84=-88,4,101,0,0,0,0 +no_defs "; //Mentor: SierraLeone1968.LL
Proj4js.defs["EPSG:2161"] = "+proj=utm +zone=28 +ellps=clrk80 +towgs84=-88,4,101,0,0,0,0 +units=m +no_defs "; //Mentor: SierraLeone1968.UTM-28N
Proj4js.defs["EPSG:2162"] = "+proj=utm +zone=29 +ellps=clrk80 +towgs84=-88,4,101,0,0,0,0 +units=m +no_defs "; //Mentor: SierraLeone1968.UTM-29N
Proj4js.defs["EPSG:29738"] = "+proj=utm +zone=38 +south +ellps=intl +towgs84=-189,-242,-91,0,0,0,0 +units=m +no_defs "; //Mentor: Tananarive1925.UTM-38S
Proj4js.defs["EPSG:29739"] = "+proj=utm +zone=39 +south +ellps=intl +towgs84=-189,-242,-91,0,0,0,0 +units=m +no_defs "; //Mentor: Tananarive1925.UTM-39S
Proj4js.defs["EPSG:4297"] = "+proj=longlat +ellps=intl +towgs84=-189,-242,-91,0,0,0,0 +no_defs "; //Mentor: Tananarive1925.LL
Proj4js.defs["EPSG:2736"] = "+proj=utm +zone=36 +south +ellps=clrk66 +units=m +no_defs "; //Mentor: Tete/a.UTM-36S
Proj4js.defs["EPSG:4127"] = "+proj=longlat +ellps=clrk66 +no_defs "; //Mentor: Tete/a.LL
Proj4js.defs["EPSG:2737"] = "+proj=utm +zone=37 +south +ellps=clrk66 +units=m +no_defs "; //Mentor: Tete/a.UTM-37S
Proj4js.defs["EPSG:4304"] = "+proj=longlat +a=6378249.2 +b=6356515 +towgs84=-73,-247,227,0,0,0,0 +no_defs "; //Mentor: Voirol1875_1.LL
Proj4js.defs["EPSG:30491"] = "+proj=lcc +lat_1=36 +lat_0=36 +lon_0=2.7 +k_0=0.999625544 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=-73,-247,227,0,0,0,0 +units=m +no_defs "; //Mentor: V1875.NordAlgerie
Proj4js.defs["EPSG:30492"] = "+proj=lcc +lat_1=33.3 +lat_0=33.3 +lon_0=2.7 +k_0=0.999625769 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=-73,-247,227,0,0,0,0 +units=m +no_defs "; //Mentor: Voirol1875_1.SudAlgerie
Proj4js.defs["EPSG:2311"] = "+proj=tmerc +lat_0=0 +lon_0=6 +k=0.9996 +x_0=500000 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-6NE
Proj4js.defs["EPSG:32766"] = "+proj=tmerc +lat_0=0 +lon_0=36 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-36SE
Proj4js.defs["EPSG:4702"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Mauritania1999.LL
Proj4js.defs["EPSG:4679"] = "+proj=longlat +ellps=clrk80 +towgs84=-80.01,253.26,291.19,0,0,0,0 +no_defs "; //Mentor: Jouik61.LL
Proj4js.defs["EPSG:4680"] = "+proj=longlat +ellps=clrk80 +towgs84=124.5,-63.5,-281,0,0,0,0 +no_defs "; //Mentor: Nouakchott65.LL
Proj4js.defs["EPSG:4707"] = "+proj=longlat +ellps=intl +towgs84=114,-116,-333,0,0,0,0 +no_defs "; //Mentor: Tern61.LL
Proj4js.defs["EPSG:4230"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: LL-ERP50
Proj4js.defs["EPSG:4258"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: LL-ETRF89
Proj4js.defs["EPSG:2207"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=10500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.GK3d-10
Proj4js.defs["EPSG:2208"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=11500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.GK3d-11
Proj4js.defs["EPSG:2209"] = "+proj=tmerc +lat_0=0 +lon_0=36 +k=1 +x_0=12500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.GK3d-12
Proj4js.defs["EPSG:2210"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=13500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.GK3d-13
Proj4js.defs["EPSG:2211"] = "+proj=tmerc +lat_0=0 +lon_0=42 +k=1 +x_0=14500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.GK3d-14
Proj4js.defs["EPSG:2212"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=15500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.GK3d-15
Proj4js.defs["EPSG:2206"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=9500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.GK3d-9
Proj4js.defs["EPSG:2319"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.TM27
Proj4js.defs["EPSG:2320"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.TM30
Proj4js.defs["EPSG:2321"] = "+proj=tmerc +lat_0=0 +lon_0=33 +k=1 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.TM33
Proj4js.defs["EPSG:2322"] = "+proj=tmerc +lat_0=0 +lon_0=36 +k=1 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.TM36
Proj4js.defs["EPSG:2323"] = "+proj=tmerc +lat_0=0 +lon_0=39 +k=1 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.TM39
Proj4js.defs["EPSG:2324"] = "+proj=tmerc +lat_0=0 +lon_0=42 +k=1 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.TM42
Proj4js.defs["EPSG:2325"] = "+proj=tmerc +lat_0=0 +lon_0=45 +k=1 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: ED50.TM45
Proj4js.defs["EPSG:2058"] = "+proj=utm +zone=38 +ellps=intl +units=m +no_defs "; //Mentor: Europ50/1977.UTM-38N
Proj4js.defs["EPSG:2059"] = "+proj=utm +zone=39 +ellps=intl +units=m +no_defs "; //Mentor: Europ50/1977.UTM-39N
Proj4js.defs["EPSG:2060"] = "+proj=utm +zone=40 +ellps=intl +units=m +no_defs "; //Mentor: Europ50/1977.UTM-40N
Proj4js.defs["EPSG:2061"] = "+proj=utm +zone=41 +ellps=intl +units=m +no_defs "; //Mentor: Europ50/1977.UTM-41N
Proj4js.defs["EPSG:2198"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=900000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.Kp2K-Bornholm
Proj4js.defs["EPSG:2196"] = "+proj=tmerc +lat_0=0 +lon_0=9.5 +k=0.99995 +x_0=200000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.Kp2K-Jutland
Proj4js.defs["EPSG:2197"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=0.99995 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.Kp2K-Zealand
Proj4js.defs["EPSG:2176"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=0.999923 +x_0=5500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-5
Proj4js.defs["EPSG:2177"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=0.999923 +x_0=6500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-6
Proj4js.defs["EPSG:2178"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=0.999923 +x_0=7500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-7
Proj4js.defs["EPSG:2179"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=0.999923 +x_0=8500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS2K-8
Proj4js.defs["EPSG:2180"] = "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.PolandCS92
Proj4js.defs["EPSG:2213"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=0.9996 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.TM-30NE
Proj4js.defs["EPSG:25884"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=0.9996 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.TM-Baltic
Proj4js.defs["EPSG:25828"] = "+proj=utm +zone=28 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-28N
Proj4js.defs["EPSG:25829"] = "+proj=utm +zone=29 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-29N
Proj4js.defs["EPSG:25830"] = "+proj=utm +zone=30 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-30N
Proj4js.defs["EPSG:25831"] = "+proj=utm +zone=31 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-31N
Proj4js.defs["EPSG:25832"] = "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-32N
Proj4js.defs["EPSG:25833"] = "+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-33N
Proj4js.defs["EPSG:25834"] = "+proj=utm +zone=34 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-34N
Proj4js.defs["EPSG:25835"] = "+proj=utm +zone=35 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-35N
Proj4js.defs["EPSG:25836"] = "+proj=utm +zone=36 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-36N
Proj4js.defs["EPSG:25837"] = "+proj=utm +zone=37 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-37N
Proj4js.defs["EPSG:25838"] = "+proj=utm +zone=38 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRS89.UTM-38N
Proj4js.defs["EPSG:23090"] = "+proj=tmerc +lat_0=0 +lon_0=0 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Europ50.TM-0N
Proj4js.defs["EPSG:23095"] = "+proj=tmerc +lat_0=0 +lon_0=5 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Europ50.TM-5NE
Proj4js.defs["EPSG:4154"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Europ50/1977.LL
Proj4js.defs["EPSG:4668"] = "+proj=longlat +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs "; //Mentor: EUROP79.LL
Proj4js.defs["EPSG:2397"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=krass +towgs84=24,-123,-94,0.02,-0.25,-0.13,1.1 +units=m +no_defs "; //Mentor: Pulkovo42/83a.GK-3
Proj4js.defs["EPSG:2398"] = "+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=4500000 +y_0=0 +ellps=krass +towgs84=24,-123,-94,0.02,-0.25,-0.13,1.1 +units=m +no_defs "; //Mentor: Pulkovo42/83a.GK-4
Proj4js.defs["EPSG:2399"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=krass +towgs84=24,-123,-94,0.02,-0.25,-0.13,1.1 +units=m +no_defs "; //Mentor: Pulkovo42/83a.GK-5
Proj4js.defs["EPSG:23028"] = "+proj=utm +zone=28 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM28
Proj4js.defs["EPSG:23029"] = "+proj=utm +zone=29 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM29
Proj4js.defs["EPSG:23030"] = "+proj=utm +zone=30 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM30
Proj4js.defs["EPSG:23031"] = "+proj=utm +zone=31 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM31
Proj4js.defs["EPSG:23032"] = "+proj=utm +zone=32 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM32
Proj4js.defs["EPSG:23033"] = "+proj=utm +zone=33 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM33
Proj4js.defs["EPSG:23034"] = "+proj=utm +zone=34 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM34
Proj4js.defs["EPSG:23035"] = "+proj=utm +zone=35 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM35
Proj4js.defs["EPSG:23036"] = "+proj=utm +zone=36 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM36
Proj4js.defs["EPSG:23037"] = "+proj=utm +zone=37 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM37
Proj4js.defs["EPSG:23038"] = "+proj=utm +zone=38 +ellps=intl +units=m +no_defs "; //Mentor: ED50-UTM38
Proj4js.defs["EPSG:4231"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: Europ87.LL
Proj4js.defs["EPSG:3034"] = "+proj=lcc +lat_1=35 +lat_2=65 +lat_0=52 +lon_0=10 +x_0=4000000 +y_0=2800000 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.Europe/Lambert
Proj4js.defs["EPSG:3035"] = "+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.Europe/EqArea
Proj4js.defs["EPSG:3038"] = "+proj=utm +zone=26 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM26
Proj4js.defs["EPSG:3039"] = "+proj=utm +zone=27 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM27
Proj4js.defs["EPSG:3040"] = "+proj=utm +zone=28 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM28
Proj4js.defs["EPSG:3041"] = "+proj=utm +zone=29 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM29
Proj4js.defs["EPSG:3042"] = "+proj=utm +zone=30 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM30
Proj4js.defs["EPSG:3043"] = "+proj=utm +zone=31 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM31
Proj4js.defs["EPSG:3044"] = "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM32
Proj4js.defs["EPSG:3045"] = "+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM33
Proj4js.defs["EPSG:3046"] = "+proj=utm +zone=34 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM34
Proj4js.defs["EPSG:3047"] = "+proj=utm +zone=35 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM35
Proj4js.defs["EPSG:3048"] = "+proj=utm +zone=36 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM36
Proj4js.defs["EPSG:3049"] = "+proj=utm +zone=37 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM37
Proj4js.defs["EPSG:3050"] = "+proj=utm +zone=38 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM38
Proj4js.defs["EPSG:3051"] = "+proj=utm +zone=39 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM39
Proj4js.defs["EPSG:3108"] = "+proj=tmerc +lat_0=49.5 +lon_0=-2.416666666666667 +k=0.999997 +x_0=47000 +y_0=50000 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.GuernseyGrid
Proj4js.defs["EPSG:3109"] = "+proj=tmerc +lat_0=49.225 +lon_0=-2.135 +k=0.9999999000000001 +x_0=40000 +y_0=70000 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.Jersey/TM
Proj4js.defs["EPSG:3067"] = "+proj=utm +zone=35 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.TM-35/Fin
Proj4js.defs["EPSG:3126"] = "+proj=tmerc +lat_0=0 +lon_0=19 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-19
Proj4js.defs["EPSG:3127"] = "+proj=tmerc +lat_0=0 +lon_0=20 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-20
Proj4js.defs["EPSG:3128"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-21
Proj4js.defs["EPSG:3129"] = "+proj=tmerc +lat_0=0 +lon_0=22 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-22
Proj4js.defs["EPSG:3130"] = "+proj=tmerc +lat_0=0 +lon_0=23 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-23
Proj4js.defs["EPSG:3131"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-24
Proj4js.defs["EPSG:3132"] = "+proj=tmerc +lat_0=0 +lon_0=25 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-25
Proj4js.defs["EPSG:3133"] = "+proj=tmerc +lat_0=0 +lon_0=26 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-26
Proj4js.defs["EPSG:3134"] = "+proj=tmerc +lat_0=0 +lon_0=27 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-27
Proj4js.defs["EPSG:3135"] = "+proj=tmerc +lat_0=0 +lon_0=28 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-28
Proj4js.defs["EPSG:3136"] = "+proj=tmerc +lat_0=0 +lon_0=29 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-29
Proj4js.defs["EPSG:3137"] = "+proj=tmerc +lat_0=0 +lon_0=30 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-30
Proj4js.defs["EPSG:3138"] = "+proj=tmerc +lat_0=0 +lon_0=31 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: ETRF89.FinlandGK-31
Proj4js.defs["EPSG:3064"] = "+proj=utm +zone=32 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: IGM1995.UTM-32N
Proj4js.defs["EPSG:3065"] = "+proj=utm +zone=33 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: IGM1995.UTM-33N
Proj4js.defs["EPSG:24817"] = "+proj=utm +zone=17 +ellps=intl +units=m +no_defs "; //Mentor: SA-17N
Proj4js.defs["EPSG:24818"] = "+proj=utm +zone=18 +ellps=intl +units=m +no_defs "; //Mentor: SA-18N
Proj4js.defs["EPSG:24819"] = "+proj=utm +zone=19 +ellps=intl +units=m +no_defs "; //Mentor: SA-19N
Proj4js.defs["EPSG:24820"] = "+proj=utm +zone=20 +ellps=intl +units=m +no_defs "; //Mentor: SA-20N
Proj4js.defs["EPSG:24821"] = "+proj=utm +zone=21 +ellps=intl +units=m +no_defs "; //Mentor: SA-21N
Proj4js.defs["EPSG:24877"] = "+proj=utm +zone=17 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-17S
Proj4js.defs["EPSG:24878"] = "+proj=utm +zone=18 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-18S
Proj4js.defs["EPSG:24879"] = "+proj=utm +zone=19 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-19S
Proj4js.defs["EPSG:24880"] = "+proj=utm +zone=20 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-20S
Proj4js.defs["EPSG:24881"] = "+proj=utm +zone=21 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-21S
Proj4js.defs["EPSG:24882"] = "+proj=utm +zone=22 +south +ellps=intl +units=m +no_defs "; //Mentor: SA-22S
Proj4js.defs["EPSG:4623"] = "+proj=longlat +ellps=intl +towgs84=-186,230,110,0,0,0,0 +no_defs "; //Mentor: CSG1967.LL
Proj4js.defs["EPSG:2971"] = "+proj=utm +zone=22 +ellps=intl +towgs84=-186,230,110,0,0,0,0 +units=m +no_defs "; //Mentor: CSG1967.UTM-22N
Proj4js.defs["EPSG:4170"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: GRSSA.LL
Proj4js.defs["EPSG:31986"] = "+proj=utm +zone=17 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-17N
Proj4js.defs["EPSG:31992"] = "+proj=utm +zone=17 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-17S
Proj4js.defs["EPSG:31987"] = "+proj=utm +zone=18 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-18N
Proj4js.defs["EPSG:31993"] = "+proj=utm +zone=18 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-18S
Proj4js.defs["EPSG:31988"] = "+proj=utm +zone=19 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-19N
Proj4js.defs["EPSG:31994"] = "+proj=utm +zone=19 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-19S
Proj4js.defs["EPSG:31989"] = "+proj=utm +zone=20 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-20N
Proj4js.defs["EPSG:31995"] = "+proj=utm +zone=20 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-20S
Proj4js.defs["EPSG:31990"] = "+proj=utm +zone=21 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-21N
Proj4js.defs["EPSG:31996"] = "+proj=utm +zone=21 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-21S
Proj4js.defs["EPSG:31991"] = "+proj=utm +zone=22 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-22N
Proj4js.defs["EPSG:31997"] = "+proj=utm +zone=22 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-22S
Proj4js.defs["EPSG:31998"] = "+proj=utm +zone=23 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-23S
Proj4js.defs["EPSG:31999"] = "+proj=utm +zone=24 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-24S
Proj4js.defs["EPSG:32000"] = "+proj=utm +zone=25 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: GRSSA.UTM-25S
Proj4js.defs["EPSG:2083"] = "+proj=tmerc +lat_0=-90 +lon_0=-69 +k=1 +x_0=2500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: Hito63a.Argentina-2
Proj4js.defs["EPSG:2084"] = "+proj=utm +zone=19 +south +ellps=intl +units=m +no_defs "; //Mentor: Hito63a.UTM-19S
Proj4js.defs["EPSG:4254"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: HitoXVIII63a.LL
Proj4js.defs["EPSG:4190"] = "+proj=longlat +ellps=GRS80 +no_defs "; //Mentor: PosGAr98.LL
Proj4js.defs["EPSG:2317"] = "+proj=lcc +lat_1=9 +lat_2=3 +lat_0=6 +lon_0=-66 +x_0=1000000 +y_0=1000000 +ellps=intl +units=m +no_defs "; //Mentor: PSAD56.IcnRegional
Proj4js.defs["EPSG:24892"] = "+proj=tmerc +lat_0=-9.5 +lon_0=-76 +k=0.99932994 +x_0=720000 +y_0=1039979.159 +ellps=intl +units=m +no_defs "; //Mentor: PSAD56.PeruCentral
Proj4js.defs["EPSG:24893"] = "+proj=tmerc +lat_0=-9.5 +lon_0=-70.5 +k=0.99952992 +x_0=1324000 +y_0=1040084.558 +ellps=intl +units=m +no_defs "; //Mentor: PSAD56.PeruEast
Proj4js.defs["EPSG:24891"] = "+proj=tmerc +lat_0=-6 +lon_0=-80.5 +k=0.99983008 +x_0=222000 +y_0=1426834.743 +ellps=intl +units=m +no_defs "; //Mentor: PSAD56.PeruWest
Proj4js.defs["EPSG:2201"] = "+proj=utm +zone=18 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: REGVEN.UTM-18N
Proj4js.defs["EPSG:4189"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: REGVEN.LL
Proj4js.defs["EPSG:2202"] = "+proj=utm +zone=19 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: REGVEN.UTM-19N
Proj4js.defs["EPSG:2203"] = "+proj=utm +zone=20 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: REGVEN.UTM-20N
Proj4js.defs["EPSG:2972"] = "+proj=utm +zone=22 +ellps=GRS80 +towgs84=2,2,-2,0,0,0,0 +units=m +no_defs "; //Mentor: RGFG95a.UTM-22N
Proj4js.defs["EPSG:29101"] = "+proj=poly +lat_0=0 +lon_0=-54 +x_0=5000000 +y_0=10000000 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD1969.BzPolyconic/01
Proj4js.defs["EPSG:29187"] = "+proj=utm +zone=17 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-17S
Proj4js.defs["EPSG:29168"] = "+proj=utm +zone=18 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-18N
Proj4js.defs["EPSG:29188"] = "+proj=utm +zone=18 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-18S
Proj4js.defs["EPSG:29169"] = "+proj=utm +zone=19 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19N
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-19S
Proj4js.defs["EPSG:29170"] = "+proj=utm +zone=20 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-20N
Proj4js.defs["EPSG:29190"] = "+proj=utm +zone=20 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-20S
Proj4js.defs["EPSG:29171"] = "+proj=utm +zone=21 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-21N
Proj4js.defs["EPSG:29191"] = "+proj=utm +zone=21 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-21S
Proj4js.defs["EPSG:29172"] = "+proj=utm +zone=22 +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-22N
Proj4js.defs["EPSG:29192"] = "+proj=utm +zone=22 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-22S
Proj4js.defs["EPSG:29193"] = "+proj=utm +zone=23 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-23S
Proj4js.defs["EPSG:29194"] = "+proj=utm +zone=24 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-24S
Proj4js.defs["EPSG:29195"] = "+proj=utm +zone=25 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SAD69.UTM-25S
Proj4js.defs["EPSG:4309"] = "+proj=longlat +ellps=intl +towgs84=-155,171,37,0,0,0,0 +no_defs "; //Mentor: Yacare/E.LL
Proj4js.defs["EPSG:4311"] = "+proj=longlat +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +no_defs "; //Mentor: Zanderij.LL
Proj4js.defs["EPSG:31171"] = "+proj=tmerc +lat_0=0 +lon_0=-55.68333333333333 +k=0.9999 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.SurinameTM
Proj4js.defs["EPSG:31170"] = "+proj=tmerc +lat_0=0 +lon_0=-55.68333333333333 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.SurinameTMOld
Proj4js.defs["EPSG:31154"] = "+proj=tmerc +lat_0=0 +lon_0=-54 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.TM-54NW
Proj4js.defs["EPSG:31121"] = "+proj=utm +zone=21 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: Zanderij.UTM-21N
Proj4js.defs["EPSG:2309"] = "+proj=tmerc +lat_0=0 +lon_0=116 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-116SE
Proj4js.defs["EPSG:2310"] = "+proj=tmerc +lat_0=0 +lon_0=132 +k=0.9996 +x_0=500000 +y_0=10000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.TM-132SE
Proj4js.defs["EPSG:4674"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: SRG-SA/2000.LL
Proj4js.defs["EPSG:4686"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: MarcoGNR.LL
Proj4js.defs["EPSG:31965"] = "+proj=utm +zone=11 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-11N
Proj4js.defs["EPSG:31966"] = "+proj=utm +zone=12 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-12N
Proj4js.defs["EPSG:31967"] = "+proj=utm +zone=13 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-13N
Proj4js.defs["EPSG:31968"] = "+proj=utm +zone=14 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-14N
Proj4js.defs["EPSG:31969"] = "+proj=utm +zone=15 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-15N
Proj4js.defs["EPSG:31970"] = "+proj=utm +zone=16 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-16N
Proj4js.defs["EPSG:31971"] = "+proj=utm +zone=17 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-17N
Proj4js.defs["EPSG:31972"] = "+proj=utm +zone=18 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-18N
Proj4js.defs["EPSG:31973"] = "+proj=utm +zone=19 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-19N
Proj4js.defs["EPSG:31974"] = "+proj=utm +zone=20 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-20N
Proj4js.defs["EPSG:31975"] = "+proj=utm +zone=21 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-21N
Proj4js.defs["EPSG:31976"] = "+proj=utm +zone=22 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-22N
Proj4js.defs["EPSG:31977"] = "+proj=utm +zone=17 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-17S
Proj4js.defs["EPSG:31978"] = "+proj=utm +zone=18 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-18S
Proj4js.defs["EPSG:31979"] = "+proj=utm +zone=19 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-19S
Proj4js.defs["EPSG:31980"] = "+proj=utm +zone=20 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-20S
Proj4js.defs["EPSG:31981"] = "+proj=utm +zone=21 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-21S
Proj4js.defs["EPSG:31982"] = "+proj=utm +zone=22 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-22S
Proj4js.defs["EPSG:31983"] = "+proj=utm +zone=23 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-23S
Proj4js.defs["EPSG:31984"] = "+proj=utm +zone=24 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-24S
Proj4js.defs["EPSG:31985"] = "+proj=utm +zone=25 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: SIRGAS2000.UTM-25S
Proj4js.defs["EPSG:32210"] = "+proj=utm +zone=10 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-10N
Proj4js.defs["EPSG:32310"] = "+proj=utm +zone=10 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-10S
Proj4js.defs["EPSG:32211"] = "+proj=utm +zone=11 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-11N
Proj4js.defs["EPSG:32311"] = "+proj=utm +zone=11 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-11S
Proj4js.defs["EPSG:32212"] = "+proj=utm +zone=12 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-12N
Proj4js.defs["EPSG:32312"] = "+proj=utm +zone=12 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-12S
Proj4js.defs["EPSG:32213"] = "+proj=utm +zone=13 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-13N
Proj4js.defs["EPSG:32313"] = "+proj=utm +zone=13 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-13S
Proj4js.defs["EPSG:32214"] = "+proj=utm +zone=14 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-14N
Proj4js.defs["EPSG:32314"] = "+proj=utm +zone=14 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-14S
Proj4js.defs["EPSG:32215"] = "+proj=utm +zone=15 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-15N
Proj4js.defs["EPSG:32315"] = "+proj=utm +zone=15 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-15S
Proj4js.defs["EPSG:32216"] = "+proj=utm +zone=16 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-16N
Proj4js.defs["EPSG:32316"] = "+proj=utm +zone=16 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-16S
Proj4js.defs["EPSG:32217"] = "+proj=utm +zone=17 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-17N
Proj4js.defs["EPSG:32317"] = "+proj=utm +zone=17 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-17S
Proj4js.defs["EPSG:32218"] = "+proj=utm +zone=18 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-18N
Proj4js.defs["EPSG:32318"] = "+proj=utm +zone=18 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-18S
Proj4js.defs["EPSG:32219"] = "+proj=utm +zone=19 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-19N
Proj4js.defs["EPSG:32319"] = "+proj=utm +zone=19 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-19S
Proj4js.defs["EPSG:32201"] = "+proj=utm +zone=1 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-1N
Proj4js.defs["EPSG:32301"] = "+proj=utm +zone=1 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-1S
Proj4js.defs["EPSG:32220"] = "+proj=utm +zone=20 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-20N
Proj4js.defs["EPSG:32320"] = "+proj=utm +zone=20 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-20S
Proj4js.defs["EPSG:32221"] = "+proj=utm +zone=21 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-21N
Proj4js.defs["EPSG:32321"] = "+proj=utm +zone=21 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-21S
Proj4js.defs["EPSG:32222"] = "+proj=utm +zone=22 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-22N
Proj4js.defs["EPSG:32322"] = "+proj=utm +zone=22 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-22S
Proj4js.defs["EPSG:32223"] = "+proj=utm +zone=23 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-23N
Proj4js.defs["EPSG:32323"] = "+proj=utm +zone=23 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-23S
Proj4js.defs["EPSG:32224"] = "+proj=utm +zone=24 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-24N
Proj4js.defs["EPSG:32324"] = "+proj=utm +zone=24 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-24S
Proj4js.defs["EPSG:32225"] = "+proj=utm +zone=25 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-25N
Proj4js.defs["EPSG:32325"] = "+proj=utm +zone=25 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-25S
Proj4js.defs["EPSG:32226"] = "+proj=utm +zone=26 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-26N
Proj4js.defs["EPSG:32326"] = "+proj=utm +zone=26 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-26S
Proj4js.defs["EPSG:32227"] = "+proj=utm +zone=27 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-27N
Proj4js.defs["EPSG:32327"] = "+proj=utm +zone=27 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-27S
Proj4js.defs["EPSG:32228"] = "+proj=utm +zone=28 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-28N
Proj4js.defs["EPSG:32328"] = "+proj=utm +zone=28 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-28S
Proj4js.defs["EPSG:32229"] = "+proj=utm +zone=29 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-29N
Proj4js.defs["EPSG:32329"] = "+proj=utm +zone=29 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-29S
Proj4js.defs["EPSG:32202"] = "+proj=utm +zone=2 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-2N
Proj4js.defs["EPSG:32302"] = "+proj=utm +zone=2 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-2S
Proj4js.defs["EPSG:32230"] = "+proj=utm +zone=30 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-30N
Proj4js.defs["EPSG:32330"] = "+proj=utm +zone=30 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-30S
Proj4js.defs["EPSG:32231"] = "+proj=utm +zone=31 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-31N
Proj4js.defs["EPSG:32331"] = "+proj=utm +zone=31 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-31S
Proj4js.defs["EPSG:32232"] = "+proj=utm +zone=32 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-32N
Proj4js.defs["EPSG:32332"] = "+proj=utm +zone=32 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-32S
Proj4js.defs["EPSG:32233"] = "+proj=utm +zone=33 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-33N
Proj4js.defs["EPSG:32333"] = "+proj=utm +zone=33 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-33S
Proj4js.defs["EPSG:32234"] = "+proj=utm +zone=34 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-34N
Proj4js.defs["EPSG:32334"] = "+proj=utm +zone=34 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-34S
Proj4js.defs["EPSG:32235"] = "+proj=utm +zone=35 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-35N
Proj4js.defs["EPSG:32335"] = "+proj=utm +zone=35 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-35S
Proj4js.defs["EPSG:32236"] = "+proj=utm +zone=36 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-36N
Proj4js.defs["EPSG:32336"] = "+proj=utm +zone=36 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-36S
Proj4js.defs["EPSG:32237"] = "+proj=utm +zone=37 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-37N
Proj4js.defs["EPSG:32337"] = "+proj=utm +zone=37 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-37S
Proj4js.defs["EPSG:32238"] = "+proj=utm +zone=38 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-38N
Proj4js.defs["EPSG:32338"] = "+proj=utm +zone=38 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-38S
Proj4js.defs["EPSG:32239"] = "+proj=utm +zone=39 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-39N
Proj4js.defs["EPSG:32339"] = "+proj=utm +zone=39 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-39S
Proj4js.defs["EPSG:32203"] = "+proj=utm +zone=3 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-3N
Proj4js.defs["EPSG:32303"] = "+proj=utm +zone=3 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-3S
Proj4js.defs["EPSG:32240"] = "+proj=utm +zone=40 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-40N
Proj4js.defs["EPSG:32340"] = "+proj=utm +zone=40 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-40S
Proj4js.defs["EPSG:32241"] = "+proj=utm +zone=41 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-41N
Proj4js.defs["EPSG:32341"] = "+proj=utm +zone=41 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-41S
Proj4js.defs["EPSG:32242"] = "+proj=utm +zone=42 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-42N
Proj4js.defs["EPSG:32342"] = "+proj=utm +zone=42 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-42S
Proj4js.defs["EPSG:32243"] = "+proj=utm +zone=43 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-43N
Proj4js.defs["EPSG:32343"] = "+proj=utm +zone=43 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-43S
Proj4js.defs["EPSG:32244"] = "+proj=utm +zone=44 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-44N
Proj4js.defs["EPSG:32344"] = "+proj=utm +zone=44 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-44S
Proj4js.defs["EPSG:32245"] = "+proj=utm +zone=45 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-45N
Proj4js.defs["EPSG:32345"] = "+proj=utm +zone=45 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-45S
Proj4js.defs["EPSG:32246"] = "+proj=utm +zone=46 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-46N
Proj4js.defs["EPSG:32346"] = "+proj=utm +zone=46 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-46S
Proj4js.defs["EPSG:32247"] = "+proj=utm +zone=47 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-47N
Proj4js.defs["EPSG:32347"] = "+proj=utm +zone=47 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-47S
Proj4js.defs["EPSG:32248"] = "+proj=utm +zone=48 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-48N
Proj4js.defs["EPSG:32348"] = "+proj=utm +zone=48 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-48S
Proj4js.defs["EPSG:32249"] = "+proj=utm +zone=49 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-49N
Proj4js.defs["EPSG:32349"] = "+proj=utm +zone=49 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-49S
Proj4js.defs["EPSG:32204"] = "+proj=utm +zone=4 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-4N
Proj4js.defs["EPSG:32304"] = "+proj=utm +zone=4 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-4S
Proj4js.defs["EPSG:32250"] = "+proj=utm +zone=50 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-50N
Proj4js.defs["EPSG:32350"] = "+proj=utm +zone=50 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-50S
Proj4js.defs["EPSG:32251"] = "+proj=utm +zone=51 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-51N
Proj4js.defs["EPSG:32351"] = "+proj=utm +zone=51 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-51S
Proj4js.defs["EPSG:32252"] = "+proj=utm +zone=52 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-52N
Proj4js.defs["EPSG:32352"] = "+proj=utm +zone=52 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-52S
Proj4js.defs["EPSG:32253"] = "+proj=utm +zone=53 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-53N
Proj4js.defs["EPSG:32353"] = "+proj=utm +zone=53 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-53S
Proj4js.defs["EPSG:32254"] = "+proj=utm +zone=54 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-54N
Proj4js.defs["EPSG:32354"] = "+proj=utm +zone=54 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-54S
Proj4js.defs["EPSG:32255"] = "+proj=utm +zone=55 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-55N
Proj4js.defs["EPSG:32355"] = "+proj=utm +zone=55 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-55S
Proj4js.defs["EPSG:32256"] = "+proj=utm +zone=56 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-56N
Proj4js.defs["EPSG:32356"] = "+proj=utm +zone=56 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-56S
Proj4js.defs["EPSG:32257"] = "+proj=utm +zone=57 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-57N
Proj4js.defs["EPSG:32357"] = "+proj=utm +zone=57 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-57S
Proj4js.defs["EPSG:32258"] = "+proj=utm +zone=58 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-58N
Proj4js.defs["EPSG:32358"] = "+proj=utm +zone=58 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-58S
Proj4js.defs["EPSG:32259"] = "+proj=utm +zone=59 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-59N
Proj4js.defs["EPSG:32359"] = "+proj=utm +zone=59 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-59S
Proj4js.defs["EPSG:32205"] = "+proj=utm +zone=5 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-5N
Proj4js.defs["EPSG:32305"] = "+proj=utm +zone=5 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-5S
Proj4js.defs["EPSG:32260"] = "+proj=utm +zone=60 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-60N
Proj4js.defs["EPSG:32360"] = "+proj=utm +zone=60 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-60S
Proj4js.defs["EPSG:32206"] = "+proj=utm +zone=6 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-6N
Proj4js.defs["EPSG:32306"] = "+proj=utm +zone=6 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-6S
Proj4js.defs["EPSG:32207"] = "+proj=utm +zone=7 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-7N
Proj4js.defs["EPSG:32307"] = "+proj=utm +zone=7 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-7S
Proj4js.defs["EPSG:32208"] = "+proj=utm +zone=8 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-8N
Proj4js.defs["EPSG:32308"] = "+proj=utm +zone=8 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-8S
Proj4js.defs["EPSG:32209"] = "+proj=utm +zone=9 +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-9N
Proj4js.defs["EPSG:32309"] = "+proj=utm +zone=9 +south +ellps=WGS72 +units=m +no_defs "; //Mentor: WGS72.UTM-9S
Proj4js.defs["EPSG:2094"] = "+proj=tmerc +lat_0=0 +lon_0=106 +k=0.9996 +x_0=500000 +y_0=0 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.TM-106NE
Proj4js.defs["EPSG:32410"] = "+proj=utm +zone=10 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-10N
Proj4js.defs["EPSG:32510"] = "+proj=utm +zone=10 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-10S
Proj4js.defs["EPSG:32411"] = "+proj=utm +zone=11 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-11N
Proj4js.defs["EPSG:32511"] = "+proj=utm +zone=11 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-11S
Proj4js.defs["EPSG:32412"] = "+proj=utm +zone=12 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-12N
Proj4js.defs["EPSG:32512"] = "+proj=utm +zone=12 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-12S
Proj4js.defs["EPSG:32413"] = "+proj=utm +zone=13 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-13N
Proj4js.defs["EPSG:32513"] = "+proj=utm +zone=13 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-13S
Proj4js.defs["EPSG:32414"] = "+proj=utm +zone=14 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-14N
Proj4js.defs["EPSG:32514"] = "+proj=utm +zone=14 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-14S
Proj4js.defs["EPSG:32415"] = "+proj=utm +zone=15 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-15N
Proj4js.defs["EPSG:32515"] = "+proj=utm +zone=15 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-15S
Proj4js.defs["EPSG:32416"] = "+proj=utm +zone=16 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-16N
Proj4js.defs["EPSG:32516"] = "+proj=utm +zone=16 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-16S
Proj4js.defs["EPSG:32417"] = "+proj=utm +zone=17 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-17N
Proj4js.defs["EPSG:32517"] = "+proj=utm +zone=17 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-17S
Proj4js.defs["EPSG:32418"] = "+proj=utm +zone=18 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-18N
Proj4js.defs["EPSG:32518"] = "+proj=utm +zone=18 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-18S
Proj4js.defs["EPSG:32419"] = "+proj=utm +zone=19 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-19N
Proj4js.defs["EPSG:32519"] = "+proj=utm +zone=19 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-19S
Proj4js.defs["EPSG:32401"] = "+proj=utm +zone=1 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-1N
Proj4js.defs["EPSG:32501"] = "+proj=utm +zone=1 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-1S
Proj4js.defs["EPSG:32420"] = "+proj=utm +zone=20 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-20N
Proj4js.defs["EPSG:32520"] = "+proj=utm +zone=20 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-20S
Proj4js.defs["EPSG:32421"] = "+proj=utm +zone=21 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-21N
Proj4js.defs["EPSG:32521"] = "+proj=utm +zone=21 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-21S
Proj4js.defs["EPSG:32422"] = "+proj=utm +zone=22 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-22N
Proj4js.defs["EPSG:32522"] = "+proj=utm +zone=22 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-22S
Proj4js.defs["EPSG:32423"] = "+proj=utm +zone=23 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-23N
Proj4js.defs["EPSG:32523"] = "+proj=utm +zone=23 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-23S
Proj4js.defs["EPSG:32424"] = "+proj=utm +zone=24 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-24N
Proj4js.defs["EPSG:32524"] = "+proj=utm +zone=24 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-24S
Proj4js.defs["EPSG:32425"] = "+proj=utm +zone=25 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-25N
Proj4js.defs["EPSG:32525"] = "+proj=utm +zone=25 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-25S
Proj4js.defs["EPSG:32426"] = "+proj=utm +zone=26 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-26N
Proj4js.defs["EPSG:32526"] = "+proj=utm +zone=26 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-26S
Proj4js.defs["EPSG:32427"] = "+proj=utm +zone=27 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-27N
Proj4js.defs["EPSG:32527"] = "+proj=utm +zone=27 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-27S
Proj4js.defs["EPSG:32428"] = "+proj=utm +zone=28 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-28N
Proj4js.defs["EPSG:32528"] = "+proj=utm +zone=28 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-28S
Proj4js.defs["EPSG:32429"] = "+proj=utm +zone=29 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-29N
Proj4js.defs["EPSG:32529"] = "+proj=utm +zone=29 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-29S
Proj4js.defs["EPSG:32402"] = "+proj=utm +zone=2 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-2N
Proj4js.defs["EPSG:32502"] = "+proj=utm +zone=2 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-2S
Proj4js.defs["EPSG:32430"] = "+proj=utm +zone=30 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-30N
Proj4js.defs["EPSG:32530"] = "+proj=utm +zone=30 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-30S
Proj4js.defs["EPSG:32431"] = "+proj=utm +zone=31 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-31N
Proj4js.defs["EPSG:32531"] = "+proj=utm +zone=31 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-31S
Proj4js.defs["EPSG:32432"] = "+proj=utm +zone=32 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-32N
Proj4js.defs["EPSG:32532"] = "+proj=utm +zone=32 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-32S
Proj4js.defs["EPSG:32433"] = "+proj=utm +zone=33 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-33N
Proj4js.defs["EPSG:32533"] = "+proj=utm +zone=33 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-33S
Proj4js.defs["EPSG:32434"] = "+proj=utm +zone=34 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-34N
Proj4js.defs["EPSG:32534"] = "+proj=utm +zone=34 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-34S
Proj4js.defs["EPSG:32435"] = "+proj=utm +zone=35 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-35N
Proj4js.defs["EPSG:32535"] = "+proj=utm +zone=35 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-35S
Proj4js.defs["EPSG:32536"] = "+proj=utm +zone=36 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-36S
Proj4js.defs["EPSG:32437"] = "+proj=utm +zone=37 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-37N
Proj4js.defs["EPSG:32537"] = "+proj=utm +zone=37 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-37S
Proj4js.defs["EPSG:32438"] = "+proj=utm +zone=38 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-38N
Proj4js.defs["EPSG:32538"] = "+proj=utm +zone=38 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-38S
Proj4js.defs["EPSG:32439"] = "+proj=utm +zone=39 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-39N
Proj4js.defs["EPSG:32539"] = "+proj=utm +zone=39 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-39S
Proj4js.defs["EPSG:32403"] = "+proj=utm +zone=3 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-3N
Proj4js.defs["EPSG:32503"] = "+proj=utm +zone=3 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-3S
Proj4js.defs["EPSG:32440"] = "+proj=utm +zone=40 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-40N
Proj4js.defs["EPSG:32540"] = "+proj=utm +zone=40 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-40S
Proj4js.defs["EPSG:32441"] = "+proj=utm +zone=41 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-41N
Proj4js.defs["EPSG:32541"] = "+proj=utm +zone=41 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-41S
Proj4js.defs["EPSG:32442"] = "+proj=utm +zone=42 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-42N
Proj4js.defs["EPSG:32542"] = "+proj=utm +zone=42 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-42S
Proj4js.defs["EPSG:32443"] = "+proj=utm +zone=43 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-43N
Proj4js.defs["EPSG:32543"] = "+proj=utm +zone=43 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-43S
Proj4js.defs["EPSG:32444"] = "+proj=utm +zone=44 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-44N
Proj4js.defs["EPSG:32544"] = "+proj=utm +zone=44 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-44S
Proj4js.defs["EPSG:32445"] = "+proj=utm +zone=45 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-45N
Proj4js.defs["EPSG:32545"] = "+proj=utm +zone=45 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-45S
Proj4js.defs["EPSG:32446"] = "+proj=utm +zone=46 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-46N
Proj4js.defs["EPSG:32546"] = "+proj=utm +zone=46 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-46S
Proj4js.defs["EPSG:32447"] = "+proj=utm +zone=47 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-47N
Proj4js.defs["EPSG:32547"] = "+proj=utm +zone=47 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-47S
Proj4js.defs["EPSG:32448"] = "+proj=utm +zone=48 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-48N
Proj4js.defs["EPSG:32548"] = "+proj=utm +zone=48 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-48S
Proj4js.defs["EPSG:32449"] = "+proj=utm +zone=49 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-49N
Proj4js.defs["EPSG:32549"] = "+proj=utm +zone=49 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-49S
Proj4js.defs["EPSG:32404"] = "+proj=utm +zone=4 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-4N
Proj4js.defs["EPSG:32504"] = "+proj=utm +zone=4 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-4S
Proj4js.defs["EPSG:32450"] = "+proj=utm +zone=50 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-50N
Proj4js.defs["EPSG:32550"] = "+proj=utm +zone=50 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-50S
Proj4js.defs["EPSG:32451"] = "+proj=utm +zone=51 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-51N
Proj4js.defs["EPSG:32551"] = "+proj=utm +zone=51 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-51S
Proj4js.defs["EPSG:32452"] = "+proj=utm +zone=52 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-52N
Proj4js.defs["EPSG:32552"] = "+proj=utm +zone=52 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-52S
Proj4js.defs["EPSG:32453"] = "+proj=utm +zone=53 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-53N
Proj4js.defs["EPSG:32553"] = "+proj=utm +zone=53 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-53S
Proj4js.defs["EPSG:32454"] = "+proj=utm +zone=54 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-54N
Proj4js.defs["EPSG:32554"] = "+proj=utm +zone=54 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-54S
Proj4js.defs["EPSG:32455"] = "+proj=utm +zone=55 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-55N
Proj4js.defs["EPSG:32555"] = "+proj=utm +zone=55 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-55S
Proj4js.defs["EPSG:32456"] = "+proj=utm +zone=56 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-56N
Proj4js.defs["EPSG:32556"] = "+proj=utm +zone=56 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-56S
Proj4js.defs["EPSG:32457"] = "+proj=utm +zone=57 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-57N
Proj4js.defs["EPSG:32557"] = "+proj=utm +zone=57 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-57S
Proj4js.defs["EPSG:32458"] = "+proj=utm +zone=58 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-58N
Proj4js.defs["EPSG:32558"] = "+proj=utm +zone=58 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-58S
Proj4js.defs["EPSG:32459"] = "+proj=utm +zone=59 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-59N
Proj4js.defs["EPSG:32559"] = "+proj=utm +zone=59 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-59S
Proj4js.defs["EPSG:32405"] = "+proj=utm +zone=5 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-5N
Proj4js.defs["EPSG:32505"] = "+proj=utm +zone=5 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-5S
Proj4js.defs["EPSG:32460"] = "+proj=utm +zone=60 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-60N
Proj4js.defs["EPSG:32560"] = "+proj=utm +zone=60 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-60S
Proj4js.defs["EPSG:32406"] = "+proj=utm +zone=6 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-6N
Proj4js.defs["EPSG:32506"] = "+proj=utm +zone=6 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-6S
Proj4js.defs["EPSG:32407"] = "+proj=utm +zone=7 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-7N
Proj4js.defs["EPSG:32507"] = "+proj=utm +zone=7 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-7S
Proj4js.defs["EPSG:32408"] = "+proj=utm +zone=8 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-8N
Proj4js.defs["EPSG:32508"] = "+proj=utm +zone=8 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-8S
Proj4js.defs["EPSG:32409"] = "+proj=utm +zone=9 +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-9N
Proj4js.defs["EPSG:32509"] = "+proj=utm +zone=9 +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs "; //Mentor: WGS72be/a.UTM-9S
Proj4js.defs["EPSG:4324"] = "+proj=longlat +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +no_defs "; //Mentor: WGS72-TBE/a.LL
Proj4js.defs["EPSG:32601"] = "+proj=utm +zone=1 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-1N
Proj4js.defs["EPSG:32701"] = "+proj=utm +zone=1 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-1S
Proj4js.defs["EPSG:32602"] = "+proj=utm +zone=2 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-2N
Proj4js.defs["EPSG:32702"] = "+proj=utm +zone=2 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-2S
Proj4js.defs["EPSG:32603"] = "+proj=utm +zone=3 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-3N
Proj4js.defs["EPSG:32703"] = "+proj=utm +zone=3 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-3S
Proj4js.defs["EPSG:32604"] = "+proj=utm +zone=4 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-4N
Proj4js.defs["EPSG:32704"] = "+proj=utm +zone=4 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-4S
Proj4js.defs["EPSG:32605"] = "+proj=utm +zone=5 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-5N
Proj4js.defs["EPSG:32705"] = "+proj=utm +zone=5 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-5S
Proj4js.defs["EPSG:32606"] = "+proj=utm +zone=6 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-6N
Proj4js.defs["EPSG:32706"] = "+proj=utm +zone=6 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-6S
Proj4js.defs["EPSG:32607"] = "+proj=utm +zone=7 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-7N
Proj4js.defs["EPSG:32707"] = "+proj=utm +zone=7 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-7S
Proj4js.defs["EPSG:32608"] = "+proj=utm +zone=8 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-8N
Proj4js.defs["EPSG:32708"] = "+proj=utm +zone=8 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-8S
Proj4js.defs["EPSG:32609"] = "+proj=utm +zone=9 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-9N
Proj4js.defs["EPSG:32709"] = "+proj=utm +zone=9 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-9S
Proj4js.defs["EPSG:32610"] = "+proj=utm +zone=10 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-10N
Proj4js.defs["EPSG:32710"] = "+proj=utm +zone=10 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-10S
Proj4js.defs["EPSG:32611"] = "+proj=utm +zone=11 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-11N
Proj4js.defs["EPSG:32711"] = "+proj=utm +zone=11 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-11S
Proj4js.defs["EPSG:32612"] = "+proj=utm +zone=12 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-12N
Proj4js.defs["EPSG:32712"] = "+proj=utm +zone=12 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-12S
Proj4js.defs["EPSG:32613"] = "+proj=utm +zone=13 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-13N
Proj4js.defs["EPSG:32713"] = "+proj=utm +zone=13 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-13S
Proj4js.defs["EPSG:32614"] = "+proj=utm +zone=14 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-14N
Proj4js.defs["EPSG:32714"] = "+proj=utm +zone=14 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-14S
Proj4js.defs["EPSG:32615"] = "+proj=utm +zone=15 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-15N
Proj4js.defs["EPSG:32715"] = "+proj=utm +zone=15 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-15S
Proj4js.defs["EPSG:32616"] = "+proj=utm +zone=16 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-16N
Proj4js.defs["EPSG:32716"] = "+proj=utm +zone=16 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-16S
Proj4js.defs["EPSG:32617"] = "+proj=utm +zone=17 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-17N
Proj4js.defs["EPSG:32717"] = "+proj=utm +zone=17 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-17S
Proj4js.defs["EPSG:32618"] = "+proj=utm +zone=18 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-18N
Proj4js.defs["EPSG:32718"] = "+proj=utm +zone=18 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-18S
Proj4js.defs["EPSG:32619"] = "+proj=utm +zone=19 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-19N
Proj4js.defs["EPSG:32719"] = "+proj=utm +zone=19 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-19S
Proj4js.defs["EPSG:32620"] = "+proj=utm +zone=20 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-20N
Proj4js.defs["EPSG:32720"] = "+proj=utm +zone=20 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-20S
Proj4js.defs["EPSG:32621"] = "+proj=utm +zone=21 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-21N
Proj4js.defs["EPSG:32721"] = "+proj=utm +zone=21 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-21S
Proj4js.defs["EPSG:32622"] = "+proj=utm +zone=22 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-22N
Proj4js.defs["EPSG:32722"] = "+proj=utm +zone=22 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-22S
Proj4js.defs["EPSG:32623"] = "+proj=utm +zone=23 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-23N
Proj4js.defs["EPSG:32723"] = "+proj=utm +zone=23 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-23S
Proj4js.defs["EPSG:32624"] = "+proj=utm +zone=24 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-24N
Proj4js.defs["EPSG:32724"] = "+proj=utm +zone=24 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-24S
Proj4js.defs["EPSG:32625"] = "+proj=utm +zone=25 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-25N
Proj4js.defs["EPSG:32725"] = "+proj=utm +zone=25 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-25S
Proj4js.defs["EPSG:32626"] = "+proj=utm +zone=26 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-26N
Proj4js.defs["EPSG:32726"] = "+proj=utm +zone=26 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-26S
Proj4js.defs["EPSG:32627"] = "+proj=utm +zone=27 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-27N
Proj4js.defs["EPSG:32727"] = "+proj=utm +zone=27 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-27S
Proj4js.defs["EPSG:32628"] = "+proj=utm +zone=28 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-28N
Proj4js.defs["EPSG:32728"] = "+proj=utm +zone=28 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-28S
Proj4js.defs["EPSG:32629"] = "+proj=utm +zone=29 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-29N
Proj4js.defs["EPSG:32729"] = "+proj=utm +zone=29 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-29S
Proj4js.defs["EPSG:32630"] = "+proj=utm +zone=30 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-30N
Proj4js.defs["EPSG:32730"] = "+proj=utm +zone=30 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-30S
Proj4js.defs["EPSG:32631"] = "+proj=utm +zone=31 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-31N
Proj4js.defs["EPSG:32731"] = "+proj=utm +zone=31 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-31S
Proj4js.defs["EPSG:32632"] = "+proj=utm +zone=32 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-32N
Proj4js.defs["EPSG:32732"] = "+proj=utm +zone=32 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-32S
Proj4js.defs["EPSG:32633"] = "+proj=utm +zone=33 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-33N
Proj4js.defs["EPSG:32733"] = "+proj=utm +zone=33 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-33S
Proj4js.defs["EPSG:32634"] = "+proj=utm +zone=34 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-34N
Proj4js.defs["EPSG:32734"] = "+proj=utm +zone=34 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-34S
Proj4js.defs["EPSG:32635"] = "+proj=utm +zone=35 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-35N
Proj4js.defs["EPSG:32735"] = "+proj=utm +zone=35 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-35S
Proj4js.defs["EPSG:32636"] = "+proj=utm +zone=36 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-36N
Proj4js.defs["EPSG:32736"] = "+proj=utm +zone=36 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-36S
Proj4js.defs["EPSG:32637"] = "+proj=utm +zone=37 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-37N
Proj4js.defs["EPSG:32737"] = "+proj=utm +zone=37 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-37S
Proj4js.defs["EPSG:32638"] = "+proj=utm +zone=38 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-38N
Proj4js.defs["EPSG:32738"] = "+proj=utm +zone=38 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-38S
Proj4js.defs["EPSG:32639"] = "+proj=utm +zone=39 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-39N
Proj4js.defs["EPSG:32739"] = "+proj=utm +zone=39 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-39S
Proj4js.defs["EPSG:32640"] = "+proj=utm +zone=40 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-40N
Proj4js.defs["EPSG:32740"] = "+proj=utm +zone=40 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-40S
Proj4js.defs["EPSG:32641"] = "+proj=utm +zone=41 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-41N
Proj4js.defs["EPSG:32741"] = "+proj=utm +zone=41 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-41S
Proj4js.defs["EPSG:32642"] = "+proj=utm +zone=42 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-42N
Proj4js.defs["EPSG:32742"] = "+proj=utm +zone=42 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-42S
Proj4js.defs["EPSG:32643"] = "+proj=utm +zone=43 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-43N
Proj4js.defs["EPSG:32743"] = "+proj=utm +zone=43 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-43S
Proj4js.defs["EPSG:32644"] = "+proj=utm +zone=44 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-44N
Proj4js.defs["EPSG:32744"] = "+proj=utm +zone=44 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-44S
Proj4js.defs["EPSG:32645"] = "+proj=utm +zone=45 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-45N
Proj4js.defs["EPSG:32745"] = "+proj=utm +zone=45 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-45S
Proj4js.defs["EPSG:32646"] = "+proj=utm +zone=46 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-46N
Proj4js.defs["EPSG:32746"] = "+proj=utm +zone=46 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-46S
Proj4js.defs["EPSG:32647"] = "+proj=utm +zone=47 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-47N
Proj4js.defs["EPSG:32747"] = "+proj=utm +zone=47 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-47S
Proj4js.defs["EPSG:32648"] = "+proj=utm +zone=48 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-48N
Proj4js.defs["EPSG:32748"] = "+proj=utm +zone=48 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-48S
Proj4js.defs["EPSG:32649"] = "+proj=utm +zone=49 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-49N
Proj4js.defs["EPSG:32749"] = "+proj=utm +zone=49 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-49S
Proj4js.defs["EPSG:32650"] = "+proj=utm +zone=50 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-50N
Proj4js.defs["EPSG:32750"] = "+proj=utm +zone=50 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-50S
Proj4js.defs["EPSG:32651"] = "+proj=utm +zone=51 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-51N
Proj4js.defs["EPSG:32751"] = "+proj=utm +zone=51 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-51S
Proj4js.defs["EPSG:32652"] = "+proj=utm +zone=52 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-52N
Proj4js.defs["EPSG:32752"] = "+proj=utm +zone=52 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-52S
Proj4js.defs["EPSG:32653"] = "+proj=utm +zone=53 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-53N
Proj4js.defs["EPSG:32753"] = "+proj=utm +zone=53 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-53S
Proj4js.defs["EPSG:32654"] = "+proj=utm +zone=54 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-54N
Proj4js.defs["EPSG:32754"] = "+proj=utm +zone=54 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-54S
Proj4js.defs["EPSG:32655"] = "+proj=utm +zone=55 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-55N
Proj4js.defs["EPSG:32755"] = "+proj=utm +zone=55 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-55S
Proj4js.defs["EPSG:32656"] = "+proj=utm +zone=56 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-56N
Proj4js.defs["EPSG:32756"] = "+proj=utm +zone=56 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-56S
Proj4js.defs["EPSG:32657"] = "+proj=utm +zone=57 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-57N
Proj4js.defs["EPSG:32757"] = "+proj=utm +zone=57 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-57S
Proj4js.defs["EPSG:32658"] = "+proj=utm +zone=58 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-58N
Proj4js.defs["EPSG:32758"] = "+proj=utm +zone=58 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-58S
Proj4js.defs["EPSG:32659"] = "+proj=utm +zone=59 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-59N
Proj4js.defs["EPSG:32759"] = "+proj=utm +zone=59 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-59S
Proj4js.defs["EPSG:32660"] = "+proj=utm +zone=60 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-60N
Proj4js.defs["EPSG:32760"] = "+proj=utm +zone=60 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UTM84-60S
Proj4js.defs["EPSG:26703"] = "+proj=utm +zone=3 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-3
Proj4js.defs["EPSG:26704"] = "+proj=utm +zone=4 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-4
Proj4js.defs["EPSG:26705"] = "+proj=utm +zone=5 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-5
Proj4js.defs["EPSG:26706"] = "+proj=utm +zone=6 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-6
Proj4js.defs["EPSG:26707"] = "+proj=utm +zone=7 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-7
Proj4js.defs["EPSG:26708"] = "+proj=utm +zone=8 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-8
Proj4js.defs["EPSG:26709"] = "+proj=utm +zone=9 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-9
Proj4js.defs["EPSG:26710"] = "+proj=utm +zone=10 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-10
Proj4js.defs["EPSG:26711"] = "+proj=utm +zone=11 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-11
Proj4js.defs["EPSG:26712"] = "+proj=utm +zone=12 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-12
Proj4js.defs["EPSG:26713"] = "+proj=utm +zone=13 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-13
Proj4js.defs["EPSG:26714"] = "+proj=utm +zone=14 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-14
Proj4js.defs["EPSG:26715"] = "+proj=utm +zone=15 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-15
Proj4js.defs["EPSG:26716"] = "+proj=utm +zone=16 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-16
Proj4js.defs["EPSG:26717"] = "+proj=utm +zone=17 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-17
Proj4js.defs["EPSG:26718"] = "+proj=utm +zone=18 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-18
Proj4js.defs["EPSG:26719"] = "+proj=utm +zone=19 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-19
Proj4js.defs["EPSG:26720"] = "+proj=utm +zone=20 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-20
Proj4js.defs["EPSG:26721"] = "+proj=utm +zone=21 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-21
Proj4js.defs["EPSG:26722"] = "+proj=utm +zone=22 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-22
Proj4js.defs["EPSG:32064"] = "+proj=tmerc +lat_0=0 +lon_0=-99 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-14N.ft
Proj4js.defs["EPSG:32065"] = "+proj=tmerc +lat_0=0 +lon_0=-93 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-15N.ft
Proj4js.defs["EPSG:32066"] = "+proj=tmerc +lat_0=0 +lon_0=-87 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-16N.ft
Proj4js.defs["EPSG:32067"] = "+proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-17N.ft
Proj4js.defs["EPSG:32081"] = "+proj=tmerc +lat_0=0 +lon_0=-53 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.MTM-1
Proj4js.defs["EPSG:32082"] = "+proj=tmerc +lat_0=0 +lon_0=-56 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: NAD27.MTM-2
Proj4js.defs["EPSG:26701"] = "+proj=utm +zone=1 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-1
Proj4js.defs["EPSG:26702"] = "+proj=utm +zone=2 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-2
Proj4js.defs["EPSG:26903"] = "+proj=utm +zone=3 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-3
Proj4js.defs["EPSG:26904"] = "+proj=utm +zone=4 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-4
Proj4js.defs["EPSG:26905"] = "+proj=utm +zone=5 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-5
Proj4js.defs["EPSG:26906"] = "+proj=utm +zone=6 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-6
Proj4js.defs["EPSG:26907"] = "+proj=utm +zone=7 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-7
Proj4js.defs["EPSG:26908"] = "+proj=utm +zone=8 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-8
Proj4js.defs["EPSG:26909"] = "+proj=utm +zone=9 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-9
Proj4js.defs["EPSG:26910"] = "+proj=utm +zone=10 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-10
Proj4js.defs["EPSG:26911"] = "+proj=utm +zone=11 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-11
Proj4js.defs["EPSG:26912"] = "+proj=utm +zone=12 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-12
Proj4js.defs["EPSG:26913"] = "+proj=utm +zone=13 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-13
Proj4js.defs["EPSG:26914"] = "+proj=utm +zone=14 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-14
Proj4js.defs["EPSG:26915"] = "+proj=utm +zone=15 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-15
Proj4js.defs["EPSG:26916"] = "+proj=utm +zone=16 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-16
Proj4js.defs["EPSG:26917"] = "+proj=utm +zone=17 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-17
Proj4js.defs["EPSG:26918"] = "+proj=utm +zone=18 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-18
Proj4js.defs["EPSG:26919"] = "+proj=utm +zone=19 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-19
Proj4js.defs["EPSG:26920"] = "+proj=utm +zone=20 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-20
Proj4js.defs["EPSG:26921"] = "+proj=utm +zone=21 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-21
Proj4js.defs["EPSG:26922"] = "+proj=utm +zone=22 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-22
Proj4js.defs["EPSG:32180"] = "+proj=tmerc +lat_0=0 +lon_0=-55.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.SCoPQ-2
Proj4js.defs["EPSG:26923"] = "+proj=utm +zone=23 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: NAD83.UTM-23N
Proj4js.defs["EPSG:2944"] = "+proj=tmerc +lat_0=0 +lon_0=-55.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.SCoPQ-2
Proj4js.defs["EPSG:26901"] = "+proj=utm +zone=1 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-1
Proj4js.defs["EPSG:26902"] = "+proj=utm +zone=2 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: UTM83-2
Proj4js.defs["EPSG:2945"] = "+proj=tmerc +lat_0=0 +lon_0=-58.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-3
Proj4js.defs["EPSG:2946"] = "+proj=tmerc +lat_0=0 +lon_0=-61.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-4
Proj4js.defs["EPSG:2947"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-5
Proj4js.defs["EPSG:2948"] = "+proj=tmerc +lat_0=0 +lon_0=-67.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-6
Proj4js.defs["EPSG:2949"] = "+proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-7
Proj4js.defs["EPSG:2950"] = "+proj=tmerc +lat_0=0 +lon_0=-73.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-8
Proj4js.defs["EPSG:2951"] = "+proj=tmerc +lat_0=0 +lon_0=-76.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-9
Proj4js.defs["EPSG:2952"] = "+proj=tmerc +lat_0=0 +lon_0=-79.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.MTM-10
Proj4js.defs["EPSG:2955"] = "+proj=utm +zone=11 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-11N
Proj4js.defs["EPSG:2956"] = "+proj=utm +zone=12 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-12N
Proj4js.defs["EPSG:2957"] = "+proj=utm +zone=13 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-13N
Proj4js.defs["EPSG:2958"] = "+proj=utm +zone=17 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-17N
Proj4js.defs["EPSG:2959"] = "+proj=utm +zone=18 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-18N
Proj4js.defs["EPSG:2960"] = "+proj=utm +zone=19 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-19N
Proj4js.defs["EPSG:2961"] = "+proj=utm +zone=20 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-20N
Proj4js.defs["EPSG:2962"] = "+proj=utm +zone=21 +ellps=GRS80 +units=m +no_defs "; //Mentor: CSRS.UTM-21N
Proj4js.defs["EPSG:32164"] = "+proj=tmerc +lat_0=0 +lon_0=-99 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-14N
Proj4js.defs["EPSG:32165"] = "+proj=tmerc +lat_0=0 +lon_0=-93 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-15NF
Proj4js.defs["EPSG:32166"] = "+proj=tmerc +lat_0=0 +lon_0=-87 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-16NF
Proj4js.defs["EPSG:32167"] = "+proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD83.BLM-17NF
Proj4js.defs["EPSG:3740"] = "+proj=utm +zone=10 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/10.UTM-10
Proj4js.defs["EPSG:3741"] = "+proj=utm +zone=11 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/11.UTM-11
Proj4js.defs["EPSG:3742"] = "+proj=utm +zone=12 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/12.UTM-12
Proj4js.defs["EPSG:3743"] = "+proj=utm +zone=13 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/13.UTM-13
Proj4js.defs["EPSG:3744"] = "+proj=utm +zone=14 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/14.UTM-14
Proj4js.defs["EPSG:3745"] = "+proj=utm +zone=15 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/15.UTM-15
Proj4js.defs["EPSG:3746"] = "+proj=utm +zone=16 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/16.UTM-16
Proj4js.defs["EPSG:3747"] = "+proj=utm +zone=17 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/17.UTM-17
Proj4js.defs["EPSG:3748"] = "+proj=utm +zone=18 +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/18.UTM-18
Proj4js.defs["EPSG:2195"] = "+proj=utm +zone=2 +south +ellps=GRS80 +units=m +no_defs "; //Mentor: HARN/02.UTM-2S
Proj4js.defs["EPSG:3395"] = "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WORLD-MERCATOR
Proj4js.defs["EPSG:32661"] = "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.UPSNorth
Proj4js.defs["EPSG:32761"] = "+proj=stere +lat_0=-90 +lat_ts=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: WGS84.UPSSouth
Proj4js.defs["EPSG:4248"] = "+proj=longlat +ellps=intl +no_defs "; //Mentor: LLPSAD56
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: UTM-SGB
Proj4js.defs["EPSG:29903"] = "+proj=tmerc +lat_0=53.5 +lon_0=-8 +k=1.000035 +x_0=200000 +y_0=250000 +a=6377340.189 +b=6356034.447938534 +units=m +no_defs "; //Mentor: ING-75
Proj4js.defs["EPSG:4291"] = "+proj=longlat +ellps=GRS67 +no_defs "; //Mentor: SAD69.LL
Proj4js.defs["EPSG:2982"] = "+proj=utm +zone=58 +south +ellps=intl +units=m +no_defs "; //Mentor: IGN72GrandTerre.UTM-58S
Proj4js.defs["EPSG:4684"] = "+proj=longlat +ellps=intl +towgs84=-133,-321,50,0,0,0,0 +no_defs "; //Mentor: Gandajika.LL
Proj4js.defs["EPSG:4635"] = "+proj=longlat +ellps=intl +towgs84=-122.383,-188.696,103.344,3.5107,-4.9668,-5.7047,4.4798 +no_defs "; //Mentor: ST87Ouvea.LL
Proj4js.defs["EPSG:4645"] = "+proj=longlat +ellps=intl +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: Caledonie91.LL
Proj4js.defs["EPSG:4731"] = "+proj=longlat +ellps=clrk80 +towgs84=51,391,-36,0,0,0,0 +no_defs "; //Mentor: VITI.LL
Proj4js.defs["EPSG:28462"] = "+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK-2N
Proj4js.defs["EPSG:28463"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Pulkovo42.GK-3N
Proj4js.defs["EPSG:32074"] = "+proj=tmerc +lat_0=0 +lon_0=-99 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-14N.Ift
Proj4js.defs["EPSG:32075"] = "+proj=tmerc +lat_0=0 +lon_0=-93 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-15N.Ift
Proj4js.defs["EPSG:32076"] = "+proj=tmerc +lat_0=0 +lon_0=-87 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-16N.Ift
Proj4js.defs["EPSG:32077"] = "+proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=clrk66 +datum=NAD27 +to_meter=0.3048006096012192 +no_defs "; //Mentor: NAD27.BLM-17N.Ift
Proj4js.defs["EPSG:4659"] = "+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs "; //Mentor: ISN93.LL
Proj4js.defs["EPSG:22191"] = "+proj=tmerc +lat_0=-90 +lon_0=-72 +k=1 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: FAJA-1
Proj4js.defs["EPSG:22192"] = "+proj=tmerc +lat_0=-90 +lon_0=-69 +k=1 +x_0=2500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: FAJA-2
Proj4js.defs["EPSG:22193"] = "+proj=tmerc +lat_0=-90 +lon_0=-66 +k=1 +x_0=3500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: FAJA-3
Proj4js.defs["EPSG:22194"] = "+proj=tmerc +lat_0=-90 +lon_0=-63 +k=1 +x_0=4500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: FAJA-4
Proj4js.defs["EPSG:22195"] = "+proj=tmerc +lat_0=-90 +lon_0=-60 +k=1 +x_0=5500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: FAJA-5
Proj4js.defs["EPSG:22196"] = "+proj=tmerc +lat_0=-90 +lon_0=-57 +k=1 +x_0=6500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: FAJA-6
Proj4js.defs["EPSG:22197"] = "+proj=tmerc +lat_0=-90 +lon_0=-54 +k=1 +x_0=7500000 +y_0=0 +ellps=intl +units=m +no_defs "; //Mentor: FAJA-7
Proj4js.defs["EPSG:2935"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=41.53333333333333 +k=1 +x_0=1300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CS63-A1
Proj4js.defs["EPSG:2936"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=44.53333333333333 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CS63-A2
Proj4js.defs["EPSG:2937"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=47.53333333333333 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CS63-A3
Proj4js.defs["EPSG:2938"] = "+proj=tmerc +lat_0=0.1166666666666667 +lon_0=50.53333333333333 +k=1 +x_0=4300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CS63-A4
Proj4js.defs["EPSG:20499"] = "+proj=utm +zone=39 +ellps=intl +units=m +no_defs "; //Mentor: BAH-SG
Proj4js.defs["EPSG:20499"] = "+proj=utm +zone=39 +ellps=intl +units=m +no_defs "; //Mentor: AinElAbd.UTM-39N
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-19S
Proj4js.defs["EPSG:29190"] = "+proj=utm +zone=20 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-20S
Proj4js.defs["EPSG:20934"] = "+proj=utm +zone=34 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: AFR50-34S/01
Proj4js.defs["EPSG:20935"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: AFR50-35S/01
Proj4js.defs["EPSG:29189"] = "+proj=utm +zone=19 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SGB-19
Proj4js.defs["EPSG:29190"] = "+proj=utm +zone=20 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SGB-20
Proj4js.defs["EPSG:29191"] = "+proj=utm +zone=21 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SGB-21
Proj4js.defs["EPSG:29192"] = "+proj=utm +zone=22 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SGB-22
Proj4js.defs["EPSG:29193"] = "+proj=utm +zone=23 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SGB-23
Proj4js.defs["EPSG:29194"] = "+proj=utm +zone=24 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SGB-24
Proj4js.defs["EPSG:29195"] = "+proj=utm +zone=25 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SGB-25
Proj4js.defs["EPSG:22523"] = "+proj=utm +zone=23 +south +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +units=m +no_defs "; //Mentor: BRAC-NE1
Proj4js.defs["EPSG:22524"] = "+proj=utm +zone=24 +south +ellps=intl +towgs84=-206,172,-6,0,0,0,0 +units=m +no_defs "; //Mentor: BRAC-NE2
Proj4js.defs["EPSG:32081"] = "+proj=tmerc +lat_0=0 +lon_0=-53 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: CANQ27-M1
Proj4js.defs["EPSG:32082"] = "+proj=tmerc +lat_0=0 +lon_0=-56 +k=0.9999 +x_0=304800 +y_0=0 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: CANQ27-M2N
Proj4js.defs["EPSG:32182"] = "+proj=tmerc +lat_0=0 +lon_0=-56 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-M2N
Proj4js.defs["EPSG:2952"] = "+proj=tmerc +lat_0=0 +lon_0=-79.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-10
Proj4js.defs["EPSG:2945"] = "+proj=tmerc +lat_0=0 +lon_0=-58.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-3
Proj4js.defs["EPSG:2946"] = "+proj=tmerc +lat_0=0 +lon_0=-61.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-4
Proj4js.defs["EPSG:2948"] = "+proj=tmerc +lat_0=0 +lon_0=-67.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-6
Proj4js.defs["EPSG:2949"] = "+proj=tmerc +lat_0=0 +lon_0=-70.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-7
Proj4js.defs["EPSG:2950"] = "+proj=tmerc +lat_0=0 +lon_0=-73.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-8
Proj4js.defs["EPSG:2951"] = "+proj=tmerc +lat_0=0 +lon_0=-76.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-9
Proj4js.defs["EPSG:2944"] = "+proj=tmerc +lat_0=0 +lon_0=-55.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.SCoPQ-2
Proj4js.defs["EPSG:2153"] = "+proj=utm +zone=11 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NAD83/98.UTM-11N
Proj4js.defs["EPSG:2152"] = "+proj=utm +zone=12 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NAD83/98.UTM-12N
Proj4js.defs["EPSG:2151"] = "+proj=utm +zone=13 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NAD83/98.UTM-13N
Proj4js.defs["EPSG:2150"] = "+proj=utm +zone=17 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NAD83/98.UTM-17N
Proj4js.defs["EPSG:2149"] = "+proj=utm +zone=18 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NAD83/98.UTM-18N
Proj4js.defs["EPSG:2960"] = "+proj=utm +zone=19 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.UTM-19N
Proj4js.defs["EPSG:2961"] = "+proj=utm +zone=20 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.UTM-20N
Proj4js.defs["EPSG:2148"] = "+proj=utm +zone=21 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NAD83/98.UTM-21N
Proj4js.defs["EPSG:2219"] = "+proj=utm +zone=19 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: CANNB-19N-ATS
Proj4js.defs["EPSG:2220"] = "+proj=utm +zone=20 +a=6378135 +b=6356750.304921594 +units=m +no_defs "; //Mentor: CANNB-20N-ATS
Proj4js.defs["EPSG:32198"] = "+proj=lcc +lat_1=60 +lat_2=46 +lat_0=44 +lon_0=-68.5 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: CANQ-LCC-83
Proj4js.defs["EPSG:2947"] = "+proj=tmerc +lat_0=0 +lon_0=-64.5 +k=0.9999 +x_0=304800 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: NAD83/98.MTM-5
Proj4js.defs["EPSG:29187"] = "+proj=utm +zone=17 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-17S
Proj4js.defs["EPSG:29188"] = "+proj=utm +zone=18 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-18S
Proj4js.defs["EPSG:21453"] = "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-13N
Proj4js.defs["EPSG:21454"] = "+proj=tmerc +lat_0=0 +lon_0=81 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-14N
Proj4js.defs["EPSG:21455"] = "+proj=tmerc +lat_0=0 +lon_0=87 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-15N
Proj4js.defs["EPSG:21456"] = "+proj=tmerc +lat_0=0 +lon_0=93 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-16N
Proj4js.defs["EPSG:21457"] = "+proj=tmerc +lat_0=0 +lon_0=99 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-17N
Proj4js.defs["EPSG:21458"] = "+proj=tmerc +lat_0=0 +lon_0=105 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-18N
Proj4js.defs["EPSG:21459"] = "+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-19N
Proj4js.defs["EPSG:21460"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-20N
Proj4js.defs["EPSG:21461"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-21N
Proj4js.defs["EPSG:21462"] = "+proj=tmerc +lat_0=0 +lon_0=129 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-22N
Proj4js.defs["EPSG:21463"] = "+proj=tmerc +lat_0=0 +lon_0=135 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: Beijing1954.GK-23N
Proj4js.defs["EPSG:21896"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-77.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: COL-O-B
Proj4js.defs["EPSG:21897"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-74.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: COL-B-B
Proj4js.defs["EPSG:21898"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-71.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: COL-EC-B
Proj4js.defs["EPSG:21899"] = "+proj=tmerc +lat_0=4.599047222222222 +lon_0=-68.08091666666667 +k=1 +x_0=1000000 +y_0=1000000 +ellps=intl +towgs84=307,304,-318,0,0,0,0 +units=m +no_defs "; //Mentor: COL-EE-B
Proj4js.defs["EPSG:29168"] = "+proj=utm +zone=18 +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-18N
Proj4js.defs["EPSG:29902"] = "+proj=tmerc +lat_0=53.5 +lon_0=-8 +k=1.000035 +x_0=200000 +y_0=250000 +a=6377340.189 +b=6356034.447938534 +units=m +no_defs "; //Mentor: TM65g.IrishNationalGrid
Proj4js.defs["EPSG:26237"] = "+proj=utm +zone=37 +ellps=bessel +towgs84=639,405,60,0,0,0,0 +units=m +no_defs "; //Mentor: ERITREA
Proj4js.defs["EPSG:20137"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: ETSU_W
Proj4js.defs["EPSG:20138"] = "+proj=utm +zone=38 +ellps=clrk80 +units=m +no_defs "; //Mentor: ETSU_E
Proj4js.defs["EPSG:27700"] = "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs "; //Mentor: GB_ORD1-MOD
Proj4js.defs["EPSG:29170"] = "+proj=utm +zone=20 +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-20N
Proj4js.defs["EPSG:29171"] = "+proj=utm +zone=21 +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-21N
Proj4js.defs["EPSG:32649"] = "+proj=utm +zone=49 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: HK-49
Proj4js.defs["EPSG:32650"] = "+proj=utm +zone=50 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: HK-50
Proj4js.defs["EPSG:3054"] = "+proj=utm +zone=26 +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +units=m +no_defs "; //Mentor: ICE-26
Proj4js.defs["EPSG:3055"] = "+proj=utm +zone=27 +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +units=m +no_defs "; //Mentor: ICE-27
Proj4js.defs["EPSG:3056"] = "+proj=utm +zone=28 +ellps=intl +towgs84=-73,46,-86,0,0,0,0 +units=m +no_defs "; //Mentor: ICE-28
Proj4js.defs["EPSG:2939"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=50.76666666666667 +k=1 +x_0=2300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CS63-K2
Proj4js.defs["EPSG:2940"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=53.76666666666667 +k=1 +x_0=3300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CS63-K3
Proj4js.defs["EPSG:2941"] = "+proj=tmerc +lat_0=0.1333333333333333 +lon_0=56.76666666666667 +k=1 +x_0=4300000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: CS63-K4
Proj4js.defs["EPSG:21097"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: KEN-N-E
Proj4js.defs["EPSG:21096"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: KEN-N-W
Proj4js.defs["EPSG:21037"] = "+proj=utm +zone=37 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: KEN-S-E
Proj4js.defs["EPSG:21036"] = "+proj=utm +zone=36 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: KEN-S-W
Proj4js.defs["EPSG:20438"] = "+proj=utm +zone=38 +ellps=intl +units=m +no_defs "; //Mentor: KU-AEA
Proj4js.defs["EPSG:20936"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: AFR50-36S/01
Proj4js.defs["EPSG:24547"] = "+proj=utm +zone=47 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: MAL-W_W
Proj4js.defs["EPSG:24548"] = "+proj=utm +zone=48 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: MAL-W_E
Proj4js.defs["EPSG:26192"] = "+proj=lcc +lat_1=29.7 +lat_0=29.7 +lon_0=-5.4 +k_0=0.9996155960000001 +x_0=500000 +y_0=300000 +a=6378249.2 +b=6356515 +towgs84=31,146,47,0,0,0,0 +units=m +no_defs "; //Mentor: Morocco-S/01
Proj4js.defs["EPSG:29333"] = "+proj=utm +zone=33 +south +ellps=bess_nam +units=m +no_defs "; //Mentor: NAM-U33
Proj4js.defs["EPSG:29169"] = "+proj=utm +zone=19 +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-19N
Proj4js.defs["EPSG:2119"] = "+proj=tmerc +lat_0=-42.68888888888888 +lon_0=173.01 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-AM-2000
Proj4js.defs["EPSG:2132"] = "+proj=tmerc +lat_0=-46.6 +lon_0=168.3427777777778 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-BLF-2000
Proj4js.defs["EPSG:2117"] = "+proj=tmerc +lat_0=-41.81055555555555 +lon_0=171.5811111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-BUL-2000
Proj4js.defs["EPSG:2114"] = "+proj=tmerc +lat_0=-40.71472222222223 +lon_0=172.6719444444444 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-COL-2000
Proj4js.defs["EPSG:2125"] = "+proj=tmerc +lat_0=-43.74861111111111 +lon_0=171.3605555555555 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-GAW-2000
Proj4js.defs["EPSG:2118"] = "+proj=tmerc +lat_0=-42.33361111111111 +lon_0=171.5497222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-GRY-2000
Proj4js.defs["EPSG:2108"] = "+proj=tmerc +lat_0=-39.65083333333333 +lon_0=176.6736111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-HB-2000
Proj4js.defs["EPSG:2121"] = "+proj=tmerc +lat_0=-42.88611111111111 +lon_0=170.9797222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-HO-2000
Proj4js.defs["EPSG:2123"] = "+proj=tmerc +lat_0=-43.97777777777778 +lon_0=168.6061111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-JB-2000
Proj4js.defs["EPSG:2116"] = "+proj=tmerc +lat_0=-41.28972222222222 +lon_0=172.1088888888889 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-KA-2000
Proj4js.defs["EPSG:2127"] = "+proj=tmerc +lat_0=-44.735 +lon_0=169.4675 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-LP-2000
Proj4js.defs["EPSG:2120"] = "+proj=tmerc +lat_0=-41.54444444444444 +lon_0=173.8019444444444 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-MA-2000
Proj4js.defs["EPSG:2105"] = "+proj=tmerc +lat_0=-36.87972222222222 +lon_0=174.7641666666667 +k=0.9999 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-MTE-2000
Proj4js.defs["EPSG:2128"] = "+proj=tmerc +lat_0=-45.13277777777778 +lon_0=168.3986111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-MTN-2000
Proj4js.defs["EPSG:2124"] = "+proj=tmerc +lat_0=-43.59055555555556 +lon_0=172.7269444444445 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-MTP-2000
Proj4js.defs["EPSG:2129"] = "+proj=tmerc +lat_0=-45.56361111111111 +lon_0=167.7386111111111 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-MTY-2000
Proj4js.defs["EPSG:2115"] = "+proj=tmerc +lat_0=-41.27444444444444 +lon_0=173.2991666666667 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-NL-2000
Proj4js.defs["EPSG:2131"] = "+proj=tmerc +lat_0=-45.86138888888889 +lon_0=170.2825 +k=0.99996 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-NT-2000
Proj4js.defs["EPSG:2130"] = "+proj=tmerc +lat_0=-45.81611111111111 +lon_0=170.6283333333333 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-OBP-2000
Proj4js.defs["EPSG:2122"] = "+proj=tmerc +lat_0=-43.11 +lon_0=170.2608333333333 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-OK-2000
Proj4js.defs["EPSG:2107"] = "+proj=tmerc +lat_0=-38.62444444444444 +lon_0=177.8855555555556 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-PB-2000
Proj4js.defs["EPSG:2126"] = "+proj=tmerc +lat_0=-44.40194444444445 +lon_0=171.0572222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-TI-2000
Proj4js.defs["EPSG:2110"] = "+proj=tmerc +lat_0=-39.51222222222222 +lon_0=175.64 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-TU-2000
Proj4js.defs["EPSG:2112"] = "+proj=tmerc +lat_0=-40.92527777777777 +lon_0=175.6472222222222 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-WA-2000
Proj4js.defs["EPSG:2111"] = "+proj=tmerc +lat_0=-40.24194444444444 +lon_0=175.4880555555555 +k=1 +x_0=400000 +y_0=800000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-WAN-2000
Proj4js.defs["EPSG:2193"] = "+proj=tmerc +lat_0=0 +lon_0=173 +k=0.9996 +x_0=1600000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: NZ-TM
Proj4js.defs["EPSG:26391"] = "+proj=tmerc +lat_0=4 +lon_0=4.5 +k=0.99975 +x_0=230738.26 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: NGRA-W
Proj4js.defs["EPSG:26392"] = "+proj=tmerc +lat_0=4 +lon_0=8.5 +k=0.99975 +x_0=670553.98 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: NGRA-M
Proj4js.defs["EPSG:26393"] = "+proj=tmerc +lat_0=4 +lon_0=12.5 +k=0.99975 +x_0=1110369.7 +y_0=0 +ellps=clrk80 +units=m +no_defs "; //Mentor: NGRA-E
Proj4js.defs["EPSG:25391"] = "+proj=tmerc +lat_0=0 +lon_0=117 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: PHIL-I
Proj4js.defs["EPSG:25392"] = "+proj=tmerc +lat_0=0 +lon_0=119 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: PHIL-II
Proj4js.defs["EPSG:25393"] = "+proj=tmerc +lat_0=0 +lon_0=121 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: PHIL-III
Proj4js.defs["EPSG:25394"] = "+proj=tmerc +lat_0=0 +lon_0=123 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: PHIL-IV
Proj4js.defs["EPSG:25395"] = "+proj=tmerc +lat_0=0 +lon_0=125 +k=0.99995 +x_0=500000 +y_0=0 +ellps=clrk66 +units=m +no_defs "; //Mentor: PHIL-V
Proj4js.defs["EPSG:3120"] = "+proj=sterea +lat_0=50.625 +lon_0=21.08333333333333 +k=0.9998 +x_0=4637000 +y_0=5467000 +ellps=krass +towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84 +units=m +no_defs "; //Mentor: Pulkovo/58.Poland-I/a
Proj4js.defs["EPSG:2190"] = "+proj=utm +zone=26 +ellps=intl +towgs84=-203,141,53,0,0,0,0 +units=m +no_defs "; //Mentor: F26-SB
Proj4js.defs["EPSG:20437"] = "+proj=utm +zone=37 +ellps=intl +units=m +no_defs "; //Mentor: SAUD-W
Proj4js.defs["EPSG:20438"] = "+proj=utm +zone=38 +ellps=intl +units=m +no_defs "; //Mentor: SAUD-C
Proj4js.defs["EPSG:20499"] = "+proj=utm +zone=39 +ellps=intl +units=m +no_defs "; //Mentor: SAUD-E
Proj4js.defs["EPSG:24500"] = "+proj=cass +lat_0=1.287646666666667 +lon_0=103.8530022222222 +x_0=30000 +y_0=30000 +a=6377304.063 +b=6356103.038993155 +towgs84=-11,851,5,0,0,0,0 +units=m +no_defs "; //Mentor: SING-G
Proj4js.defs["EPSG:20538"] = "+proj=utm +zone=38 +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +units=m +no_defs "; //Mentor: SOMAL-W
Proj4js.defs["EPSG:20539"] = "+proj=utm +zone=39 +ellps=krass +towgs84=-43,-163,45,0,0,0,0 +units=m +no_defs "; //Mentor: SOMAL-E
Proj4js.defs["EPSG:31121"] = "+proj=utm +zone=21 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: SUR
Proj4js.defs["EPSG:31170"] = "+proj=tmerc +lat_0=0 +lon_0=-55.68333333333333 +k=0.9996 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: SUR-OLD
Proj4js.defs["EPSG:31171"] = "+proj=tmerc +lat_0=0 +lon_0=-55.68333333333333 +k=0.9999 +x_0=500000 +y_0=0 +ellps=intl +towgs84=-265,120,-358,0,0,0,0 +units=m +no_defs "; //Mentor: SUR-NEW
Proj4js.defs["EPSG:21035"] = "+proj=utm +zone=35 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: AFR60-35S
Proj4js.defs["EPSG:21036"] = "+proj=utm +zone=36 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: AFR60-36S
Proj4js.defs["EPSG:21037"] = "+proj=utm +zone=37 +south +ellps=clrk80 +units=m +no_defs "; //Mentor: AFR60-37S
Proj4js.defs["EPSG:22332"] = "+proj=utm +zone=32 +a=6378249.2 +b=6356515 +units=m +no_defs "; //Mentor: TUNIS-O-MOD
Proj4js.defs["EPSG:32635"] = "+proj=utm +zone=35 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: TURK84-35N
Proj4js.defs["EPSG:32637"] = "+proj=utm +zone=37 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: TURK84-37N
Proj4js.defs["EPSG:32638"] = "+proj=utm +zone=38 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: TURK84-38N
Proj4js.defs["EPSG:21095"] = "+proj=utm +zone=35 +ellps=clrk80 +units=m +no_defs "; //Mentor: AFR60-35N
Proj4js.defs["EPSG:21096"] = "+proj=utm +zone=36 +ellps=clrk80 +units=m +no_defs "; //Mentor: AFR60-36N
Proj4js.defs["EPSG:3087"] = "+proj=aea +lat_1=24 +lat_2=31.5 +lat_0=24 +lon_0=-84 +x_0=400000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: FLHP
Proj4js.defs["EPSG:32164"] = "+proj=tmerc +lat_0=0 +lon_0=-99 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: BLM-14
Proj4js.defs["EPSG:32165"] = "+proj=tmerc +lat_0=0 +lon_0=-93 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: BLM-15
Proj4js.defs["EPSG:32166"] = "+proj=tmerc +lat_0=0 +lon_0=-87 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: BLM-16
Proj4js.defs["EPSG:32167"] = "+proj=tmerc +lat_0=0 +lon_0=-81 +k=0.9996 +x_0=500000.001016002 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: BLM-17
Proj4js.defs["EPSG:2782"] = "+proj=tmerc +lat_0=18.83333333333333 +lon_0=-155.5 +k=0.999966667 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HIHP-1-MOD
Proj4js.defs["EPSG:2783"] = "+proj=tmerc +lat_0=20.33333333333333 +lon_0=-156.6666666666667 +k=0.999966667 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HIHP-2-MOD
Proj4js.defs["EPSG:2784"] = "+proj=tmerc +lat_0=21.16666666666667 +lon_0=-158 +k=0.99999 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HIHP-3-MOD
Proj4js.defs["EPSG:2785"] = "+proj=tmerc +lat_0=21.83333333333333 +lon_0=-159.5 +k=0.99999 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HIHP-4-MOD
Proj4js.defs["EPSG:2786"] = "+proj=tmerc +lat_0=21.66666666666667 +lon_0=-160.1666666666667 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs "; //Mentor: HIHP-5-MOD
Proj4js.defs["EPSG:3089"] = "+proj=lcc +lat_1=37.08333333333334 +lat_2=38.66666666666666 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=1500000 +y_0=999999.9998983998 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs "; //Mentor: KY1Z-FT
Proj4js.defs["EPSG:3088"] = "+proj=lcc +lat_1=37.08333333333334 +lat_2=38.66666666666666 +lat_0=36.33333333333334 +lon_0=-85.75 +x_0=1500000 +y_0=1000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: KY1Z-M
Proj4js.defs["EPSG:26802"] = "+proj=tmerc +lat_0=41.5 +lon_0=-85.75 +k=0.999909091 +x_0=152400.3048006096 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MI27-OC
Proj4js.defs["EPSG:26801"] = "+proj=tmerc +lat_0=41.5 +lon_0=-83.66666666666667 +k=0.999942857 +x_0=152400.3048006096 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MI27-OE
Proj4js.defs["EPSG:26803"] = "+proj=tmerc +lat_0=41.5 +lon_0=-88.75 +k=0.999909091 +x_0=152400.3048006096 +y_0=0 +a=6378450.047548896 +b=6356826.621488444 +to_meter=0.3048006096012192 +no_defs "; //Mentor: MI27-OW
Proj4js.defs["EPSG:2991"] = "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=400000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs "; //Mentor: OR-GIS83-M
Proj4js.defs["EPSG:2195"] = "+proj=utm +zone=2 +south +ellps=GRS80 +units=m +no_defs "; //Mentor: SAMOA-HARN
Proj4js.defs["EPSG:2516"] = "+proj=tmerc +lat_0=0 +lon_0=153 +k=1 +x_0=500000 +y_0=0 +ellps=krass +units=m +no_defs "; //Mentor: GK42-26N
Proj4js.defs["EPSG:32618"] = "+proj=utm +zone=18 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: VEN-18N
Proj4js.defs["EPSG:32619"] = "+proj=utm +zone=19 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: VEN-19N
Proj4js.defs["EPSG:32620"] = "+proj=utm +zone=20 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: VEN-20N
Proj4js.defs["EPSG:20935"] = "+proj=utm +zone=35 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ZIM-35/01
Proj4js.defs["EPSG:20936"] = "+proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +units=m +no_defs "; //Mentor: ZIM-36/01
Proj4js.defs["EPSG:21097"] = "+proj=utm +zone=37 +ellps=clrk80 +units=m +no_defs "; //Mentor: AFR60-37N
Proj4js.defs["EPSG:29172"] = "+proj=utm +zone=22 +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-22N
Proj4js.defs["EPSG:29191"] = "+proj=utm +zone=21 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-21S
Proj4js.defs["EPSG:29192"] = "+proj=utm +zone=22 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-22S
Proj4js.defs["EPSG:29193"] = "+proj=utm +zone=23 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-23S
Proj4js.defs["EPSG:29194"] = "+proj=utm +zone=24 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-24S
Proj4js.defs["EPSG:29195"] = "+proj=utm +zone=25 +south +ellps=aust_SA +units=m +no_defs "; //Mentor: SA69-25S
Proj4js.defs["EPSG:26701"] = "+proj=utm +zone=1 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-1N
Proj4js.defs["EPSG:26702"] = "+proj=utm +zone=2 +ellps=clrk66 +datum=NAD27 +units=m +no_defs "; //Mentor: UTM27-2N
Proj4js.defs["EPSG:32661"] = "+proj=stere +lat_0=90 +lat_ts=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UPS-N
Proj4js.defs["EPSG:32761"] = "+proj=stere +lat_0=-90 +lat_ts=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs "; //Mentor: UPS-S
Proj4js.defs["EPSG:2989"] = "+proj=utm +zone=20 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; //Mentor: RRAF91.UTM-20N
Proj4js.defs["EPSG:31265"] = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-5
Proj4js.defs["EPSG:31266"] = "+proj=tmerc +lat_0=0 +lon_0=18 +k=1 +x_0=6500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-6
Proj4js.defs["EPSG:31267"] = "+proj=tmerc +lat_0=0 +lon_0=21 +k=1 +x_0=7500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-7
Proj4js.defs["EPSG:31268"] = "+proj=tmerc +lat_0=0 +lon_0=24 +k=1 +x_0=8500000 +y_0=0 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs "; //Mentor: MGI/gc.Gauss3d-8
/**
 * Fusion.Layers.MapServer
 *
 * $Id: MapServer.js 2414 2011-07-22 18:54:29Z madair $
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/******************************************************************************
 * Class: Fusion.Layers.MapServer
 *
 * Implementation of the map widget for MapServer CGI interface services
*/
Fusion.Event.MAP_LAYER_ORDER_CHANGED = Fusion.Event.lastEventId++;

Fusion.Layers.MapServer = OpenLayers.Class(Fusion.Layers, {
    arch: 'MapServer',
    session: [null],
    sActiveLayer: null,
    selectionType: 'INTERSECTS',
    bSelectionOn: false,
    oSelection: null,
    bLayersReversed: true,     //MS returns layers bottom-most layer first, we treat layer order in reverse sense
    mapMetadataKeys: null,
    layerMetadataKeys: null,
    oMaptip:null,
    bMapTipFired: false,
    bRestoreMapState: false,
    oRestoredState: {},

    //the map file
    sMapFile: null,
    supports: {
      query: true,
      edit: true
    },

    initialize: function(map, mapTag, isMapWidgetLayer) {
        //console.log('Fusion.Layers.MapServer.initialize');
        Fusion.Layers.prototype.initialize.apply(this, arguments);
        this.registerEventID(Fusion.Event.MAP_SESSION_CREATED);
        //this.selectionType = extension.SelectionType ? extension.SelectionType[0] : 'INTERSECTS';

        this.sMapFile = mapTag.extension.MapFile ? mapTag.extension.MapFile[0] : '';

        // load mapfrom the querystring if "theme" is present.
        var newTheme = Fusion.getQueryParam('theme');
        if (newTheme != '') {
        this.sMapFile = newTheme;
          //clear the query param after it has been used once
          Fusion.queryParams['theme'] = null;
        }

        var restoreMapState = Fusion.getQueryParam('restoreState');
        if (restoreMapState != '') {
          //clear the query param after it has been used once
          this.oRestoredState.id = restoreMapState;
          Fusion.queryParams['restoreState'] = null;
          // set flag to true so we can restore the map from a saved session.
          this.bRestoreMapState = true;
        }
        

        this.mapMetadataKeys = mapTag.extension.MapMetadata ? mapTag.extension.MapMetadata[0] : null;
        this.layerMetadataKeys = mapTag.extension.LayerMetadata ? mapTag.extension.LayerMetadata[0] : null;
        
        rootOpts = {
          displayInLegend: this.bDisplayInLegend,
          expandInLegend: this.bExpandInLegend,
          legendLabel: this._sMapname,
          uniqueId: 'layerRoot',
          groupName: 'layerRoot',
          visible: true,
          actuallyVisible: true
          //TODO: set other opts for group initialization as required
        };
        this.layerRoot = new Fusion.Layers.Group(rootOpts,this);

        this.keepAliveInterval = parseInt(mapTag.extension.KeepAliveInterval ? mapTag.extension.KeepAliveInterval[0] : 300);
        this.noCache = true;

        if (mapTag.sid) {
            this.session[0] = mapTag.sid;
            this.mapSessionCreated();
        } else {
            this.createSession();
        }
    },

    createSession: function() {
        if (!this.session[0]) {
            this.session[0] = this;
            var sl = Fusion.getScriptLanguage();
            var scriptURL = 'layers/' + this.arch + '/' + sl + '/CreateSession.' + sl;
            var options = {onSuccess: OpenLayers.Function.bind(this.createSessionCB, this)};
            Fusion.ajaxRequest(scriptURL,options);
        }
        if (this.session[0] instanceof Fusion.Layers.MapServer) {
            this.session[0].registerForEvent(Fusion.Event.MAP_SESSION_CREATED, 
                        OpenLayers.Function.bind(this.mapSessionCreated, this));
        } else {
            this.mapSessionCreated();
        }
    },

    createSessionCB: function(r) {
        if (r.status == 200) {
            var o;
            eval('o='+r.responseText);
            this.session[0] = o.sessionId;
            var acceptLang = o.acceptLanguage.split(',');
            //IE - en-ca,en-us;q=0.8,fr;q=0.5,fr-ca;q=0.3
            //FF - en-us,en;q=0.5
            for (var i=0; i<acceptLang.length; ++i) {
              var locale = acceptLang[i].split(";");
              Fusion.initializeLocale(locale[0]);
              break;
            }

            /* Session is created, Check to see if we are going to restore a saved
                map state and restore it if set to true.
            */
            if(this.bRestoreMapState == true){
                var that = this;
                var sl = Fusion.getScriptLanguage();
                var scriptURL = 'layers/' + this.arch + '/' + sl + '/RestoreState.' + sl;

                var params = {
                    parameters: OpenLayers.Util.getParameterString({
                        session: this.session[0] ,
                        id: this.oRestoredState.id
                    }),
                    onComplete: function(xhr) {
                        var o;
                        eval('o='+xhr.responseText);
                        that.restoreStateCB(o);
                    }
                };
                Fusion.ajaxRequest(scriptURL,params);
            }
            else
            {
                // done with session create, fire the event.
                this.triggerEvent(Fusion.Event.MAP_SESSION_CREATED);
            }
            
        }
    },

    restoreStateCB: function(oResponse){
        if(oResponse.error) {
            Fusion.reportError(new Fusion.Error(Fusion.Error.WARNING, "Error Restoring Map State - "+oResponse.error));
        } else {
            this.oRestoredState = oResponse;
            var a = this.oRestoredState.extents.split(',');
            this.mapWidget.mapGroup.initialView = {
              minX: parseFloat(a[0]),
              minY: parseFloat(a[1]),
              maxX: parseFloat(a[2]),
              maxY: parseFloat(a[3])
            }
            if (this.oRestoredState.queryfile && this.oRestoredState.queryfile.length>0) {
              this.bSelectionOn = true;
              this._sQueryfile = this.oRestoredState.queryfile;
            }
        }
        // done with session create, fire the event.
        this.triggerEvent(Fusion.Event.MAP_SESSION_CREATED);
    },

    mapSessionCreated: function() {
        // restore the mapfile from a saved state.
        if(this.bRestoreMapState === true && this.oRestoredState.loadmap ){
            this.loadMap(this.oRestoredState.loadmap);
        }
        else if (this.sMapFile != '') {
            this.loadMap(this.sMapFile);
        }
        window.setInterval(OpenLayers.Function.bind(this.pingServer, this), 
                                                this.keepAliveInterval * 1000);
    },

    sessionReady: function() {
        return (typeof this.session[0] == 'string');
    },

    getSessionID: function() {
        return this.session[0];
    },

    loadMap: function(mapfile, options) {
        while (this.mapWidget.isBusy()) {
	        this.mapWidget._removeWorker();
        }
        this.bMapLoaded = false;
        //console.log('loadMap: ' + resourceId);
        /* don't do anything if the map is already loaded? */
        if (this._sMapFile == mapfile) {
            return;
        }

        if (!this.sessionReady()) {
            this.sMapFile = mapfile;
            return;
        }
        this.triggerEvent(Fusion.Event.LAYER_LOADING);
        this.mapWidget._addWorker();

        this._fScale = -1;
        this._nDpi = 72;

        options = options || {};

        this.aVisibleLayers = options.showlayers || [];
        this.aVisibleGroups = options.showgroups || [];
        this.aLayers = [];

        this.oSelection = null;
        this.aSelectionCallbacks = [];
        this._bSelectionIsLoading = false;

        var sl = Fusion.getScriptLanguage();
        var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/LoadMap.' + sl;
        var params = {
            'mapfile': mapfile,
            'session': this.getSessionID()
        };
        if (this.mapMetadataKeys) {
            params.map_metadata = this.mapMetadataKeys;
        }
        if (this.layerMetadataKeys) {
            params.layer_metadata = this.layerMetadataKeys;
        }
        var options = {onSuccess:OpenLayers.Function.bind(this.mapLoaded, this), parameters: params};
        Fusion.ajaxRequest(loadmapScript, options);
    },

    mapLoaded: function(r) {
        if (r.status == 200)
        {
            var o;
            eval('o='+r.responseText);
            this._sMapFile = o.mapId;
            this._sMapname = o.mapName;
            this._sMapTitle = o.mapTitle;
            this._sImageType = o.imagetype;
            this.metadata = o.metadata;
            
            //setup the projection in the map widget
            if (o.projString.length > 0) {
              var epsg = o.projString.indexOf("init=");
              if (epsg >= 0) {
                this.mapTag.layerOptions.projection = o.projString.substring(epsg+5).toUpperCase();
              } else {
                this.mapTag.layerOptions.projection = o.mapName.toUpperCase();
                Proj4js.defs[this.mapTag.layerOptions.projection] = o.projString;
              }
            }

            this.mapTag.layerOptions.maxExtent = OpenLayers.Bounds.fromArray(o.extent);

            this.layerRoot.clear();
            this.layerRoot.legendLabel = this._sMapTitle;
            this.layerRoot.displayInLegend = true;
            this.layerRoot.expandInLegend = true;

            this.parseMapLayersAndGroups(o);

            var minScale = 1.0e10;
            var maxScale = 0;
            for (var i=0; i<this.aLayers.length; i++) {
              if (this.aLayers[i].visible) {
                  this.aVisibleLayers.push(this.aLayers[i].layerName);
              }
      				minScale = Math.min(minScale, this.aLayers[i].minScale);
      				maxScale = Math.max(maxScale, this.aLayers[i].maxScale);
            }
            //a scale value of 0 is undefined
            if (minScale <= 0) {
              minScale = 1.0;
            }

            //to allow for scaling that doesn't match any of the pre-canned units
            this.mapTag.layerOptions.units = Fusion.getClosestUnits(o.metersPerUnit);
            
            var layerOptions = {
      				singleTile: true,
      				ratio: this.ratio,
              maxResolution: 'auto',
      				minScale: maxScale,	//OL interpretation of min/max scale is reversed from Fusion
      				maxScale: minScale
      			};
            OpenLayers.Util.extend(layerOptions, this.mapTag.layerOptions);

            //create the OL layer for this Map layer
            var params = {
              layers: this.aVisibleLayers.join(' '),
              session: this.getSessionID(),
              map: this._sMapFile,
              map_imagetype: this._sImageType
            };
            if (this.hasSelection()) {
              params.queryfile = this._sQueryfile;
            }
            OpenLayers.Util.extend(params, this.mapTag.layerParams);

            //remove this layer if it was already loaded
            if (this.oLayerOL) {
                this.oLayerOL.events.unregister("loadstart", this, this.loadStart);
                this.oLayerOL.events.unregister("loadend", this, this.loadEnd);
                this.oLayerOL.events.unregister("loadcancel", this, this.loadEnd);
                this.oLayerOL.destroy();
            }

            var url = Fusion.getConfigurationItem('mapserver', 'cgi');
            this.oLayerOL = new OpenLayers.Layer.MapServer( o.mapName, url, params, layerOptions);
            this.oLayerOL.events.register("loadstart", this, this.loadStart);
            this.oLayerOL.events.register("loadend", this, this.loadEnd);
            this.oLayerOL.events.register("loadcancel", this, this.loadEnd);

            if (this.bIsMapWidgetLayer) {
              this.mapWidget.addMap(this);
            }
            this.bMapLoaded = true;
        }
        else
        {
            Fusion.reportError( new Fusion.Error(Fusion.Error.FATAL,
					'Failed to load requested map:\n'+r.responseText));
        }
        this.mapWidget._removeWorker();
        this.triggerEvent(Fusion.Event.LAYER_LOADED);
    },

    reloadMap: function() {
        this.mapWidget._addWorker();
        this.aVisibleLayers = [];
        this.aVisibleGroups = [];
        this.layerRoot.clear();
        this.aLayers = [];

        var sl = Fusion.getScriptLanguage();
        var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/LoadMap.' + sl;

        var params = {
            'mapname': this._sMapname,
            'session': this.getSessionID(),
            'reload': true
        };
        if (this.mapMetadataKeys) {
            params.map_metadata = this.mapMetadataKeys;
        }
        if (this.layerMetadataKeys) {
            params.layer_metadata = this.layerMetadataKeys;
        }
        var options = {onSuccess: OpenLayers.Function.bind(this.mapReloaded, this),
                                     parameters: params};
        Fusion.ajaxRequest(loadmapScript, options);
    },

    /**
     * Function: loadScaleRanges
     * 
     * This function should be called after the map has loaded. It
     * loads the scsle ranges for each layer. I tis for now only
     * used by the legend widget.
     */
        
    loadScaleRanges: function() {
        var sl = Fusion.getScriptLanguage();
        var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/LoadScaleRanges.' + sl;
        var params = {'mapname': this._sMapname, "session": this.getSessionID()};
        var options = {
          onSuccess: OpenLayers.Function.bind(this.scaleRangesLoaded, this), 
          parameters: params
        };
        Fusion.ajaxRequest(loadmapScript, options);
    },

    scaleRangesLoaded: function(r) {
        if (r.status == 200) {
            var o;
            eval('o='+r.responseText);
            
            if (o.layers && o.layers.length > 0) {
                var iconOpt = {
                    url: o.icons_url || null,
                    width: o.icons_width || 16,
                    height: o.icons_height || 16
                };
                for (var i=0; i<o.layers.length; i++) {
                    var oLayer = this.getLayerById(o.layers[i].uniqueId);
                    if (oLayer) {
                        oLayer.scaleRanges = [];
                        for (var j=0; j<o.layers[i].scaleRanges.length; j++) {
                            var scaleRange = new Fusion.Layers.ScaleRange(o.layers[i].scaleRanges[j], 
                                                                                 oLayer.layerType, iconOpt);
                            oLayer.scaleRanges.push(scaleRange);
                        }
                    }
                }
            }
            this.mapWidget.triggerEvent(Fusion.Event.MAP_SCALE_RANGE_LOADED);
        }
    },

    mapReloaded: function(r) {  
        if (r.status == 200) {
            var o;
            eval('o='+r.responseText);

            //can metadata change?
            //this.metadata = o.metadata;

            this.parseMapLayersAndGroups(o);
            this.aVisibleLayers = [];
            for (var i=0; i<this.aLayers.length; i++) {
                if (this.aLayers[i].visible) {
                    this.aVisibleLayers.push(this.aLayers[i].layerName);
                }
            }
            this.drawMap();
            this.mapWidget.triggerEvent(Fusion.Event.MAP_RELOADED);
        } else {
            Fusion.reportError( new Fusion.Error(Fusion.Error.FATAL,
                OpenLayers.i18n('mapLoadError', {'error':r.responseText})));
        }
        this.mapWidget._removeWorker();
    },

    reorderLayers: function(aLayerIndex) {
        var sl = Fusion.getScriptLanguage();
        var loadmapScript = 'layers/' + this.arch + '/' + sl  + '/SetLayers.' + sl;

        var params = {
            'mapname': this._sMapname,
            'session': this.getSessionID(),
            'layerindex': aLayerIndex.join()
        };
        var options = {onSuccess: OpenLayers.Function.bind(this.mapLayersReset, this, aLayerIndex),
                                     parameters: params};
        Fusion.ajaxRequest(loadmapScript, options);
    },

    mapLayersReset: function(aLayerIndex,r) {
        //console.log("mapLayersReset");
        var o;
        eval('o='+r.responseText);
  			if (o.success) {
  				var layerCopy = $A(this.aLayers);
                var nLayers = layerCopy.length -1;
          
          //Mapserver has list of layers reversed from MapGuide
          //aLayerIndex.reverse();
    
  				this.aLayers = [];
  				this.aVisibleLayers = [];
          for (var i=0; i<aLayerIndex.length; ++i) {
                //this.aLayers.push( layerCopy[ nLayers - aLayerIndex[i] ] );
                this.aLayers.push( layerCopy[ aLayerIndex[i] ] );
                if (this.aLayers[i].visible) {
                    this.aVisibleLayers.push(this.aLayers[i].layerName);
                }
            }
            //this.layerRoot.clear();
            this.drawMap();
            this.triggerEvent(Fusion.Event.MAP_LAYER_ORDER_CHANGED);
        }

    },

    parseLayers: function() {
        //this.layerRoot.clear();
        for (var i=0; i<this.aLayers.length; i++) {
            var layer = this.aLayers[i];
            var parent;
            if (layer.parentGroup != '') {
                parent = this.layerRoot.findGroup(layer.parentGroup.name);
            } else {
                parent = this.layerRoot;
            }
            parent.addLayer(layer, this.bLayersReversed);
        }
    },

    parseMapLayersAndGroups: function(o) {
        //sort the groups so that root folders are created before subfolders 
        o.groups.sort(function(a,b) {return a.depth-b.depth} );
        
        for (var i=0; i<o.groups.length; i++) {
            var group = new Fusion.Layers.Group(o.groups[i], this);
            var parent;
            if (group.parentUniqueId != '') {
                parent = this.layerRoot.findGroup(group.parentUniqueId);
            } else {
                parent = this.layerRoot;
            }
            parent.addGroup(group, this.bLayersReversed);
        }

        for (var i=0; i<o.layers.length; i++) {
            var layer = new Fusion.Layers.Layer(o.layers[i], this);
            var parent;
            if (layer.parentGroup != '') {
                parent = this.layerRoot.findGroup(layer.parentGroup);
            } else {
                parent = this.layerRoot;
            }
            parent.addLayer(layer, this.bLayersReversed);
            this.aLayers.push(layer);
        }
    },

    updateLayer: function() {   //to be fleshed out, add query file to layer if selection, call this before draw
      if (this.hasSelection()) {
          this.oLayerOL.addOptions({queryfile: this._sQueryfile});
      }
    },

    drawMap: function() {
        if (!this.bMapLoaded || this.deferredDraw) {
            return;
        }
        var aLayers = [];
        for (var i=0; i<this.aLayers.length; i++) {
            var l = this.aLayers[i];
            if (l.isVisible()) {
                aLayers.push(l.layerName);
            }
        }
        var params = { layers: /*this.aVisibleLayers */aLayers.join(' '), ts : (new Date()).getTime()};
        if (this.hasSelection()) {
            params['queryfile']=this._sQueryfile;
        } else {
            params['queryfile'] = '';
        }
        this.oLayerOL.mergeNewParams(params);
    },

    showLayer: function( sLayer ) {
        this.aVisibleLayers.push(sLayer.layerName);
        this.drawMap();
    },

    hideLayer: function( sLayer ) {
        for (var i=0; i<this.aLayers.length; i++) {
            if (this.aVisibleLayers[i] == sLayer.layerName) {
                this.aVisibleLayers.splice(i,1);
                break;
            }
        }
        this.drawMap();
    },

    showGroup: function( group, noDraw ) {
        this.processGroupEvents(group, true);
        if (group.groupName == 'layerRoot') {
            this.oLayerOL.setVisibility(true);
        } else {
            this.aVisibleGroups.push(group.uniqueId);
            this.deferredDraw = true;
            for (var i=0; i<group.groups.length; ++i) {
              if (group.groups[i].wasVisibleInGroup) {
                this.showGroup(group.groups[i], true);
              }
            }
            for (var i=0; i<group.layers.length; ++i) {
              if (group.layers[i].wasVisibleInGroup) {
                group.layers[i].show();
              }
              group.layers[i].wasVisibleInGroup = null;
            }
            this.deferredDraw = false;
            this.drawMap();
        }
    },
    hideGroup: function( group, noDraw ) {
        this.processGroupEvents(group, false);
        if (group.groupName == 'layerRoot') {
            this.oLayerOL.setVisibility(false);
        } else {
            for (var i=0; i<this.aVisibleGroups.length; i++) {
                if (this.aVisibleGroups[i] == group.uniqueId) {
                    this.aVisibleGroups.splice(i,1);
                    break;
                }
            }
            this.deferredDraw = true;
            for (var i=0; i<group.groups.length; ++i) {
              if (group.groups[i].visible) {
                group.groups[i].wasVisibleInGroup = true;
                this.hideGroup(group.groups[i], true);
              }
            }
            for (var i=0; i<group.layers.length; ++i) {
              if (group.layers[i].visible) {
                group.layers[i].wasVisibleInGroup = true;
                group.layers[i].hide();
              }
            }
            this.deferredDraw = false;
            this.drawMap();
        }
    },
    showGroupOLD: function( sGroup ) {
      if (sGroup == 'layerRoot') {
        this.oLayerOL.setVisibility(true);
      } else {
        this.aVisibleGroups.push(sGroup);
        var group = this.layerRoot.findGroup(sGroup);
        this.deferredDraw = true;
        for (var i=0; i<group.layers.length; ++i) {
          group.layers[i].show();
        }
        this.deferredDraw = false;
        this.drawMap();
      }
    },

    hideGroupOLD: function( sGroup ) {
      if (sGroup == 'layerRoot') {
        this.oLayerOL.setVisibility(false);
      } else {
        for (var i=0; i<this.aVisibleGroups.length; i++) {
            if (this.aVisibleGroups[i] == sGroup) {
                this.aVisibleGroups.splice(i,1);
                break;
            }
        }
        var group = this.layerRoot.findGroup(sGroup);
        this.deferredDraw = true;
        for (var i=0; i<group.layers.length; ++i) {
          group.layers[i].hide();
        }
        this.deferredDraw = false;
        this.drawMap();
      }
    },

    refreshLayer: function( sLayer ) {
        this.drawMap();
    },

    hasSelection: function() { return this.bSelectionOn; },

    getSelectionCB: function(userFunc, r) {
      if (r.status == 200) {
          var o;
          eval("o="+r.responseText);
          var oSelection = new Fusion.SelectionObject(o);
          userFunc(oSelection);
      }
    },

    /**
     * advertise a new selection is available and redraw the map
     */
    newSelection: function() {

        this.bSelectionOn = true;
        this.drawMap();
        this.triggerEvent(Fusion.Event.MAP_SELECTION_ON);
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectedFeatureCount: function() {
      var total = 0;
      for (var j=0; j<this.aLayers.length; ++j) {
        total += this.aLayers[j].selectedFeatureCount;
      }
      return total;
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectedLayers: function() {
      var layers = [];
      for (var j=0; j<this.aLayers.length; ++j) {
        if (this.aLayers[j].selectedFeatureCount>0) {
          layers.push(this.aLayers[j]);
        }
      }
      return layers;
    },

    /**
     * Returns the number of features selected for this map layer
     */
    getSelectableLayers: function() {
      var layers = [];
      for (var j=0; j<this.aLayers.length; ++j) {
        if (this.aLayers[j].selectable) {
          layers.push(this.aLayers[j]);
        }
      }
      return layers;
    },

    /**
     * asynchronously load the current selection.  When the current
     * selection changes, the selection is not loaded because it
     * could be a lengthy process.  The user-supplied function will
     * be called when the selection is available.
     *
     * @param userFunc {Function} a function to call when the
     *        selection has loaded
     * @param layers {string} Optional parameter.  A comma separated
     *        list of layer names (Roads,Parcels). If it is not
     *        given, all the layers that have a selection will be used
     *
     * @param startcount {string} Optional parameter.  A comma separated
     *        list of a statinh index and the number of features to be retured for
     *        each layer given in the layers parameter. Index starts at 0
     *        (eg: 0:4,2:6 : return 4 elements for the first layers starting at index 0 and
     *         six elements for layer 2 starting at index 6). If it is not
     *        given, all the elemsnts will be returned.
     */
    getSelection: function(userFunc, layers, startcount) {

        if (userFunc)
        {
            var s = 'layers/' + this.arch + '/' + Fusion.getScriptLanguage() + "/Selection." + Fusion.getScriptLanguage() ;
            var params = {
                'mapname': this._sMapname,
                'session': this.getSessionID(),
                'layers': layers,
                'startcount': startcount,
                'queryfile': this._sQueryfile
            };
            var options = {
                parameters:params,
                onSuccess: OpenLayers.Function.bind(this.getSelectionCB, this, userFunc)
            };
            Fusion.ajaxRequest(s, options);
        }

    },

    /**
       Utility function to clear current selection
    */
    clearSelection: function() {
      if (!this.aLayers) return;

        //clear the selection count for the layers
        for (var j=0; j<this.aLayers.length; ++j) {
          this.aLayers[j].selectedFeatureCount = 0;
        }

        this.bSelectionOn = false;
        this._sQueryfile = "";
        this.triggerEvent(Fusion.Event.MAP_SELECTION_OFF);
        this.drawMap();
        this.oSelection = null;
    },


    /**
       Call back function when slect functions are called (eg queryRect)
    */
    processQueryResults: function(zoomTo, r) {
        this.mapWidget._removeWorker();
        if (r.status == 200) {
            var o;
            eval("o="+r.responseText);
            if (!o.hasSelection) {
                //this.drawMap();
                return;
            } else {
                this._sQueryfile = o.queryFile;
                for (var i=0; i<o.layers.length; ++i) {
                  var layerName = o.layers[i];
                  for (var j=0; j<this.aLayers.length; ++j) {
                    if (layerName == this.aLayers[j].layerName) {
                      this.aLayers[j].selectedFeatureCount = o[layerName].featureCount;
                    }
                  }
                }
                this.newSelection();
                if (zoomTo) {
                var ext = oNode.extents;
                var extents = new OpenLayers.Bounds(ext.minx, ext.miny, ext.maxx, ext.maxy);
                this.zoomToSelection(extents);
              }
            }
        }
    },
    /**
       Do a query on the map
    */
    query: function(options) {
        this.mapWidget._addWorker();

        //clear the selection count for the layers
        for (var j=0; j<this.aLayers.length; ++j) {
          this.aLayers[j].selectedFeatureCount = 0;
        }

        var bPersistant = options.persistent || true;
        var layers = options.layers || '';
        /* if no layes are given, query only visible layers. This is ususally the most common case*/
        if (layers == '') {
          //layers = this.aVisibleLayers.join(',');
        }
        var zoomTo = options.zoomTo || false;
        var sl = Fusion.getScriptLanguage();
        var queryScript = 'layers/' + this.arch + '/' + sl  + '/Query.' + sl;

        var params = {
            'mapname': this._sMapname,
            'session': this.getSessionID(),
            'spatialfilter': options.geometry || '',
            'maxfeatures': options.maxFeatures || -1, //-1 means select all features
            'layers': layers,
            'variant': options.selectionType || this.selectionType
        };
        if (options.filter) {
            params.filter = options.filter;
            params.filterItem = options.filterItem;
        }
        if (options.extendSelection) {
            params.extendselection = true;
        }
        if (options.computedProperties) {
            params.computed = true;
        }
        var ajaxOptions = {
            onSuccess: OpenLayers.Function.bind(this.processQueryResults, this, zoomTo), 
            parameters: params
        };
        Fusion.ajaxRequest(queryScript, ajaxOptions);
    },

    pingServer: function() {
        var s = 'layers/' + this.arch + '/' + Fusion.getScriptLanguage() + "/Common." + Fusion.getScriptLanguage() ;
        var params = {};
        params.parameters = {'session': this.getSessionID()};
        Fusion.ajaxRequest(s, params);
  },

    getMetadata: function(key, layerName) {
      if (layerName) {
        var layer = this.getLayerByName(layerName);
        if (layer.metadata && typeof layer.metadata[key] != 'undefined') {
          return layer.metadata[key];
        } else {
          return '';
        }
        
      } else {
        if (this.metadata && typeof this.metadata[key] != 'undefined') {
            return this.metadata[key];
        } else {
          //TODO: a better way to create the layer metadata string?
            var metadataString = ''
            var join = "";
            for (var i=0; i<this.aLayers.length; i++) {
              if (this.aLayers[i].metadata && 
                  typeof this.aLayers[i].metadata[key] != 'undefined' &&
                  this.aLayers[i].metadata[key].length>0 ) {
                metadataString += join + this.aLayers[i].legendLabel + ":" + this.aLayers[i].metadata[key] ;
                join = " - ";
              }
            }
            return metadataString;
        }
      }
    },
    
    getGroupInfoUrl: function(groupName) {
      return null;
   },

    getLayerInfoUrl: function(layerName) {
      var layer = this.getLayerByName(layerName);
      var layerUrl = null;
      if (layer) {
        var metadata = layer.metadata;
        if (metadata && metadata["LAYERINFOURL"]) {
          layerUrl = metadata["LAYERINFOURL"];
        }
      }
      return layerUrl;
  },

    getLayerByName: function(name)
    {
        var oLayer = null;
        for (var i=0; i<this.aLayers.length; i++)
        {
            if (this.aLayers[i].layerName == name)
            {
                oLayer = this.aLayers[i];
                break;
            }
        }
        return oLayer;
    },           

    getLayerById: function(id)
    {
        var oLayer = null;
        for (var i=0; i<this.aLayers.length; i++)
        {
            if (this.aLayers[i].uniqueId == id)
            {
                oLayer = this.aLayers[i];
                break;
            }
        }
        return oLayer;
    },           

    getLegendImageURL: function(fScale, layer, style) {
        var sl = Fusion.getScriptLanguage();
        var url = Fusion.getFusionURL() + '/layers/' + this.arch + '/' + sl  + '/LegendIcon.' + sl;
        var sessionid = this.getSessionID();
        var params = 'mapname='+this._sMapname+"&session="+sessionid + '&layername='+layer.resourceId + '&classindex='+style.index;
        return url + '?'+params;
    },

    getLinkParams: function() {
      var queryParams = {};
      queryParams.theme = this.sMapFile;

      return queryParams;
    },
    
    getMapTip: function(oMapTips){
        if (!this.aLayers || 
            !this.aLayers.length || 
            !this.aLayers[0].scaleRanges) {
            return;
        }
        
        if(this.bMapTipFired == false){
            //console.log("MAPSERVER:getMapTip");
            var pos = this.mapWidget.pixToGeo(oMapTips.oCurrentPosition.x, oMapTips.oCurrentPosition.y);
            var dfGeoTolerance = this.mapWidget.pixToGeoMeasure(oMapTips.nTolerance);
            var minx = pos.x-dfGeoTolerance;
            var miny = pos.y-dfGeoTolerance;
            var maxx = pos.x+dfGeoTolerance;
            var maxy = pos.y+dfGeoTolerance;
            var geometry = 'POLYGON(('+ minx + ' ' + miny + ', ' + minx + ' ' + maxy + ', ' + maxx + ' ' + maxy + ', ' + maxx + ' ' + miny + ', ' + minx + ' ' + miny + '))';
            var selectionType = "INTERSECTS";

           var aVisLayers = [];
           
           for(var i = 0; i<this.aLayers.length;i++ ){
                var iLayerMinScale = this.aLayers[i].scaleRanges[0].minScale;
                var iLayerMaxScale = this.aLayers[i].scaleRanges[0].maxScale;
                var iCurrentScale = this.mapWidget.getScale();

                if(iCurrentScale < iLayerMaxScale && iCurrentScale > iLayerMinScale){
                    if(this.aLayers[i].isVisible() === true){
                        aVisLayers.push(this.aLayers[i].layerName);
                    }
                }
           }

            var loadmapScript = '/layers/'+ this.arch + '/php/Maptip.php';
            var params = {
                'mapname': this._sMapname,
                'session': this.getSessionID(),
                'spatialfilter': geometry,
                'maxfeatures': 0, //zero means select all features
                'variant': selectionType,
                'layer': oMapTips.aLayers || '',
                'textfield': oMapTips.aTextFields || '',
                'label': oMapTips.aLabels || '',
                'customURL': oMapTips.aCustomURL || '',
                'visLayers' : aVisLayers
                
            }
            var parseMapTip = this.parseMapTip.bind(this);
            this.bMapTipFired = true;
            var ajaxOptions = {
                onSuccess: function(response){
                        eval("rjson=" + response.responseText);
                        parseMapTip(rjson);
                        },
                        parameters: params};
            Fusion.ajaxRequest(loadmapScript, ajaxOptions);
        }
    },
    
    parseMapTip: function(json){
        this.bMapTipFired = false;
        this.oMaptip = {};       
        this.oMaptip.t = json.maptips;
        this.oMaptip.h = json.url;
        // mapserver only
        this.oMaptip.l= json.label;
        
        this.mapWidget.triggerEvent(Fusion.Event.MAP_MAPTIP_REQ_FINISHED, this.oMaptip);
    }
});


var MSLAYER_POINT_TYPE = 0;
var MSLAYER_LINE_TYPE = 1;
var MSLAYER_POLYGON_TYPE = 2;
var MSLAYER_SOLID_TYPE = 3;
var MSLAYER_RASTER_TYPE = 4;


