/*========================================================================
 * Map Projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 02-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The Mapx class contains methods common to all map transformation classes.
 * It is abstract and not intended to be instantiated directly.
 *
 * To extend, use (replacing CHILDCLASS with the name of the new class) :
 *      CHILDCLASS.prototype = Mapx; 
 *      CHILDCLASS.prototype.parent = Mapx;
 * You can then override any functions.  If you do so and need to call the 
 * parent function, use (within the overriding function) :
 *      this.parent.FUNCTION.call(this);
 * If your object constuctor should set default values for some of the Mapx
 * variables, use this in your constructor:
 *      this.parent.setupvars.apply(this, arguments);  // method #1
 *      this.parent.setupvars.call(this, ...);         // method #2
 * Use #1 if the constructor is not being overridden, or otherwise uses the
 * same parameter order.  Use #2 if the parameters of the new constructor
 * differ; see the setupvars function for the exact parameter order.
 *
 * To use this, you must also import MiscFunctions.js, as it contains some
 * custom non-generic functions that are used by various Mapx subclasses.
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *========================================================================*/


/*-----------------------------------
 * CONSTANTS USED IN VARIOUS DEFAULTS
 *-----------------------------------*/ 

var default_equatorial_radius = 6371.228;
var default_eccentricity = 0.082271673;


/*
 * MAPX OBJECT DECLARATION AND DEFINITION
 */

Mapx = {

    map_projection_name : 'NONE',       // String of the Projection Name
    lat0 : 0.0,                         // Projection origin latitude
    lon0 : 0.0,                         // Projection origin longitiude
    lat1 : 999.0,                       // ??
    lon1 : 999.0,                       // ??
    rotation : 0.0,                     // ?? Rotation factor??
    scale : 1.0,                        // Scale...??? not sure
    center_lat : 0.0,                   // Map origin latitude
    center_lon : 0.0,                   // Map origin longitude
    south : -90.0,                      // Mapx South Transform Boundary
    north : 90.0,                       // Mapx North Transform Boundary
    west : -180.0,                      // Mapx West Transform Boundary
    east : 180.0,                       // Mapx East Transform Boundary
    lat_interval : 30.0,                // ?
    lon_interval : 30.0,                // ?
    label_lat : 0.0,                    // ?
    label_lon : 0.0,                    // ?
    cil_detail : 1,                     // ?
    bdy_detail : 0,                     // ?
    riv_detail : 0,                     // ?
    equatorial_radius : default_equatorial_radius,  // Equator radius of projection (km)
    eccentricity : default_eccentricity, // Ellipsoid eccentricity of projection

    map_straddles_180 : false,          // true if map crosses the 180 longitude
    e2 : 0,                             // eccentricity squared
    e4 : 0,                             // eccentricity ^ 4
    e6 : 0,                             // eccentricity ^ 6
    e8 : 0,                             // eccentricity ^ 8
    Rg : 0,                             // Ratio of Equatorial Radius:Scale
    T00 : 0,                            // Transformation matrix (based on rotation)
    T01 : 0,                            // Transformation matrix
    T10 : 0,                            // Transformation matrix
    T11 : 0,                            // Transformation matrix
    u0 : 0,                             // x offset from projection origin to map origin
    v0 : 0,                             // y offset from projection origin to map origin
    
    /*.............................................................................
     * setupvars - used to set up values, similar to a constructor
     * 
     *   input  : a list of parameters to set up variables.  All but the equatorial
     *              radius and the eccentricity need to be supplied            
     *
     *   result : no return value;
     *.............................................................................*/
    setupvars : function(
        new_map_projection_name, 
        new_lat0, new_lon0, 
        new_lat1, new_lon1, 
        new_rotation, 
        new_scale, 
        new_center_lat, new_center_lon, 
        new_south, new_north, new_west, new_east, 
        new_lat_interval, new_lon_interval, 
        new_label_lat, new_label_lon, 
        new_cil_detail, 
        new_bdy_detail, 
        new_riv_detail, 
        new_equatorial_radius, 
        new_eccentricity) {

        this.map_projection_name = new_map_projection_name;
        this.lat0 = parseFloat(new_lat0);
        this.lon0 = parseFloat(new_lon0);
        this.lat1 = parseFloat(new_lat1);
        this.lon1 = parseFloat(new_lon1);
        this.rotation = parseFloat(new_rotation);
        this.scale = parseFloat(new_scale);
        this.center_lat = parseFloat(new_center_lat);
        this.center_lon = parseFloat(new_center_lon);
        this.south = parseFloat(new_south);
        this.north = parseFloat(new_north);
        this.west = parseFloat(new_west);
        this.east = parseFloat(new_east);
        this.lat_interval = parseFloat(new_lat_interval);
        this.lon_interval = parseFloat(new_lon_interval);
        this.label_lat = parseFloat(new_label_lat);
        this.label_lon = parseFloat(new_label_lon);
        this.cil_detail = parseFloat(new_cil_detail);
        this.bdy_detail = parseFloat(new_bdy_detail);
        this.riv_detail = parseFloat(new_riv_detail);

        if (new_equatorial_radius)
            this.equatorial_radius = parseFloat(new_equatorial_radius);
        else
            this.equatorial_radius = parseFloat(default_equatorial_radius);

        if (new_eccentricity)
            this.eccentricity = parseFloat(new_eccentricity);
        else
            this.eccentricity = parseFloat(new_eccentricity);
    }, // END FUNCTION : setupvars


    /*............................................................................
     * reinit_mapx - Reinitialize mapx constants depending on parameters supplied
     *                  in setupvars.  Should be called upon creating child object
     *
     *   input  : none
     *
     *   result : TRUE if no problems encountered, FALSE otherwise.
     *............................................................................*/
     
    reinit_mapx : function() {
        var theta;
        var u = [999.0], v = [999.0];
        var output = '';

        // Check that Map Transform Bounds are valid
        if (this.east < -180.0 || this.east > 360 
            || this.west < -180.0 || this.west > 360) {

            output = 'reinit_mapx: illegal bounds: west=' + this.west
                   + ',east=' + this.east
                   + '\n             should be >= -180 and <= 360 degrees.';
            alert(output);
            return false;
        }

        if (Math.abs(this.east - this.west) > 360) {
            output = 'reinit_mapx: illegal bounds: west=' + this.west
                   + ',esst=' + this.east
                   + '\n            bounds cannot span > 360 degrees.';
            alert(output);
            return false;
        }

        if (this.east > 180 && this.west > 180) {
            this.east -= 360;
            this.west -= 360;
        }

        // Check whether map straddles the 180 longitude line
        if (this.east < this.west || this.east > 180) 
            this.map_straddles_180 = true;
        else 
            this.map_straddles_180 = false;

        // set series expansion constants (powers of eccentricity)
        this.e2 = Math.pow(this.eccentricity, 2);
        this.e4 = Math.pow(this.eccentricity, 4);
        this.e6 = Math.pow(this.eccentricity, 6);
        this.e8 = Math.pow(this.eccentricity, 8);

        // set scaled radius for spherical and elliptical projections
        this.Rg = this.equatorial_radius / this.scale;

        // set projection-specific constants
        if (!this.initialize())
            return false;

        // create rotation matrix
        theta = radians(this.rotation);

        this.T00 =  Math.cos(theta);
        this.T01 =  Math.sin(theta);
        this.T10 = -Math.sin(theta);
        this.T11 =  Math.cos(theta),

        // get offset from projection origin (lat0, lon0)
        // to this map's origin (center_lat, center_lon)
        this.u0 = this.v0 = 0.0;
        this.forward_mapx( this.center_lat, this.center_lon, u, v );
        this.u0 = u[0];
        this.v0 = v[0];

        return true;   // success
    }, // END FUNCTION : reinit_mapx


    /*......................................................
     * toString - Creates a diagnostic string of mapx object
     *
     *   input  : none
     *
     *   result : A string with the parameters in it
     *......................................................*/
     
    toString : function()
    {
        var returnString
            = 'mapx['
            + this.map_projection_name + ', '
            + this.lat0 + ', '
            + this.lon0 + ', '
            + this.lat1 + ', '
            + this.lon1 + ', '
            + this.rotation + ', '
            + this.scale + ', '
            + this.center_lat + ', '
            + this.center_lon + ', '
            + this.south + ', '
            + this.north + ', '
            + this.west + ', '
            + this.east + ', '
            + this.lat_interval + ', '
            + this.lon_interval + ', '
            + this.label_lat + ', '
            + this.label_lon + ', '
            + this.cil_detail + ', '
            + this.bdy_detail + ', '
            + this.riv_detail + ', '
            + this.equatorial_radius + ', '
            + this.eccentricity + ']';

        return returnString;
    }, // END FUNCTION : toString


    /*...........................................................................
     * within_mapx - Test if lat/lon are within map transformation bounds
     *
     *   input  : lat - A latitude value
     *            lon - A longitude value
     * 
     *   result : TRUE if lat and lon are within the mapx bounds, false otherwise
     *...........................................................................*/
    within_mapx : function(lat, lon) {
        if (lat < this.south || lat > this.north)
            return false;

        lon = normalize(lon);

        if (this.map_straddles_180) {
            if (lon < this.east && lon > this.west)
                return false;
        }
        else {
            if (lon > this.east || lon < this.west)
                return false;
        }

        return true;
    },

    // forward_mapx
    forward_mapx : function(lat, lon, u, v) {
        var status;
        status = this.geo_to_map(lat, lon, u, v);
        return status;
    },

    // inverse_mapx
    inverse_mapx : function(u, v, lat, lon) {
        var status;
        status = this.map_to_geo(u, v, lat, lon);
        return status;
    },

    // initialize - OVERRIDE IN CHILDREN
    initialize : function() {
    },

    // geo_to_map - OVERRIDE IN CHILDREN
    geo_to_map : function(lat, lon, u, v) {
    },

    // map_to_geo - OVERRIDE IN CHILDREN
    map_to_geo : function(x, y, lat, lon) {
    }
}; // END OF DEFINITION: Mapx
/*=========================================================================
 * MiscFunctions - miscellaneous functions useful for map projections
 *
 * 27-Mar-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Original version, pulled from Mapx.js)
 *
 * These functions were pulled out of the Mapx.js file so they could be
 * more universally used without having to import the entire Mapx.js object
 * 
 * Most of these functions can be called without any object qualifiers
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=========================================================================*/


/* FUNCTION LIST
    
    ** OBJECTLESS FUNCTIONS **

    isArray
    radians
    degrees
    normalize
    _normalize_helper

    
    ** MATH PROTOTYPE FUNCTIONS **

    Math.sinh
    Math.cosh
*/



/*---------------------
 * OBJECTLESS FUNCTIONS
 *---------------------/*


/*..........................................................
 * isArray - Determine whether a variable is an array
 *
 *   input  : a variable of any type
 * 
 *   result : true if the variable is an array, false if not
 *..........................................................*/

function isArray() {
    if (typeof(arguments) == 'object') {
        var criterion = arguments[0].constructor.toString().match(/array/i);
        return (criterion != null);
    }
    return false;
}


/*.....................................
 * radians - Convert degrees to radians
 * degrees - Convert radians to degrees
 *
 *   input  : The value to be converted
 *
 *   result : The converted value
 *.....................................*/

function radians(degrees) {
    return degrees * Math.PI / 180;
}

function degrees(radians) {
    return radians * 180 / Math.PI;
}


/*..............................................................................
 * normalize         - push longitude value into -180 to 180 range
 * _normalize_helper - generic for both arrays and scalars
 *
 *   input  : a longitude value, or an array containing a longitude value
 *
 *   result : * TRUE if the parameter is an array; the converted value is stored
 *                directly back into the array.
 *            * FALSE if the parameter is neither an array or a number
 *            * A normalized longitude value if the value is an integer
 *..............................................................................*/
        
function _normalize_helper(lon) {
    var templon = lon;

    if (lon < -180 || lon > 180) {
        templon  = Math.abs(lon) + 180;
        templon -= 360*Math.floor(templon/360);
        templon -= 180;
        if (lon < 0)
            templon = -templon;
    }

    return parseFloat(templon);
}

function normalize(lon) {
    if ( isArray(lon) ) {
        lon[0] = _normalize_helper(lon[0]);

        return true;
    }
    else if ( typeof(lon) == 'number' ) {
        return _normalize_helper(lon);
    }
    else {
        return false;
    }
}


/*...................................................................
 * constrainLat - make sure Latitude is within -90 to 90 degree range
 *
 *   input  : a latitude value
 *
 *   result : the latitude value pushed within -90 to 90
 *...................................................................*/

function constrainLat(lat) {
    lat = parseFloat(lat);

    if (lat >  90.0) lat =  90.0;
    if (lat < -90.0) lat = -90.0;

    return lat;
} 


/*..........................................................................
 * greatCircleLat - Compute the Latitude of a third point on a Great Circle,
 *                  when given 2 full points and the longitude of the third
 *
 *   input  : lat0 - The latitude of the first full point
 *            lon0 - The longitude of the first full point
 *            lat1 - The latitude of the second full point
 *            lon1 - The longitude of the second full point
 *            lon2 - The longitude of the partial point
 *            (NOTE: All values should be in DEGREES)
 *
 *   result : The latitude (lat2) to match lon2.  Returned in DEGREES
 *..........................................................................*/

 function greatCircleLat(lat0, lon0, lat1, lon1, lon2) {
    if (lon0 == lon1) {
        return ( (lat0 + lat1) / 2 );
    }
    else {
        lat0 = radians(lat0);
        lon0 = radians(lon0);
        lat1 = radians(lat1);
        lon1 = radians(lon1);
        lon2 = radians(lon2);

        var numleft  = Math.sin(lat0) * Math.cos(lat1) * Math.sin(lon2-lon1);
        var numright = Math.sin(lat1) * Math.cos(lat0) * Math.sin(lon2-lon0);
        var den      = Math.cos(lat0) * Math.cos(lat1) * Math.sin(lon0-lon1);

        var result = Math.atan( (numleft-numright) / den );

        return degrees(result);
    }        
 }



/*-------------------------
 * MATH PROTOTYPE FUNCTIONS
 *-------------------------*/

/*................................................
 * Math.sinh - Hyperbolic sine
 * Math.cosh - Hyperbolic cosine
 *
 *   input  : The hyperbolic angle, in radians
 *
 *   result : The result of the hyperbolic function
 *.................................................*/

Math.sinh = function(angle) {
    return (Math.exp(angle) - Math.exp(-angle))/2.0;
};

Math.cosh = function(angle) {
    return (Math.exp(angle) + Math.exp(-angle))/2.0;
};


/*.............................................................................
 * Math.nint - Round or Truncate a value to an int
 * 
 *   input  : value - The value to integerize
 *            round - A boolean, set to true for rounding, false for truncating
 *
 *   result : The integerized value
 *.............................................................................*/

Math.nint = function(value, round) {
    value = parseFloat(value);
    if (round) {
        return parseInt(value + 0.5);
    }
    else {
        return parseInt(value);
    }
};


/*...............................................................................
 * Math.sqrDist - Get the square of the distance between two points
 *
 *   input  : Two points.  Each point should be an object with x and y properties
 *
 *   result : The square of the distance between the two points
 *...............................................................................*/

Math.sqrDist = function( pt1, pt2 ) {
    
    var dx = pt1.x - pt2.x;
    var dy = pt1.y - pt2.y;

    return (dx*dx) + (dy*dy);
};
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 06-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The AzimuthalEqualAreaEllipsoid class contains transform methods for
 * the Azimuthal Equal Area projection (ellipsoid)
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 182-190 (formulae on page 187-190, esp 187)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

AzimuthalEqualAreaEllipsoid.prototype = Mapx;
AzimuthalEqualAreaEllipsoid.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function AzimuthalEqualAreaEllipsoid() {
    
    // RELEVANT MAIN VARIABLES
    //   * lat0         - Projection origin latitude
    //   * lon0         - Projection origin longitude
    //   * eccentricity - The eccentricity of the projection
    //   * e2, e4, e6   - Eccentricity expansions
    //   * Rg           - Radius:Scale ratio

    this.parent.setupvars.apply(this, arguments);

    
    // HELPER VARIABLES

    var tiny = 0.00000000001;
    var sin_phi1;
    var cos_phi1;
    var qp, q1;
    var Rq;
    var beta1;
    var sin_beta1;
    var cos_beta1;
    var m1, D;

    // function to get Q values (rather than redundant typing
    this.get_q = function(mysin) {
        
        var e2sin2, two_e, esin, lnx, res;
        
        e2sin2 = this.e2 * mysin * mysin;
        two_e = 2 * this.eccentricity;
        esin = this.eccentricity * mysin;
        lnx = Math.log( (1-esin)/(1+esin) );

        res = (1 - this.e2) * ( mysin/(1-e2sin2) - lnx/two_e );

        return res;
    };

    // initialize overrider
    this.initialize = function() {
        
        sin_phi1 = Math.sin( radians(this.lat0) );
        cos_phi1 = Math.cos( radians(this.lat0) );
    
        // Eccentricty of 0 should never happen, but here it is anyway
        if (this.eccentricity == 0.0) {
            qp = 2.0;
            q1 = 2.0;
        }
        else {
            qp = this.get_q(1);
            q1 = this.get_q(sin_phi1);
        }

        Rq = this.Rg * Math.sqrt(qp/2);
            
        // if qp somehow smaller than q1, set to 90 or -90
        if ( Math.abs(qp) <= Math.abs(q1) ) {
            beta1 = (Math.PI/2) * ( (q1/qp > 0) ? 1 : -1 );
        }
        else {
            beta1 = Math.asin(q1 / qp);
        }

        sin_beta1 = Math.sin(beta1);
        cos_beta1 = Math.cos(beta1);

        m1 = cos_phi1 / Math.sqrt( 1 - this.e2 * sin_phi1 * sin_phi1 );
        D = this.Rg * m1 / (Rq * cos_beta1);

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y;
        var phi, dlam, rho, beta;
        var sin_dlam, cos_dlam;
        var sin_phi, sin_beta, cos_beta, q, B;

        lat = parseFloat(lat);
        lon = parseFloat(lon);

        phi = radians(lat);
        dlam = radians(lon - this.lon0);
        sin_phi = Math.sin(phi);
        sin_dlam = Math.sin(dlam);
        cos_dlam = Math.cos(dlam);
        q = this.get_q(sin_phi);

        if (this.lat0 == 90) {
            if (Math.abs(qp - q) < tiny) {
                rho = 0.0;
            }
            else {
                rho = this.Rg * Math.sqrt(qp - q);
            }
            x =  rho * sin_dlam;
            y = -rho * cos_dlam;
        }
        else if (this.lat0 == -90) {
            if (Math.abs(qp - q) < tiny) {
                rho = 0.0;
            }
            else {
                rho = this.Rg * Math.sqrt(qp + q);
            }
            x = rho * sin_dlam;
            y = rho * cos_dlam;
        }
        else {
            if (Math.abs(q - qp) < tiny) {
                beta = Math.PI/2 * ( (q>0) ? 1 : -1 );
            }
            else {
                beta = Math.asin(q/qp);
            }
            sin_beta = Math.sin(beta);
            cos_beta = Math.cos(beta);

            B = Rq * Math.sqrt(2.0 / (1.0 + sin_beta1*sin_beta
                                          + cos_beta1*cos_beta*cos_dlam));

            x = B * D * cos_beta * sin_dlam;
            y = (B/D) * (cos_beta1*sin_beta - sin_beta1*cos_beta*cos_dlam);
        }

        u[0] = this.T00*x + this.T01*y - this.u0;
        v[0] = this.T10*x + this.T11*y - this.v0;

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, rho, ce, x, y, beta;
        var Rg2, rho2, two_e, lnx;
        var sin_ce, cos_ce, D2;
        var ser1, ser2, ser3;

        u = parseFloat(u);
        v = parseFloat(v);

        if (this.eccentricity == 0.0) {
            alert('Ellipsoid projection being used with no eccentricity!!');
            return false;
        }

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        // polar
        if (this.lat0 == 90.00 || this.lat0 == -90.00) {
            // converting the origin
            if (x == 0.0 && y == 0.0) {
                phi = radians(this.lat0);
                lam = 0.0;
            }
            // non-origin conversions
            else {
                rho = Math.sqrt((x*x) + (y*y));
                if (rho < tiny) rho = tiny;

                Rg2 = this.Rg * this.Rg;
                rho2 = rho * rho;
                two_e = 2 * this.eccentricity;
                lnx = Math.log( (1-this.eccentricity) / (1+this.eccentricity) );

                beta = Math.asin(1 - rho2/( Rg2*( 1 - ((1-this.e2)/two_e)*lnx ) ));

                // south polar
                if (this.lat0 == -90.00) {
                    beta = -beta;
                    lam = Math.atan2(x,y);
                }
                // north polar
                else {
                    lam = Math.atan2(x,-y);
                }
            }
        }
        // non-polar
        else {
            D2 = D*D;
            rho = Math.sqrt( (x*x)/D2 + D2*y*y );
            ce = 2 * Math.asin( rho/(2*Rq) );
            sin_ce = Math.sin(ce);
            cos_ce = Math.cos(ce);

            if (rho < tiny) {
                beta = beta1;
            }
            else {
                beta = Math.asin( cos_ce*sin_beta1 + D*y*sin_ce*cos_beta1/rho );
            }
            
            lam = Math.atan2(x*sin_ce, D*rho*cos_beta1*cos_ce - D2*y*sin_beta1*sin_ce);
        }

        ser1 = this.e2/3 + 31*this.e4/180 + 517*this.e6/5040;
        ser2 =             23*this.e4/360 + 251*this.e6/3780;
        ser3 =                              761*this.e6/45360;

        phi = beta + ser1*Math.sin(2*beta) + ser2*Math.sin(4*beta) + ser3*Math.sin(6*beta);

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0;
        normalize(lon);
       
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : AzimuthalEqualAreaEllipsoid
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 05-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The AzimuthalEqualArea class contains transform methods for the
 * Azimuthal Equal Area projection (spherical)
 *
 * See: USG/P/1395 (Map Projectiosn - A Working Manual, Snyder)
 * Pages: 182-190 (formulae on p.185-187)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

AzimuthalEqualArea.prototype = Mapx;
AzimuthalEqualArea.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function AzimuthalEqualArea() {
    
    // RELEVANT MAIN VARIABLES
    //   * lat0 - projection center latitude
    //   * lon0 - projection center longitude
    //   * Rg   - Radius:Scale ratio

    this.parent.setupvars.apply(this, arguments);

    
    // HELPER VARIABLES
    //   * sin_phi1 - Sine of projection center latitude
    //   * cos_phi1 - Cosine of projection center latitude

    var sin_phi1;
    var cos_phi1;


    // initialize overrider
    this.initialize = function() {
        
        sin_phi1 = Math.sin( radians(this.lat0) );
        cos_phi1 = Math.cos( radians(this.lat0) );

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y;
        var kp, phi, dlam, rho;
        var sin_phis, cos_phis, sin_dlam, cos_dlam;

        lat = parseFloat(lat);
        lon = parseFloat(lon);

        phi = radians(lat);
        dlam = radians(lon - this.lon0); //delta between lon and proj center

        // north polar
        if (this.lat0 == 90) {
            rho = 2 * this.Rg * Math.sin(Math.PI/4 - phi/2);
            x =  rho * Math.sin(dlam);
            y = -rho * Math.cos(dlam);
        }
        // south polar
        else if (this.lat0 == -90) {
            rho = 2 * this.Rg * Math.cos(Math.PI/4 - phi/2);
            x =  rho * Math.sin(dlam);
            y =  rho * Math.cos(dlam);
        }
        // anywhere else
        else {
            sin_phi = Math.sin(phi);
            cos_phi = Math.cos(phi);
            sin_dlam = Math.sin(dlam);
            cos_dlam = Math.cos(dlam);
            
            kp = Math.sqrt(2.0 / ( 1 + sin_phi1*sin_phi 
                                     + cos_phi1*cos_phi*cos_dlam ));
            x = this.Rg * kp * cos_phi * sin_dlam;
            y = this.Rg * kp * (cos_phi1*sin_phi - sin_phi1*cos_phi*cos_dlam);
        }

        u[0] = this.T00*x + this.T01*y - this.u0;
        v[0] = this.T10*x + this.T11*y - this.v0;

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, rho, c, x, y;
        var sin_c, cos_c;
        var num, denom;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        rho = Math.sqrt(x*x + y*y);

        // not projection origin
        if (rho != 0.0) {
            c = 2 * Math.asin( rho / (2*this.Rg) );
            sin_c = Math.sin(c);
            cos_c = Math.cos(c);

            phi = Math.asin( cos_c*sin_phi1 + y*sin_c*cos_phi1/rho );

            // north polar
            if (this.lat0 == 90) {
                lam = Math.atan2(x,-y);
            }
            // south polar
            else if (this.lat0 == -90) {
                lam = Math.atan2(x, y);
            }
            // anywhere else
            else {
                num = x*sin_c;
                denom = rho*cos_phi1*cos_c - y*sin_phi1*sin_c;
            
                lam = Math.atan2(num, denom);
            }
        }
        // projection origin
        else {
            phi = radians(this.lat0);
            lam = 0.0;
        }

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0;
        normalize(lon);

        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : AzimuthalEqualArea
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 15-Jan-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The CylindricalEqualAreaEllipsoid class contains transform methods for 
 * the Cylindrical EqualArea projection (for the ellipsoid).
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 76-85 (formulae on p.81-83)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

CylindricalEqualAreaEllipsoid.prototype = Mapx;
CylindricalEqualAreaEllipsoid.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function CylindricalEqualAreaEllipsoid() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0         - Central Meridian for projection
    //   * lat1         - Standard Parallel for projection
    //   * eccentricity - The eccentricity of the projection
    //   * e2, e4, e6   - Eccentricity expansions
    //   * Rg           - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    
    var k0;
    var qp;


    // function to get Q values (rather than redundant typing)
    this.get_q = function(mysin) {
        
        var e2sin2, two_e, esin, lnx, res;
        
        e2sin2 = this.e2 * mysin * mysin;
        two_e = 2 * this.eccentricity;
        esin = this.eccentricity * mysin;
        lnx = Math.log( (1-esin)/(1+esin) );

        res = (1 - this.e2) * ( mysin/(1-e2sin2) - lnx/two_e );

        return res;
    };


    // initialize overrider
    this.initialize = function() {
        
        var sin_phis, cos_phis;
        
        if (this.lat1 == 999.0)
            this.lat1 = 30.00;

        sin_phis = Math.sin( radians(this.lat0) );
        cos_phis = Math.cos( radians(this.lat0) );

        k0 = cos_phis / Math.sqrt( 1 - this.e2 * sin_phis * sin_phis );

        if (this.eccentricity == 0.0) {
            qp = 2.0;
        }
        else {
            qp = this.get_q(1.0);
        }

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
    
        var x, y, dlon;
        var phi, lam;
        var sin_phi;
        var q;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        sin_phi = Math.sin( phi );
        q = this.get_q(sin_phi);

        x = this.Rg * k0 * lam;
        y = this.Rg * q / (2*k0);

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
    
        var phi, lam, x, y;
        var beta;
        var ser1, ser2, ser3;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        beta = Math.asin( (2.0*y*k0) / (this.Rg*qp) );
        
        ser1 = this.e2/3 + 31*this.e4/180 + 517*this.e6/5040;
        ser2 =             23*this.e4/360 + 251*this.e6/3780;
        ser3 =                              761*this.e6/45360;

        phi = beta + ser1*Math.sin(2*beta) + ser2*Math.sin(4*beta) + ser3*Math.sin(6*beta);
        lam = x / (this.Rg * k0);

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);

        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : CylindricalEqualEllipsoid
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 15-Jan-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The CylindricalEqualArea class contains transform methods for the
 * Cylindrical EqualArea projection (for the sphere).
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 76-85 (formulae on p.77,80-81)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

CylindricalEqualArea.prototype = Mapx;
CylindricalEqualArea.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function CylindricalEqualArea() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0 - Central Meridian for projection
    //   * lat1 - Standard Parallel for projection
    //   * Rg   - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * cos_phi1 - Cosine of lat1
    
    var cos_phi1;
    


    // initialize overrider
    this.initialize = function() {
        if (this.lat1 == 999.0)
            this.lat1 = 30.00;

        cos_phi1 = Math.cos( radians(this.lat1) );

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;
        var sin_phi;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        sin_phi = Math.sin( phi );

        x = this.Rg * lam * cos_phi1;
        y = this.Rg * (sin_phi/cos_phi1);

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        phi = Math.asin( y * cos_phi1/this.Rg );
        lam = x / (this.Rg * cos_phi1);

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : CylindricalEqualArea
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 02-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The CylindricalEquidistant class contains transform methods for the
 * Cylindrical Equidistant projection.
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 90-91 (formulae on p.91)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

CylindricalEquidistant.prototype = Mapx;
CylindricalEquidistant.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function CylindricalEquidistant() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0 - Central Meridian for projection
    //   * lat1 - Standard Parallel for projection
    //   * Rg   - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * cos_phi1 - Cosine of lat1
    
    var cos_phi1;
    


    // initialize overrider
    this.initialize = function() {
        if (this.lat1 == 999.0)
            this.lat1 = 0.00;

        cos_phi1 = Math.cos( radians(this.lat1) );

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        x = this.Rg * lam * cos_phi1;
        y = this.Rg * phi;

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        phi = y / this.Rg;
        lam = x / (this.Rg * cos_phi1);

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : CylindricalEquidistant
/*========================================================================
 * grids - Grid Coordinate System definition and Transformations 
 *
 * 26-Dec-1991 K.Knowles knowles@kryos@colorado.edu 303-492-0644
 *      (original C code)
 * 20-Apr-1998 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 19-Jul-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 13-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The Grids class loads and initializes grid information for use with a
 * Map projection
 *
 * NOTE: You must also manually import the Tokenizer.js file to enable
 *       gdp file parsing!
 *       You must import MapMaker.js for this to function correctly.
 *       (See MapMaker.js for additional required imports)
 *
 * TO USE:
 *   - Create a Grids object:  x = new Grids();
 *   - Create a MapX projection: y = new ABC(...);
 *          // ABC is the Projection Object, like CylindricalEquidistant
 *   - Initialize the object:  x.initialize_grid(..., y);
 *          // Parameters can vary, see function below.  y is the MapX
 *   - Call the conversion functions: 
 *          // x.forward_grid(...) OR x.inverse_grid(...)
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *========================================================================*/


var gpdfiles = [
        ['East.gpd','East.mpp\n1000 1000\n1000 1000\n500.0 500.0'],
        ['Eorthographic.gpd','Eorthographic.mpp\n500.0 250.0\n250.0 250.0\n250.0 125.0'],
        ['fiveminute.gpd','onedegree.mpp\n4320 2160\n12 12\n2159.5 1079.5'],
        ['halfdegree.gpd','onedegree.mpp\n720 360\n2 2\n359.5 179.5'],
        ['Mh.gpd','M200correct.mpp\n2766 1171\n16 16\n1382.0 585.0'],
        ['Ml.gpd','M200correct.mpp\n1383 586\n8 8\n691.0 292.5'],
        ['MOD_Sin.gpd','Sinusoidal.mpp\n3600 1800\n1 1\n1800 900'],
        ['Moll1:100.gpd','Mollweide.mpp\n781.0 344.0\n24.0 24.0\n390.5 172.0'],
        ['Moll1:160.gpd','Mollweide.mpp\n490.0 216.0\n15.0 15.0\n245.0 108.0'],
        ['Moll1:40.gpd','Mollweide.mpp\n2160.0 1080.0\n60.0 60.0\n1080.0 540.0'],
        ['Moll1:80.gpd','Mollweide.mpp\n974.0 427.0\n30.0 30.0\n487.0 213.5'],
        ['Moll.gpd','Mollweide.mpp\n490.0 216.0\n15.0 15.0\n245.0 108.0'],
        ['N3A.gpd','Nps.mpp\n608 896\n8 8\n307.5 467.5'],
        ['N3B.gpd','Nps.mpp\n304 448\n4 4\n153.5 233.5'],
        ['Na1.gpd','N200avhrr.mpp\n7220 7220\n160 160\n3609.5 3609.5'],
        ['Na25.gpd','N200correct.mpp\n361 361\n8 8\n180.5 180.5'],
        ['Na5.gpd','N200correct.mpp\n1805 1805\n40 40\n902.5 902.5'],
        ['Nh.gpd','N200correct.mpp\n1441 1441\n16 16\n720.0 720.0'],
        ['Nl.gpd','N200correct.mpp\n721 721\n8 8\n360.0 360.0'],
        ['North.gpd','North.mpp\n1000 1000\n1000 1000\n500.0 500.0'],
        ['NpathP.gpd','NpathP.mpp\n67 67\n1 1\n33 33'],
        ['onedegree.gpd','onedegree.mpp\n360 180\n1 1\n179.5 89.5'],
        ['quarterdegree.gpd','onedegree.mpp\n1440 720\n4 4\n719.5 359.5'],
        ['S3A.gpd','Sps.mpp\n632 644\n8 8\n315.5 347.5'],
        ['S3B.gpd','Sps.mpp\n316 332\n4 4\n157.5 157.5'],
        ['Sa1.gpd','S200avhrr.mpp\n6420 6420\n160 160\n3209.5 3209.5'],
        ['Sa25.gpd','S200correct.mpp\n321 321\n8 8\n160.0 160.0'],
        ['Sa5.gpd','S200correct.mpp\n1605 1605\n40 40\n802.5 802.5'],
        ['Sh.gpd','S200correct.mpp\n1441 1441\n16 16\n720.0 720.0'],
        ['Sl.gpd','S200correct.mpp\n721 721\n8 8\n360.0 360.0'],
        ['South.gpd','South.mpp\n1000 1000\n1000 1000\n500.0 500.0'],
        ['thirtyseconds.gpd','onedegree.mpp\n43200 21600\n120 120\n21599.5 10799.5'],
        ['West.gpd','West.mpp\n1000 1000\n1000 1000\n500.0 500.0'],
        ['World.gpd','World.mpp\n2000 1000\n1000 1000\n1000.0 500.0'],
        ['Worthographic.gpd','Worthographic.mpp\n500.0 250.0\n250.0 250.0\n250.0 125.0']
    ];



function Grids() {

    this.map_origin_col = 999;
    this.map_origin_row = 999;
    this.cols_per_map_unit = 999;
    this.rows_per_map_unit = 999;
    this.cols = 999;
    this.rows = 999;

    this.map = null;

    this.mpp_filename = 'NONE';


    /*.................................................................
     * initialize_grid - Loads and/or initializes the Grid object
     *
     * MULTIPLE INPUTS SEQUENCES EXIST FOR THIS FUNCTION.
     * All forms return a copy of this Grids object
     *
     * ** LOAD PRESET FILE **
     *   input  : filename - the name of the gpd file to open
     *
     * ** MANUAL INITIALIZATION **
     *   input  : new_map_origin_col - the map origin column
     *            new_map_origin_row - the map origin row
     *            new_cols_per_map_unit - the # of columns per map unit
     *            new_rows_per_map_unit - the # of rows per map unit
     *            new_cols - the total number of columns
     *            new_rows - the total number of rows
     *            new_map - A MapX projection object (this should be 
     *                      created and initialized prior to sending)
     *.................................................................*/
     
    this.initialize_grid = function() {
        if (arguments.length == 1) {
            return _initialize_grid_filename.apply(this, arguments);
        }
        else if (arguments.length == 7) {
            return _initialize_grid_manual.apply(this, arguments);
        }
    }; // END initialize_grid

    

    /*..................................................................
     * copy_grid - Copys this grid object into a new object
     *
     *   input  : None
     *   
     *   result : A new Grids object with the same variables as this one
     *..................................................................*/
     
    this.copy_grid = function() {
        var newGrid = new Grids();

        newGrid.initialize_grid(
            this.map_origin_col, this.map_origin_row,
            this.cols_per_map_unit, this.rows_per_map_unit,
            this.cols, this.rows,
            this.map);
        newGrid.mpp_filename = this.mpp_filename;

        return newGrid;
    }; // END copy_grid



    /*.....................................................................
     * forward_grid - Translates a latitude/longitude into r/s for the map,
     *                based on the current map projection
     *
     *   input  : lat, lon - the Latitude and Longitude to convert
     *            r, s - arrays that will hold the converted values in [0]
     *
     *   result : true if no problems in converting, false otherwise
     *.....................................................................*/
                                        
    this.forward_grid = function(lat, lon, r, s) {
        var status;
        var u = [999.0], v = [999.0];

        lat = parseFloat(lat);
        lon = parseFloat(lon);

        if (null == this.map) {
            alert ('The map has not been initialized!!!');
            return false;
        }

        if (!this.map.within_mapx(lat, lon)) {
            return false;
        }

        status = this.map.forward_mapx(lat, lon, u, v);
        if (!status) return false;

        r[0] = this.map_origin_col + u[0]*this.cols_per_map_unit;
        s[0] = this.map_origin_row - v[0]*this.rows_per_map_unit;

        //if ( r[0] < -0.5 || r[0] >= (this.cols - 0.5)
        //    || s[0] < -0.5 || s[0] >= (this.rows - 0.5) )
        //    return false;
        //else
            return true;        
    }; // END forward_grid



    /*........................................................................
     * inverse_grid - Translates an r/s from a map to a latitude/longitude,
     *                based on the current map projection
     *
     *   input  : r, s - the r and s values from the map
     *
     *   result : lat, lon - arrays that will hold the converted values in [0]
     *........................................................................*/
     
    this.inverse_grid = function(r, s, lat, lon) {
        var status;
        var u, v;

        r = parseFloat(r);
        s = parseFloat(s);

        u =  (r - this.map_origin_col) / this.cols_per_map_unit;
        v = -(s - this.map_origin_row) / this.rows_per_map_unit;

        if (null == this.map) {
            alert('The map has not been initialized!!!');
            return false;
        }        

        status = this.map.inverse_mapx(u, v, lat, lon);
        if (!status) return false;

        return this.map.within_mapx(lat[0], lon[0]);
    }; // END inverse_grid



    /*....................................................
     * toString - dumps the data in the object to a String
     *
     *   input  : none
     *
     *   result : A string with the object data in it
     *....................................................*/
     
    this.toString = function() {
        var tempstr = 'grids['
                    + 'mpp filename: ' + this.mpp_filename + ', '
                    + 'col origin: ' + this.map_origin_col + ', '
                    + 'row origin: ' + this.map_origin_row + ', '
                    + 'cols pmu: ' + this.cols_per_map_unit + ', '
                    + 'rows pmu: ' + this.rows_per_map_unit + ', '
                    + 'cols: ' + this.cols + ', '
                    + 'rows: ' + this.rows + ', ';

        if (this.map == null)
            tempstr += 'map: [NULL] ]';
        else
            tempstr += 'map: ' + this.map + ' ]';

        return tempstr;
    }; // END toString
    


// BEGINNING OF INTERNAL PRIVATE FUNCTIONS


    /*..................................................................
     * _initialize_grid_manual - Function for manual grid initialization
     *
     *   input  : new_map_origin_col - the map origin column
     *            new_map_origin_row - the map origin row
     *            new_cols_per_map_unit - # of columns per map unit
     *            new_rows_per_map_unit - # of rows per map unit
     *            new_cols - # of columns
     *            new_rows - # of rows
     *            new_map - A MapX projection object
     *
     *   result : A pointer to "this"
     *..................................................................*/

    var _initialize_grid_manual = function(
        new_map_origin_col, new_map_origin_row,
        new_cols_per_map_unit, new_rows_per_map_unit,
        new_cols, new_rows,
        new_map) {

        this.map_origin_col = parseFloat(new_map_origin_col);
        this.map_origin_row = parseFloat(new_map_origin_row);
        this.cols_per_map_unit = parseFloat(new_cols_per_map_unit);
        this.rows_per_map_unit = parseFloat(new_rows_per_map_unit);
//        this.cols = parseInt(new_cols);
//        this.rows = parseInt(new_rows);
        this.cols = parseFloat(new_cols);
        this.rows = parseFloat(new_rows);

        this.map = new_map;

        return this;
    }; // END _initialize_grid_manual        


    /*..........................................................
     * _initialize_grid_filename - Tries to open a gpd file
     *
     *   input  : filename - the name of the gpd file (no path)
     *
     *   result : A pointer to "this" if successful, null if not
     *..........................................................*/
     
     var _initialize_grid_filename = function(filename) {
        var x;
        var tempMaker;

        for (x = 0; x < gpdfiles.length; x++) {
            if (gpdfiles[x][0] == filename) {
                if (_parse_grid_info.call(this, gpdfiles[x][1]))
                {
                    tempMaker = new MapMaker();
                    this.map = tempMaker.createMapx(this.mpp_filename);
                    return this;
                }
                else
                    return null;
            }
        }

        return null;
     };

     
    /*..................................................................
     * _parse_grid_info - Function for parsing grid info from a string
     *
     *   input  : grid_string - A string containing the grid information
     *
     *   result : True if successful, false if not
     *..................................................................*/

     var _parse_grid_info = function(grid_string) {
        var token;
        var tokstr = new Tokenizer(grid_string);
        tokstr.eolIsSignificant(true);
        tokstr.combineSymbols(true);

        token = tokstr.nextToken();
        this.mpp_filename = token;
        if (!tokstr.isNewline(token=tokstr.nextToken())) {
            alert('Invalid gpd file format on line ' + tokstr.lineno() + ' ('+token+')');
            return false;
        }

        token = tokstr.nextToken();
        this.cols = parseInt(token);
        token = tokstr.nextToken();
        this.rows = parseInt(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            alert('Invalid gpd file format on line ' + tokstr.lineno());
            return false;
        }

        token = tokstr.nextToken();
        this.cols_per_map_unit = parseFloat(token);
        token = tokstr.nextToken();
        this.rows_per_map_unit = parseFloat(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            alert('Invalid gpd file format on line ' + tokstr.lineno());
            return false;
        }

        token = tokstr.nextToken();
        this.map_origin_col = parseFloat(token);
        token = tokstr.nextToken();
        this.map_origin_row = parseFloat(token);
        
        return true;
     }; // END _parse_grid_info 





} // END GRIDS CLASS
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 18-Jan-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The LambertConicConformalEllipsoid class contains transform methods 
 * for the Lambert Conformal Conic projection (ellipsoid).
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 104-109 (formulae on pp.107-109)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

LambertConicConformalEllipsoid.prototype = Mapx;
LambertConicConformalEllipsoid.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function LambertConicConformalEllipsoid() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0         - Central Meridian for projection
    //   * lat0         - Standard Parallel for projection (#1)
    //   * lat1         - Standard Parallel for projection (#2)
    //   * Rg           - Radius:Scale ratio
    //   * eccentricity - Eccentricity of the projection
    //   * e2,e4,e6,e8  - Eccentricity expansions
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    
    var n;
    var rho0;
    var F;
    

    // function to get T values (rather than redundant typing)
    this.get_t = function(mysin) {
        var esinphi;
        var res;

        esinphi = this.eccentricity * mysin;

        res = Math.sqrt( (1.0-mysin)/(1.0+mysin) *
                         Math.pow( (1.0+esinphi)/(1.0-esinphi), this.eccentricity ) );

        return res;
    };


    // initialize overrider
    this.initialize = function() {
        var cos_phi0, cos_phi1;
        var sin_phi0, sin_phi1;
        var m0, m1, t0, t1;

        cos_phi0 = Math.cos( radians(this.lat0) );
        sin_phi0 = Math.sin( radians(this.lat0) );

        cos_phi1 = Math.cos( radians(this.lat1) );
        sin_phi1 = Math.sin( radians(this.lat1) );

        m0 = cos_phi0 / Math.sqrt( 1 - this.e2 * sin_phi0 * sin_phi0 );
        m1 = cos_phi1 / Math.sqrt( 1 - this.e2 * sin_phi1 * sin_phi1 );

        t0 = this.get_t( sin_phi0 );
        t1 = this.get_t( sin_phi1 );

        n = (Math.log(m0)-Math.log(m1)) / (Math.log(t0)-Math.log(t1));
        F = m0 / (n*Math.pow(t0,n));
        rho0 = this.Rg * F * Math.pow(t0,n);

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;
        var sin_phi;
        var t, rho, theta;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        sin_phi = Math.sin(phi);
        t = this.get_t(sin_phi);
        rho = this.Rg * F * Math.pow(t,n);
        theta = n * lam;

        x = rho * Math.sin(theta);
        y = rho0 - (rho * Math.cos(theta));

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;
        var rho, t, chi, theta;
        var sign_n;
        var ser1, ser2, ser3, ser4, ser5;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        if (n < 0.0) 
            sign_n = -1;
        else
            sign_n = 1;

        rho = sign_n * Math.sqrt(x*x + ((rho0-y)*(rho0-y)));
        t = Math.pow( (rho / (this.Rg*F)), (1/n) );
        chi = Math.PI/2.0 - 2.0*Math.atan(t);
        theta = Math.atan( (sign_n*x) / (sign_n*rho0 - sign_n*y) );
        
        lam = theta/n;

        ser1 = chi;
        ser2 = Math.sin(2*chi) * (this.e2/2 + 5*this.e4/24 +    this.e6/12  +  13*this.e8/360);
        ser3 = Math.sin(4*chi) * (            7*this.e4/48 + 29*this.e6/240 + 811*this.e8/11520);
        ser4 = Math.sin(6*chi) * (                            7*this.e6/120 +  81*this.e8/1120);
        ser5 = Math.sin(8*chi) * (                                           4279*this.e8/161280);

        phi = ser1 + ser2 + ser3 + ser4 + ser5;        

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : LambertConicConformalEllipsoid
/*========================================================================
 * Map Projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 13-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The MapMaker class loads and initializes a Map Transformation
 *
 * NOTE: You must also manually import the following files:
 *       * Mapx.js
 *       * Tokenizer.js
 *       * AzimuthalEqualArea.js
 *       * AzimuthalEqualAreaEllipsoid.js
 *       * CylindricalEqualAreaEllipsoid.js
 *       * CylindricalEqualArea.js
 *       * CylindricalEquidistant.js
 *       * LambertConicConformalEllipsoid.js
 *       * Mercator.js
 *       * Molleweide.js
 *       * Orthographic.js
 *       * PolarStereographic.js
 *       * PolarStereographicEllipsoid.js
 *       * Sinusoidal.js
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *========================================================================*/


var mppfiles = [
        ['East.mpp','Orthographic\n0.0 90.0 999 999\n0.0\n13000.0\n0.0 90.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 0.0\n1 0 0'],
        ['Eorthographic.mpp','ORTHOGRAPHIC\n0.0 90.0 999.0 999.0\n0.0\n13000.0\n0.0 90.0\n'
                   +'-90.0 90.0\n0.0 180.0\n30.0 30.0\n0.0 0.0\n1 0 0\n6371.228\n0.08227167'],
        ['M200correct.mpp','Cylindrical-Equal-Area\n0.0 0.0 30.0\n0.0\n200.5402\n0.0 0.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 0.0\n1 0 0'],
        ['Mollweide.mpp','Mollweide\n0 0 0 999\n0\n1111.9894\n0 0\n'
                   +'-90 90\n-180 180\n30 30\n0 180\n1 0 0\n6371.23\n0.0822717'],
        ['N200avhrr.mpp','Azimuthal-Equal-Area\n90.0 0.0\n0.0\n200.5402\n90.0 0.0\n'
                   +'0.0 90.0\n-180.0 180.0\n15.0 30.0\n0.0 0.0\n1 0 0'],
        ['N200correct.mpp','Azimuthal-Equal_area\n90.0 0.0\n0.0\n200.5402\n90.0 0.0\n'
                   +'0.0 90.0\n-180.0 180.0\n15.0 30.0\n0.0 0.0\n1 0 0'],
        ['North.mpp','PolarStereographic\n90.0 0.0 999 999\n0.0\n26000.0\n90.0 0.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 180.0\n1 0 0'],
        ['NpathP.mpp','Azimuthal-Equal-Area\n90.0 90.0\n0.0\n100.2701\n90.00 00.00\n'
                   +'0.00 90.00\n-180.00 180.00\n5.00 20.00\n0.00 00.00\n1 0 0'],
        ['Nps.mpp','PolarStereographicEllipsoid\n90.0 -45.0 70.0\n0.0\n100.0\n90.00 135.00\n'
                   +'20.00 90.00\n-180.00 180.00\n10.00 15.00\n00.00 00.00\n1 0 0\n6378.273\n0.081816153'],
        ['onedegree.mpp','CylindricalEquidistant\n0.0 0.0 0.0\n0.0\n1.0\n0.0 0.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n10.00 10.00\n0.00 00.00\n1 0 0\n57.295780'],
        ['S200avhrr.mpp','Azimuthal-Equal-Area\n-90.0 0.0\n0.0\n200.5402\n-90.0 0.0\n'
                   +'-90.0 0.0\n-180.0 180.0\n15.0 30.0\n0.0 0.0\n1 0 0'],
        ['S200correct.mpp','Azimuthal-Equal-Area\n-90.0 0.0\n0.0\n200.5402\n-90.0 0.0\n'
                   +'-90.0 0.0\n-180.0 180.0\n15.0 30.0\n0.0 0.0\n1 0 0'],
        ['Sinusoidal.mpp','Sinusoidal\n0 0 0 999\n0.0\n11.119894\n0.0 0.0\n'
                   +'-90 90\n-180 180\n30 30\n0.0 0.0\n1 0 0\n6371.23\n0.0822717'],
        ['South.mpp','PolarStereographic\n-90.0 0.0 999 999\n0.0\n26000.0\n-90.0 0.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 180.0\n1 0 0'],
        ['Sps.mpp','PolarStereographicEllipsoid\n-90.0 0.0 -70.0\n0.0\n100.0\n-90.00 0.00\n'
                   +'-90.00 -20.00\n-180.00 180.00\n10.00 15.00\n0.00 0.00\n1 0 0\n6378.273\n0.081816153'],
        ['SWorld.mpp','CylindricalEquidistant\n0.0 0.0 0.0 999\n0.0\n20000.0\n0.0 180.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 180.0\n1 0 0'],
        ['West.mpp','Orthographic\n0.0 -90.0 999 999\n0.0\n13000.0\n0.0 -90.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 0.0\n1 0 0'],
        ['world.mpp','CYLINDRICALEQUIDISTANT\n0 0 0 999\n0\b20000\n0 0\n'
                   +'-90 90\n-180 180\n30 30\n0 180\n1 0 0\n6371.23\n0.0822717'],
        ['World.mpp','CylindricalEquidistant\n0.0 0.0 0.0 999\n0.0\n20000.0\n0.0 0.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 180.0\n1 0 0'],
        ['Worthographic.mpp','ORTHOGRAPHIC\n0.0 -90.0 999.0 999.0\n0.0\n13000.0\n0.0 -90.0\n'
                   +'-90.0 90.0\n-180.0 180.0\n30.0 30.0\n0.0 0.0\n1 0 0\n6371.228\n0.08227167']
    ];


function MapMaker() {

    this.map_projection_name = '';
    this.lat0 = 0.0;
    this.lon0 = 0.0;
    this.lat1 = 999;
    this.lon1 = 999;
    this.rotation = 0.0;
    this.scale = 1.0;
    this.center_lat = 0.0;
    this.center_lon = 0.0;
    this.south = -90.0;
    this.north = 90.0;
    this.west = -180.0;
    this.east = 180.0;
    this.lat_interval = 30.0;
    this.lon_interval = 30.0;
    this.label_lat = 0.0;
    this.label_lon = 0.0;
    this.cil_detail = 1;
    this.bdy_detail = 0;
    this.riv_detail = 0;
    this.equatorial_radius = default_equatorial_radius;
    this.eccentricity = default_eccentricity;


    /*........................................................
     * createMapx - Creates a new MapX object
     *
     * MULTIPLE INPUT SEQUENCES EXIST FOR THIS FUNCTION.
     * All forms return a copy of the MapX object created
     *
     * ** LOAD PRESET FILE **
     *   input  : filename - the name of the mpp file to open
     *
     * ** MANUAL INITIALIZATION **
     *   input  : new_projection_name - the type of projection
     *            new_lat0
     *            new_lon0
     *            new_lat1
     *            new_lon1
     *            new_rotation
     *            new_scale
     *            new_center_lat
     *            new_center_lon
     *            new_south
     *            new_north
     *            new_west
     *            new_east
     *            new_lat_interval
     *            new_lon_interval
     *            new_label_lat
     *            new_label_lon
     *            new_cil_detail
     *            new_bdy_detail
     *            new_riv_detail
     *            new_equatorial_radius (optional)
     *            new_eccentricity (optional)
     *.........................................................*/

     this.createMapx = function() {
        var load_ok = false;
     
        if (arguments.length == 1) {
            load_ok = _createMapx_filename.apply(this, arguments);
        }
        else if (arguments.length == 2) {
            load_ok = _createMapx_URL.apply(this, arguments);
        }
        else {
            load_ok = _createMapx_manual.apply(this, arguments);
        }

        if (load_ok) 
            return _createMap.call(this);
        else
            return null;
    }; // END _createMapx


// BEGINNING OF INTERNAL PRIVATE FUNCTIONS


    /*.............................................................
     * _createMap - Function for creating the actual MapX object
     *
     *   input  : None.  (Uses this.map_projection_name)
     *
     *   result : A MapX child object, based on the projection name
     *.............................................................*/

    var _createMap = function() {
        var tempMapx;
        
        if (this.map_projection_name == '') {
            return null;
        }
        else {
            eval('tempMapx = new ' + this.map_projection_name + '('
                +'this.map_projection_name, '
                +'this.lat0, this.lon0, this.lat1, this.lon1, '
                +'this.rotation, this.scale, this.center_lat, this.center_lon, '
                +'this.south, this.north, this.west, this.east, '
                +'this.lat_interval, this.lon_interval, '
                +'this.label_lat, this.label_lon, '
                +'this.cil_detail, this.bdy_detail, this.riv_detail, '
                +'this.equatorial_radius, this.eccentricity)');

            tempMapx.reinit_mapx();

            return tempMapx;
        }
    }; // END _createMap

     

    /*............................................................
     * _createMapx_manual - Function for manual map initialization
     *
     *   input  : new_projection_name - the name of the projection
     *            new_lat0
     *            new_lon0
     *            new_lat1
     *            new_lon1
     *            new_rotation
     *            new_scale
     *            new_center_lat
     *            new_center_lon
     *            new_south
     *            new_north
     *            new_west
     *            new_east
     *            new_lat_interval
     *            new_lon_interval
     *            new_label_lat
     *            new_label_lon
     *            new_cil_detail
     *            new_bdy_detail
     *            new_riv_detail
     *            new_equatorial_radius (optional)
     *            new_eccentricity (optional)
     *
     *    return: True if successful, false if not     
     *............................................................*/

    var _createMapx_manual = function(
        new_projection_name,
        new_lat0, new_lon0, new_lat1, new_lon1,
        new_rotation,
        new_scale,
        new_center_lat, new_center_lon,
        new_south, new_north, new_west, new_east,
        new_lat_interval, new_lon_interval,
        new_label_lat, new_label_lon,
        new_cil_detail, new_bdy_detail, new_riv_detail,
        new_equatorial_radius,
        new_eccentricity) {
        this.map_projection_name = _standard_name(new_projection_name);
        this.lat0 = new_lat0;
        this.lon0 = new_lon0;
        this.lat1 = new_lat1;
        this.lon1 = new_lon1;
        this.rotation = new_rotation;
        this.scale = new_scale;
        this.center_lat = new_center_lat;
        this.center_lon = new_center_lon;
        this.south = new_south;
        this.north = new_north;
        this.west = new_west;
        this.east = new_east;
        this.lat_interval = new_lat_interval;
        this.lon_interval = new_lon_interval;
        this.label_lat = new_label_lat;
        this.label_lon = new_label_lon;
        this.cil_detail = new_cil_detail;
        this.bdy_detail = new_bdy_detail;
        this.riv_detail = new_riv_detail;

        if (new_equatorial_radius != null)
            this.equatorial_radius = new_equatorial_radius;
        if (new_eccentricity != null)
            this.eccentricity = new_eccentricity;

        return true;
    }; // END _createMapx_manual
        


    /*..........................................................
     * _createMapx_filename - Tries to open a hardcoded mpp file
     *
     *   input  : filename - the name of the mpp file (no path)
     *
     *   result : true if successful, false if not
     *..........................................................*/

    var _createMapx_filename = function(filename) {
        var x;

        for (x = 0; x < mppfiles.length; x++) {
            if (mppfiles[x][0] == filename) {
                if (_parse_map_info.call(this, mppfiles[x][1]))
                    return true;
                else
                    return false;
            }
        }

        return false;
    };


    /*....................................................
     * _createMapx_URL - Tries to open a URL to a mpp file
     *
     *   input  : server - the server & path to the file
     *            filename - the actual mpp file
     *
     *   result : true if successful, false if not
     *
     *....................................................*/

     var _createMapx_URL = function(server, filename) {
        var ajax;

        if (window.XMLHttpRequest) {
            ajax = new XMLHttpRequest();
        }
        else {
            ajax = new ActiveXObject("Microsoft.XMLHTTP");
        }

        if (ajax) {
            var url = server;
            
            url += filename;

            ajax.open("GET", url, false);
            ajax.send(null);
            
            if (ajax.status != 200) {
                alert('There was an error opening the file: ' + ajax.statusText);
                return false;
            }
            else {
                if (_parse_map_info.call(this, ajax.responseText))
                    return true;
                else
                    return false;
            }
        }
        else {
            alert ('Cannot create AJAX object.  Aborting...');
            return false;
        }
    };
    

    /*................................................................
     * _parse_map_info - Function for parsing map info from a string
     *
     *   input  : map_string - A string containing the map information
     *
     *   result : True if successful, false if not
     *................................................................*/

    var _parse_map_info = function(map_string) {
        var token;
        var tokstr = new Tokenizer(map_string);
        tokstr.eolIsSignificant(true);
        tokstr.combineSymbols(true);

        token = tokstr.nextToken();
        this.map_projection_name = _standard_name(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'Projection Name', token);
            return false;
        }

        token = tokstr.nextToken();
        this.lat0 = parseFloat(token);
        token = tokstr.nextToken();
        this.lon0 = parseFloat(token);
        token = tokstr.nextToken();
        if (!tokstr.isNewline(token)) {
            this.lat1 = parseFloat(token);
            token = tokstr.nextToken();
            if (!tokstr.isNewline(token)) {
                this.lon1 = parseFloat(token);
                token = tokstr.nextToken();
            }
            if (!tokstr.isNewline(token)) {
                _parse_error(tokstr.lineno(), 'Lat/Lons', token);
                return false;
            }
        }

        token = tokstr.nextToken();
        this.rotation = parseFloat(token);
        if (!tokstr.isNewline(token=tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'Rotation', token);
            return false;
        }        
        
        token = tokstr.nextToken();
        this.scale = parseFloat(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'Scale', token);
            return false;
        }

        token = tokstr.nextToken();
        this.center_lat = parseFloat(token);
        token = tokstr.nextToken();
        this.center_lon = parseFloat(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'Center', token);
            return false;
        }

        token = tokstr.nextToken();
        this.south = parseFloat(token);
        token = tokstr.nextToken();
        this.north = parseFloat(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'N/S', token);
            return false;
        }

        token = tokstr.nextToken();
        this.west = parseFloat(token);
        token = tokstr.nextToken();
        this.east = parseFloat(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'W/E', token);
            return false;
        }

        token = tokstr.nextToken();
        this.lat_interval = parseFloat(token);
        token = tokstr.nextToken();
        this.lon_interval = parseFloat(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'Intervals', token);
            return false;
        }

        token = tokstr.nextToken();
        this.label_lat = parseFloat(token);
        token = tokstr.nextToken();
        this.label_lon = parseFloat(token);
        if (!tokstr.isNewline(tokstr.nextToken())) {
            _parse_error(tokstr.lineno(), 'Labels', token);
            return false;
        }

        token = tokstr.nextToken();
        this.cil_detail = parseInt(token);
        token = tokstr.nextToken();
        this.bdy_detail = parseInt(token);
        token = tokstr.nextToken();
        this.riv_detail = parseInt(token);

        token = tokstr.nextToken();
        if (token == null)
            return true;
        if (!tokstr.isNewline(token)) {
            _parse_error(tokstr.lineno(), 'CIL/BDY/RIV', token);
            return false;
        }
        
        token = tokstr.nextToken();
        if (token == null)
            return true;
        this.equatorial_radius = parseFloat(token);
        token = tokstr.nextToken();
        if (token == null)
            return true;
        if (!tokstr.isNewline(token)) {
            _parse_error(tokstr.lineno(), 'Radius', token);
            return false;
        }

        token = tokstr.nextToken();
        this.eccentricity = parseFloat(token);

        return true;
    }; // END _parse_map_info


    
    /*.................................................
     * _parse_error - Prints an error from parsing file
     *
     *  input  : line - the line number for the error
     *           expected - the type of value expected
     *           found - the value found
     *
     *  result : None
     *.................................................*/

    var _parse_error = function(line, expected, found) {
        alert ('Invalid mpp file format on line ' + line + '.\n\n' 
              +'Expected: ' + expected + '\n'
              +'Found: ' + found);
    }; // END _parse_error



    /*...............................................
     * _standard_name - Standardizes projection names
     *
     *  input  : projname - A projection name
     *
     *  result : The projection name, standardized
     *...............................................*/

     var _standard_name = function(projname) {
        var newname = projname;

        projname = projname.replace(/_/g, '');
        projname = projname.replace(/-/g, '');
        projname = projname.replace(/ /g, '');
        projname = projname.replace(/\(/g, '');
        projname = projname.replace(/\)/g, '');
        projname = projname.toUpperCase();

        if ( projname.match(/AZIMUTHAL/) && projname.match(/EQUALAREA/) ) {
            if (projname.match(/ELLIPSOID/)) {
                newname = 'AzimuthalEqualAreaEllipsoid';
            }
            else {
                newname = 'AzimuthalEqualArea';
            }
        }
        else if ( projname.match(/CYLINDRICAL/) ) {
            if (projname.match(/EQUALAREA/)) {
                if (projname.match(/ELLIPSOID/)) {
                    newname = 'CylindricalEqualAreaEllipsoid';
                }
                else {
                    newname = 'CylindricalEqualArea';
                }
            }
            else if (projname.match(/EQUIDISTANT/)) {
                newname = 'CylindricalEquidistant';
            }
            else {
                newname = '';
            }
        }
        else if ( projname.match(/POLAR/) && projname.match(/STEREOGRAPHIC/) ) {
            if (projname.match(/ELLIPSOID/)) {
                newname = 'PolarStereographicEllipsoid';
            }
            else {
                newname = 'PolarStereographic';
            }
        }
        else if ( projname.match(/LAMBERT/) && projname.match(/CONIC/)
                &&projname.match(/CONFORMAL/) ) {
                
            if (projname.match(/ELLIPSOID/)) {
                newname = 'LambertConicConformalEllipsoid';
            }
            else {
                newname = '';
            }
        }
        else if ( projname.match(/MERCATOR/) ) {
            if (projname.match(/TRANSVERSE/)) {
                newname = 'MercatorTransverse';
            }
            else {
                newname = 'Mercator';
            }
        }
        else if ( projname.match(/MOLLWEIDE/) ) {
            newname = 'Mollweide';
        }
        else if ( projname.match(/ORTHOGRAPHIC/) ) {
            newname = 'Orthographic';
        }
        else if ( projname.match(/SINUSOIDAL/) ) {
            newname = 'Sinusoidal';
        }
        else {
            newname = '';
        }

        return newname;   

     }; // END _standard_name



} // END MAPMAKER CLASS
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 02-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The Mercator class contains transform methods for the
 * Mercator projection.
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 38-47 (formulae on pp.41,44)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

Mercator.prototype = Mapx;
Mercator.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function Mercator() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0 - Central Meridian for projection
    //   * Rg   - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * NONE

    // initialize overrider
    this.initialize = function() {

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        x = this.Rg * lam;
        y = this.Rg * Math.log( Math.tan(Math.PI/4 + phi/2) );

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        phi = Math.PI/2 - 2*Math.atan(Math.exp(-y/this.Rg));
        lam = x/this.Rg;

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : Mercator
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 02-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The MercatorTransverse class contains transform methods for the
 * Transverse Mercator projection.
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 48-65 (formulae on pp.58,60)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

MercatorTransverse.prototype = Mapx;
MercatorTransverse.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function MercatorTransverse() {
    
    // RELEVANT MAIN VARIABLES
    //   * lat0  - Latitude of projection origin
    //   * lon0  - Longitude of projection origin
    //   * Rg    - Radius:Scale ratio
    //   * scale - Scale factor along central meridian
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * NONE

    // initialize overrider
    this.initialize = function() {

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;
        var B, phisign;
        var sin_phi, cos_phi, tan_phi;
        var sin_lam, cos_lam;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        sin_phi = Math.sin(phi);
        cos_phi = Math.cos(phi);
        tan_phi = Math.tan(phi);
        sin_lam = Math.sin(lam);
        cos_lam = Math.cos(lam);

        B = cos_phi * sin_lam;

        if (B == -1.0 || B == 1.0) {
            x = 9999999.0
        }
        else {
            x = ( this.Rg*this.scale*Math.log( (1+B)/(1-B) ) )/2.0;
        }

        if (phi == 90.0 || phi == -90.0 || lam == 90.0 || lam == -90.0) {
            phisign = (phi > 0.0) ? 1 : -1;

            y = this.Rg*this.scale*(phisign*Math.PI/2 - this.lat0);
        }
        else {
            y = this.Rg*this.scale*(Math.atan( tan_phi/cos_lam ) - this.lat0);
        }            

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;
        var D, sin_D, cos_D;
        var cosh_x_RK0, sinh_x_RK0;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        D = (y / (this.Rg*this.scale)) + this.lat0;

        sin_D = Math.sin(D);
        cos_D = Math.cos(D);
        cosh_x_RK0 = Math.cosh(x / (this.Rg*this.scale));
        sinh_x_RK0 = Math.sinh(x / (this.Rg*this.scale));

        phi = Math.asin( sin_D / cosh_x_RK0 );
        lam = Math.atan( sinh_x_RK0 / cos_D );
    
        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : MercatorTransverse
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 02-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The Mollweide class contains transform methods for the
 * Mollweide projection.
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 249-252 (formulae on pp.251-252)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

Mollweide.prototype = Mapx;
Mollweide.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function Mollweide() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0 - Central Meridian for projection
    //   * Rg   - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * root2 - Square root of 2.0

    var root2;

    // initialize overrider
    this.initialize = function() {
        root2 = Math.sqrt(2.0);

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;
        var theta, delta_theta;
        var sin_theta, cos_theta;
        var pi_sin_phi;
        var iteration;
        var max_iteration = 10;
        var epsilon = .00000025;
        
        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        delta_theta = 999.0;
        theta = phi;
        iteration = 0;

        pi_sin_phi = Math.PI*Math.sin(phi);

        while (Math.abs(delta_theta) > epsilon) {
/*            alert('delta_theta = ' + delta_theta + '(' + degrees(delta_theta) + ')\n'
                   +'theta = ' + theta + '(' + degrees(theta) + ')\n'
                   +'iteration = ' + iteration + '\n'
                   );
*/
        
            sin_theta = Math.sin(theta);
            cos_theta = Math.cos(theta);
        
            delta_theta = -(theta + sin_theta - pi_sin_phi) / (1 + cos_theta);
            theta += delta_theta;

            if (++iteration >= max_iteration) break;
        }

        theta /= 2.0;
        sin_theta = Math.sin(theta);
        cos_theta = Math.cos(theta);

        x = (2*root2/Math.PI) * this.Rg * lam * cos_theta;
        y = root2 * this.Rg * sin_theta;

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;
        var theta;
        var sin_2theta, cos_theta;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        theta = Math.asin(y / (root2*this.Rg));
        sin_2theta = Math.sin(2.0*theta);
        cos_theta = Math.cos(theta);
        
        phi = Math.asin( (2*theta + sin_2theta) / Math.PI );
        if (cos_theta != 0.0) {
            lam = (Math.PI*x) / (2*root2*this.Rg*cos_theta);
        }
        else {
            lam = 0.0;
        }

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : Mollweide
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 02-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The Orthographic class contains transform methods for the
 * Orthographic projection.
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 145-153 (formulae on pp.148-150)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

Orthographic.prototype = Mapx;
Orthographic.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function Orthographic() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0 - Central Meridian for projection
    //   * lat1 - Standard Parallel for projection
    //   * Rg   - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * sin_phi1 - Sine of lat0
    //   * cos_phi1 - Cosine of lat0
    
    var sin_phi1;
    var cos_phi1;
    


    // initialize overrider
    this.initialize = function() {
        if (this.lat0 == 999.0)
            this.lat0 = 0.00;

        sin_phi1 = Math.sin(radians(this.lat0));
        cos_phi1 = Math.cos(radians(this.lat0));

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;
        var cos_beta;
        var sin_phi, cos_phi, sin_lam, cos_lam;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        
        sin_phi = Math.sin(phi);
        cos_phi = Math.cos(phi);
        sin_lam = Math.sin(lam);
        cos_lam = Math.cos(lam);

        cos_beta = sin_phi1*sin_phi + cos_phi1*cos_phi*cos_lam;

        if (cos_beta < 0.0) return false; // unplottable

        x = this.Rg * cos_phi * sin_lam;
        y = this.Rg * (cos_phi1*sin_phi - sin_phi1*cos_phi*cos_lam);

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;
        var rho, sin_beta, cos_beta;
        
        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        
        rho = Math.sqrt(x*x + y*y);

        if (rho == 0.0) {
            phi = radians(this.lat0);
            lam = 0.0;
        }
        else {
            sin_beta = rho/this.Rg;
            cos_beta = Math.sqrt(1 - sin_beta*sin_beta);

            phi = Math.asin( cos_beta*sin_phi1 + (y*sin_beta*cos_phi1)/rho );

            if (this.lat0 == 90.0) {
                lam = Math.atan2(x, -y);
            }
            else if (this.lat0 == -90.0) {
                lam = Math.atan2(x, y);
            }
            else {
                lam = Math.atan2( (x*sin_beta), (rho*cos_phi1*cos_beta - y*sin_phi1*sin_beta) );
            }
        }

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : Orthographic
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 09-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The PolarStereographicEllipsoid class contains transform methods for
 * the Polar Stereographic projection (ellipsoid)
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 154-163 (formulae on p.160-162)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

PolarStereographicEllipsoid.prototype = Mapx;
PolarStereographicEllipsoid.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function PolarStereographicEllipsoid() {
    
    // RELEVANT MAIN VARIABLES
    //   * lat0  - Latitude of projection origin
    //   * lon0  - Longitude of projection origin
    //   * lat1  - Latitude of "true scale" for projection 
    //   * Rg    - Radius:Scale ratio
    //   * scale - Scale of projection
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //  (NOTE: The variable names are chosen to match those in the formulae,
    //         and thus may not match the "lat/lon" variable names)
    //   * sin_phi1 - The Sine of the projection center (will be 1 or -1)
    //   * mc - used for equations
    //   * tc - used for equations

    var sin_phi1;
    var mc;
    var tc;
    
    // initialize overrider
    this.initialize = function() {
        var sin_phic, cos_phic;
        var esin_phic, tan_phicx, e_2;
        var phic;
    
        if (this.lat1 == 999.0)
            this.lat1 = this.lat0;

        if (this.lat0 != 90.00 && this.lat0 != -90.00) {
            alert('Only polar aspects allowed: lat0 = ' + this.lat0);
            return false;
        }

        phic = radians(this.lat1);

        if (this.lat0 == -90) phic = -phic;

        sin_phi1 = Math.sin( radians(this.lat0) );
        sin_phic = Math.sin( phic );
        cos_phic = Math.cos( phic );
        
        esin_phic = this.eccentricity * sin_phic;
        tan_phicx = Math.tan( Math.PI/4 - phic/2 );
        e_2 = this.eccentricity / 2;

        //mc = cos_phic / Math.sqrt(1 - Math.pow(esin_phic,2));
        mc = cos_phic / Math.sqrt(1 - this.e2*sin_phic*sin_phic);
        tc = tan_phicx / Math.pow( (1-esin_phic)/(1+esin_phic), e_2 );

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, phi, dlam;
        var t, rho;
        var sin_phi, sin_dlam, cos_dlam, esin_phi, tan_phix, e_2;
        var eplus1, efrom1;
        var rho_num, rho_den;

        phi = radians(lat);
        dlam = radians(lon - this.lon0);

        if (this.lat0 != 90.0 && this.lat0 != -90.0) {
            //alert ('Only polar aspects allowed: lat0 = ' + this.lat0);
            return false;
        }

        // adjust for south polar
        if (this.lat0 == -90.0) {
            phi = -phi;
            dlam = -dlam;
        }

        sin_phi = Math.sin(phi);
        sin_dlam = Math.sin(dlam);
        cos_dlam = Math.cos(dlam);
        esin_phi = this.eccentricity * sin_phi;
        tan_phix = Math.tan(Math.PI/4 - phi/2);
        e_2 = this.eccentricity / 2;

        t = tan_phix / Math.pow( (1-esin_phi)/(1+esin_phi), e_2 );

        // non-polar true-scale
        if (this.lat1 != 90.0 && this.lat1 != -90.0) {
            rho = this.Rg * mc * t / tc;
        }
        //  polar true-scale
        else {
            eplus1 = 1 + this.eccentricity;
            efrom1 = 1 - this.eccentricity;
        
            rho_num = this.Rg * t;
            rho_den = Math.sqrt(  Math.pow( eplus1, eplus1 ) 
                                * Math.pow( efrom1, efrom1 ) );

            rho = rho_num / rho_den;
        }

        x =  rho * sin_dlam;
        y = -rho * cos_dlam;

        // adjust for south polar
        if (this.lat0 == -90.0) {
            x = -x;
            y = -y;
        }

        u[0] = this.T00*x + this.T01*y - this.u0;
        v[0] = this.T10*x + this.T11*y - this.v0;        

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;
        var rho, t, chi;
        var eplus1,efrom1;
        var t_num, t_den;
        var ser1, ser2, ser3, ser4;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        if (this.lat0 != 90.0 && this.lat0 != -90.0) {
            //alert ('Only polar aspects allowed: lat0 = ' + this.lat0);
            return false;
        }

        // adjust for south polar
        if (this.lat0 == -90.0) {
            x = -x;
            y = -y;
        }

        rho = Math.sqrt(x*x + y*y);
        
        // non-polar true-scale
        if (this.lat1 != 90.0 && this.lat1 != -90.0) {
            t = rho*tc / (this.Rg*mc);
        }
        // polar true-scale
        else {
            eplus1 = 1 + this.eccentricity;
            efrom1 = 1 - this.eccentricity;
            t_num = rho*Math.sqrt(  Math.pow(eplus1, eplus1)
                                  * Math.pow(efrom1, efrom1) );
            t_den = 2 * this.Rg * this.scale;
            
            t = t_num/t_den;
        }
        
        chi = Math.PI/2 - 2*Math.atan(t);

        ser1 = (this.e2/2 + 5*this.e4/24 +    this.e6/12  +  13*this.e8/360)   
              * Math.sin(2*chi);
        ser2 = (            7*this.e4/48 + 29*this.e6/240 + 811*this.e8/11520) 
              * Math.sin(4*chi);
        ser3 = (                            7*this.e6/120 +  81*this.e8/1120)
              * Math.sin(6*chi);
        ser4 = (                                           4279*this.e8/161280)
              * Math.sin(8*chi);

        phi = chi + ser1 + ser2 + ser3 + ser4;
        lam = Math.atan2(x,-y);

        lat[0] = degrees(phi);
        if (this.lat0 == -90.0) {
            lon[0] = -degrees(lam) + this.lon0;
            lat[0] = -lat[0];
        }
        else if (this.lat0 == 90.0) {
            lon[0] =  degrees(lam) + this.lon0;
        }
        normalize(lon);

        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : PolarStereographicEllipsoid
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 08-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The PolarStereographic class contains transform methods for the
 * Polar Stereographic projection (spherical)
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 154-163 (formulae on p.157-160)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

PolarStereographic.prototype = Mapx;
PolarStereographic.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function PolarStereographic() {
    
    // RELEVANT MAIN VARIABLES
    //   * lat0 - Latitude of projection origin
    //   * lon0 - Longitude of projection origin
    //   * Rg   - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * sin_phi1 - The Sine of the projection center (will be 1 or -1) 

    var sin_phi1;
    
    // initialize overrider
    this.initialize = function() {
        if (this.lat0 != 90.00 && this.lat0 != -90.00) {
            alert('Only polar aspects allowed: lat0 = ' + this.lat0);
            return false;
        }

        sin_phi1 = Math.sin( radians(this.lat0) );

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, phi, dlam;
        var rho;
        var sin_dlam, cos_dlam;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlam = lon - this.lon0;        
        dlam = normalize(dlam);

        dlam = radians(dlam);
        phi = radians(lat);

        sin_dlam = Math.sin(dlam);
        cos_dlam = Math.cos(dlam);

        // north polar
        if (this.lat0 == 90.0) {
            rho = 2 * this.Rg * Math.tan(Math.PI/4 - phi/2);
            x =  rho * sin_dlam;
            y = -rho * cos_dlam;
        }
        // south polar
        else if (this.lat0 == -90.0) {
            rho = 2 * this.Rg * Math.tan(Math.PI/4 + phi/2);
            x =  rho * sin_dlam;
            y =  rho * cos_dlam;
        }
        // anything else, invalid for this projection
        else {
            alert ('Only polar aspects allowed: lat0 = ' + this.lat0);
            return false;
        }

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;
        var rho, c, sin_c, cos_c;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        if (this.lat0 != 90.0 && this.lat0 != -90.0) {
            alert ('Only polar aspects allowed: lat0 = ' + this.lat0);
            return false;
        }

        rho = Math.sqrt(x*x + y*y);
        c = 2 * Math.atan2(rho, 2*this.Rg);
        sin_c = Math.sin(c);
        cos_c = Math.cos(c);
        
        // origin conversion
        if (rho == 0.0) {
            phi = radians(this.lat0);
            lam = 0.0;
        }
        // anywhere else
        else {
            phi = Math.asin( cos_c * sin_phi1 );
            if (this.lat0 == 90.0) {
                lam = Math.atan2(x,-y);
            }
            else if (this.lat0 == -90.0) {
                lam = Math.atan2(x,y);
            }
        }

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : PolarStereographic
/*=======================================================================
 * map projections - convert geographic map coordinates
 *
 * 02-Jul-1991 K.Knowles knowles@kryos.colorado.edu 303-492-0644
 *      (Original version in C)
 * 10-Dec-1992 R.Swick swick@krusty@colorado.edu 303-492-1395
 *      (Added ellipsoid projections)
 * 22-May-1997 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (Java class)
 * 22-Feb-2002 R.Swick swick@chukchi.colorado.edu 303-492-6069
 *      (reorganized to take fuller advantage of OOP)
 * 02-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Converted to JavaScript)
 *
 * The Sinusoidal class contains transform methods for the
 * Sinusoidal projection.
 *
 * See: USG/P/1395 (Map Projections - A Working Manual, Snyder)
 * Pages: 243-248 (formulae on pp.247-248)
 *
 * NOTE: You must also manually import the Mapx.js file for this to work!
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=======================================================================*/


/*
 * set up inheritance
 */

Sinusoidal.prototype = Mapx;
Sinusoidal.prototype.parent = Mapx;

/*
 * object constuctor, simply calls the parent to set up variables
 */
function Sinusoidal() {
    
    // RELEVANT MAIN VARIABLES
    //   * lon0 - Central Meridian for projection
    //   * Rg   - Radius:Scale ratio
        
    this.parent.setupvars.apply(this, arguments);


    // HELPER VARIABLES
    //   * NONE

    // initialize overrider
    this.initialize = function() {

        return true;
    }; // END FUNCTION : initialize


    // geo_to_map overrider
    this.geo_to_map = function(lat, lon, u, v) {
        var x, y, dlon;
        var phi, lam;

        lon = parseFloat(lon);
        lat = parseFloat(lat);

        // dlon is difference between given lon and central meridian
        dlon = lon - this.lon0;        
        dlon = normalize(dlon);

        phi = radians(lat);
        lam = radians(dlon);

        x = this.Rg * lam * Math.cos(phi);
        y = this.Rg * phi;

        u[0] = (this.T00*x + this.T01*y - this.u0);
        v[0] = (this.T10*x + this.T11*y - this.v0);

        return true;
    }; // END FUNCTION : geo_to_map


    // map_to_geo overrider
    this.map_to_geo = function(u, v, lat, lon) {
        var phi, lam, x, y;
        var cos_phi;

        u = parseFloat(u);
        v = parseFloat(v);

        x =  this.T00*(u + this.u0) - this.T01*(v + this.v0);
        y = -this.T10*(u + this.u0) + this.T11*(v + this.v0);

        phi = y / this.Rg;
        cos_phi = Math.cos(phi);
        if (cos_phi == 0.0) {
            lam = 0.0;
        }
        else
        {
            lam = x / (this.Rg * Math.cos(phi));
        }

        lat[0] = degrees(phi);
        lon[0] = degrees(lam) + this.lon0; // add central meridian
        normalize(lon);
        
        return true;
    }; // END FUNCTION : map_to_geo

} // END CLASS : Sinusoidal
/*=================================================================
 * Tokenizer - String tokenizer functions
 *
 * 12-Nov-2007 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (original version)
 *
 * The Tokenizer functions allow for string tokenizing in a
 * convenient and small package.
 *
 * Based on public domain code by Christopher Diggins
 * http://www.cdiggins.com/tokenizer.html
 *
 * This library is free to distribute and modify.
 *
 * National Snow & Ice Data Center, University of Colorado, Boulder
 *=================================================================*/


/*..................................................................
 * trim - Addition to String object, provides a true "trim" function
 * 
 *   input  : None (the string is passed via "this")
 *
 *   result : A string with whitespace removed from head and tail
 *..................................................................*/

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


/*
 * Tokenizer object
 *
 * Constructor input: myString - The string to tokenize
 */
function Tokenizer(myString) {

    // Internal variables
    var tokens;     // stores the tokens broken into an array
    var curtoken;   // internal index of current token
    var numtokens;  // the number of tokens in the array
    var curline;    // the current "line number"

    // Token subtype filters
    // If true, these subtypes will be returned, otherwise they are skipped
    var ws_tokens = false;  // true if Whitespace tokens should be returned
    var nl_tokens = false;  // true if Newline tokens should be returned
    var combine_symbols = false;  // true if WORDsymbolWORD should be combined
    
    // Token Type expressions, used for determining specific token subtypes
    var __line_comment = /^\/\/.*$/;
    var __full_comment = /^\/\*(?:.|[\r\n])*?\*\//;
    var __indentifier  = /[a-zA-Z_][a-zA-Z0-9_]*\b/;
    var __integer      = /^[+-]?\d+$/;
    var __float        = /^[+-]?\d*[.]\d+(?:[eE][+-]?\d+)?$/;
    var __doublequote  = /^["][^"]*["]$/;
    var __singlequote  = /^['][^']*[']$/;
    var __newline      = /^\r\n$|^\r$|^\n$/;
    var __whitespace   = /^[^\S\r\n]+$/;
    var __symbol       = /^[^\w\s]+$/;
    
    // Main tokenizer, expression to break things into tokens
    var __tokenize     = /[+-]?\d*[.]\d+(?:[eE][+-]?\d+)?|[+-]?\d+\b|\w+\b|["][^"]*?["]|['][^']*?[']|\/\/.*|\/\*(?:.|\r|\n|\r\n)*?\*\/|\s+|[^\w\s]+|\r\n|\r|\n/g;

    
    // initialize the token array
    tokens = myString.match(__tokenize);
    curtoken = 0;
    numtokens = tokens.length;
    curline = 1;


    /*....................................................................
     * nextToken - Returns the next token in the list.  Ignores Whitespace
     *             and Newline tokens unless specified otherwise
     *
     *   input  : None.  The "this" object is used for the Tokenizer
     *
     *   result : The string of the next token.  If no more tokens, then
     *            null is returned
     *...................................................................*/
    
    this.nextToken = function() {
        var temptok;

        while (true) {
            // quit if no more tokens
            if (curtoken >= numtokens)
                return null;

            // get next token
            temptok = tokens[curtoken++];
            var ok = true;

            // check if we should skip whitespace or newline
            if (this.isWhitespace(temptok) && !ws_tokens) {
                ok = false;  
            }
            if (this.isNewline(temptok)) {
                curline ++;
                if (!nl_tokens) {
                    ok = false; 
                }
            }
            if (this.isComment(temptok)) {
                ok = false;
            }
            
            // exit if the token is valid
            if (ok) break;
        }
        
        if (combine_symbols && curtoken < numtokens) {
            if (this.isSymbol(temptok) || this.isSymbol(tokens[curtoken]))
                temptok += this.nextToken();
        } 
        
        // return the token
//        alert('returning: ' + temptok);
        return temptok;
    }; // nextToken


    /*...............................................................
     * lineno - Returns the current "line number" of the token string
     *
     *   input  : None.  The "this" object is used for the Tokenizer
     *
     *   result : The current "line number" (1 is the first line)
     *...............................................................*/

    this.lineno = function() {
        return curline;
    };



    /*....................................................................
     * xxxxIsSignificant - Set or clear certain token filters
     * combineSymbols - Set or clear the symbol combiner option
     *
     *   input  : newval - Boolean.  Internal filters set to this value
     *
     *   result : None.
     *
     * xxxx = "eol" for Newline tokens, "whitespace" for Whitespace tokens
     *....................................................................*/
    this.eolIsSignificant = function(newval) {
        nl_tokens = newval;
    };

    this.whitespaceIsSignificant = function(newval) {
        ws_tokens = newval;
    };

    this.combineSymbols = function(newval) {
        combine_symbols = newval;
    };


    
    /*....................................................................
     * reset - Resets the internal index, and retokenizes a string
     *
     *   input  : newString - Optional string, if provided it will rebuild
     *              the internal token array with this new string
     *
     *   result : None.
     *....................................................................*/

    this.reset = function(newString) {
        curtoken = 0;
        curline = 1;

        if (newString != null) {
            tokens = newString.match(__tokenize);
            numtokens = tokens.length;
        }
    };


    /*...........................................................
     * isXxxx - Checks whether a token has a certain type
     *
     *   input  : checkStr - the token string to check the type of
     *
     *   result : true if the token is of that type, false if not
     *
     * Xxxx = "Comment" (for comments, single or multi-line)
     *        "Integer" for integers
     *        "Float" for Floating points (must have decimal)
     *        "Number" for integers OR floats
     *        "Quote" for single- or double-quoted strings
     *        "Newline" for newlines
     *        "Whitespace" for non-newline whitespace
     *        "Symbol" for non-alphanumeric character symbols
     *............................................................*/

    this.isComment = function(checkStr) {
        return (checkStr.match(__line_comment) 
                || checkStr.match(__full_comment));
    };

    this.isInteger = function(checkStr) {
        return (checkStr.match(__integer));
    };

    this.isFloat = function(checkStr) {
        return (checkStr.match(__float));
    };

    this.isNumber = function(checkStr) {
        return (this.isInteger(checkStr) || this.isFloat(checkStr));
    };

    this.isQuote = function(checkStr) {
        return (checkStr.match(__doublequote) || checkStr.match(__singlequote));
    };

    this.isNewline = function(checkStr) {
        return (checkStr.match(__newline));
    };

    this.isWhitespace = function(checkStr) {
        return (checkStr.match(__whitespace) || checkStr == '');
    };

    this.isSymbol = function(checkStr) {
        return (checkStr.match(__symbol));
    };
    

    
    /*.....................................................................
     * dumpit - debugging function, dumps the tokenized string, one token
     *          per line.  NEWLINES and WHITESPACE are given as placeholder
     *          strings.
     *
     *   input  : None.  Uses internal tokens
     *
     *   result : A string with the token dump in it
     *.....................................................................*/
     
    this.dumpit = function() {
        this.reset();

        var tempstr = '';
        var temp;
        var count = 0;

        temp = this.nextToken();
        while (temp != null) {
            if (this.isNewline(temp)) {
                tempstr += '--NEWLINE--';
            }
            else if (this.isWhitespace(temp)) {
                tempstr += '--WHITESPACE--';
            }
            else {
                if (this.isNumber(temp)) tempstr += 'NUMBER: ';
                if (this.isInteger(temp)) tempstr += 'INTEGER: ';
                if (this.isFloat(temp)) tempstr += 'FLOAT: ';
                if (this.isQuote(temp)) tempstr += 'QUOTE: ';
                if (this.isSymbol(temp)) tempstr += 'SYMBOL: ';
                tempstr += temp;
            }
            tempstr += '\n';
        
            temp = this.nextToken();
        }

        return tempstr;
    };  


} // End of Tokenizer class
/*=====
 * MapCanvas - a Map Canvas for JAZ, using Dojo toolkit
 *
 * 01-Apr-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Javascript version)
 *======*/


// Dojo toolkit is needed to use this functionality
dojo.require("dojox.gfx");


function MapCanvas(container, inputGrid) {
    
    // Object Variables

    // Map container object (usually a DIV)
    var mapCont;

    // Grids objects
    var realGrid, screenGrid;
    var rowOffset, colOffset;

    // Map image stuff
    var mapImg;
    var useImageSize = false;

    // Map canvas surface and group for dojo
    var canvas, mapGrp, msgGrp;

    // Rubber Band box object and dojo group
    var rbb, rbbGrp;

    // dragging is true if box is being dragged, false if not.
    var dragging = false;
    
    // dragcoords allows for clicking to be separated from dragging
    var downcoords = {x:-1, y:-1};
    
    // clearOnClick, if true, will cause a click to clear the box (false will leave it as-is)
    var clearOnClick = true;

    // canvas-level event listeners
    var dblClickListeners = new Array();
    var sglClickListeners = new Array();
    var mousePosListeners = new Array();
    var imgLoadListeners  = new Array();
    var loadGridEvents    = new Array();


    // Begin constructor code
    if (container == null) {
        return;
    }

    mapCont = container;
    if (inputGrid != null) {
        realGrid = inputGrid;
    }
    dojo.connect(mapCont, "ondragstart", dojo, "stopEvent");
    dojo.connect(mapCont, "onselectstart", dojo, "stopEvent");

    rbb = null;

    // Disable onDragStart for IE browsers; otherwise, canvas will not behave properly
    mapCont.ondragstart = function() {return false;};

    // Attach the map surface, and do initial setup
    canvas = dojox.gfx.createSurface(mapCont, mapCont.offsetWidth, mapCont.offsetHeight);
    mapGrp = canvas.createGroup();

    rbbGrp = canvas.createGroup();
    rbb = new RubberBandBox(rbbGrp, this);
    
    dojo.connect(rbbGrp, "ondragstart", dojo, "stopEvent");
    dojo.connect(rbbGrp, "onselectstart", dojo, "stopEvent");
    dojo.connect(rbbGrp, "ondrag", dojo, "stopEvent");
    dojo.connect(rbbGrp, "mousedown", function(evt){if(evt.preventDefault){evt.preventDefault();}});

    var msgfont = {size:'8pt', family:'Arial'};
    msgGrp = canvas.createGroup();
    var msgBox  = msgGrp.createRect({x:5,  y:8, width:100, height:15}).setFill([255,0,0,1]);
    var msgtext = msgGrp.createText({x:10, y:20, text:'LOADING IMAGE...'}).setFont(msgfont).setFill([255,255,255,1]);
    canvas.remove(msgGrp);

    canvas.connect("onmousedown", mousedown);
    canvas.connect("onmousemove", mousemove);
    canvas.connect("onmouseup",   mouseup);
    canvas.connect("ondblclick",  mousedblclick);
    canvas.connect("onclick", mousesglclick);
    // end constructor code
    
    // Load a map image
    this.loadImage = function(newImage, newUseImageSize) {
        var tmpImg;

        if (!newImage) return false;

        canvas.add(msgGrp);
        

        useImageSize = (newUseImageSize == true);
        
        tmpImg = new Image();
        tmpImg.onload = finishLoadImage;
        tmpImg.src = newImage;
        
        return true;
    };

    function finishLoadImage() {
        var imgW, imgH;
        var mcW, mcH;
        var newW, newH;
        var xratio, yratio;

        imgW = this.width;
        imgH = this.height;

        mcW = mapCont.offsetWidth;
        mcH = mapCont.offsetHeight;
  
        if (useImageSize) {
            newW = imgW;
            newH = imgH;
            mapCont.style.width = newW;
            mapCont.style.height = newH;
        } 
        else {
            newW = mcW;
            newH = mcH;
        }
        
        canvas.setDimensions(newW, newH);

        xratio = newW / imgW;
        yratio = newH / imgH;

        mapImg = {width: newW, height: newH, src: this.src};

        mapGrp.createImage({height: imgH, width: imgW, src:this.src})
                       .setTransform({xx:xratio, yy:yratio});

        scaleScreenGrid();

        canvas.remove(msgGrp);

        for (x = 0; x < imgLoadListeners.length; x++) {
            imgLoadListeners[x]();
        }
            
    }

    this.getImageSize = function() {
        var ww,hh;
        
        if (!mapImg) return null;
        
        ww = mapImg.width;
        hh = mapImg.height;

        return {width:ww, height:hh};
    };

    this.getRBB = function() {
        return rbb;
    };
    
    this.getCanvas = function() {
        return canvas;
    };

    // Load a Grid
    this.loadGrid = function(newGrid, newRowOffset, newColOffset) {
        if (!newGrid) return false;

        if (newRowOffset == null) newRowOffset = 0;
        if (newColOffset == null) newColOffset = 0;

        realGrid = newGrid.copy_grid();
        rowOffset = newRowOffset;
        colOffset = newColOffset;

        scaleScreenGrid();
        while (loadGridEvents.length > 0) {
        	var thisEvent = loadGridEvents.shift();
        	thisEvent;
        }
        rbb.refresh();
        
        return true;
    };
    
    this.setClearOnClick = function(newClick) {
    	clearOnClick = newClick;
    };

    this.setShowXY = function(newShow) {
        rbb.setShowXY(newShow);
    };

    this.setShowLatLon = function(newShow) {
        rbb.setShowLatLon(newShow);
    };

    this.setShowSphericalRectangle = function(newShow) {
        rbb.setShowSphRect(newShow);
    };

    this.setShowRowCol = function(newShow) {
        rbb.setShowRowCol(newShow);
    };

    // Scale the Screen Grid to match the image
    var ssg_retry = 0;
    scaleScreenGrid = function() {
        if (!realGrid) {
            if (ssg_retry < 15) {
                ssg_retry++;
                setTimeout( function(){ scaleScreenGrid() }, 1000);
                return;
            }
            else {
                //alert('No Real Grid');
                return;
            }
        }
        if (!mapImg) {
            if (ssg_retry < 15) {
                ssg_retry++;
                setTimeout( function(){ scaleScreenGrid() }, 1000);
                return;
            }
            else {
                //alert('no Map Image'); 
                return;
            }
        }
        ssg_retry = 0;

        var factor;

        screenGrid = realGrid.copy_grid();

        screenGrid.rows = mapImg.height;
        factor = realGrid.rows / screenGrid.rows;
        screenGrid.rows_per_map_unit = realGrid.rows_per_map_unit / factor;
        screenGrid.map_origin_row = realGrid.map_origin_row / factor;

        screenGrid.cols = mapImg.width;
        factor = realGrid.cols / screenGrid.cols;
        screenGrid.cols_per_map_unit = realGrid.cols_per_map_unit / factor;
        screenGrid.map_origin_col = realGrid.map_origin_col / factor;
    };

    this.setXYFromLatLonPolygon = function(polygon) {
    	if (!polygon) return false;
    	if (rbb) {
        	if (!screenGrid || !realGrid) {
        		//tempPoly = polygon;
        		//setTimeout(function (){ setXYFromLatLonPolygon(polygon) }, 1000);
        		return false;
        	}
            rbb.setXYFromLatLonPolygon(polygon);
        }
        else {
            return false;
        }
    	
    	return true;
    };

    // Adjust a Lat/Lon parameter
    this.adjustLatLonParam = function(param, newValue) {
        if (rbb) {
            rbb.adjustLatLon(param, newValue); 
        }
    };

    // Grids access functions
    this.xyToLatLon = function(x, y, lat, lon) {
        var status = false;
    
        if (screenGrid) {
            status = screenGrid.inverse_grid(x, y, lat, lon);
        }

        return status;
    };

    this.xyToRowCol = function(x, y, row, col, truncit) {
        var status = false;
        var factor; 

        if (screenGrid && realGrid) {
            factor = realGrid.rows / screenGrid.rows;
            row[0] = y * factor;

            factor = realGrid.cols / screenGrid.cols;
            col[0] = x * factor;

            status = true;

            if (truncit == true) {
                col[0] = parseInt(col[0]);
                row[0] = parseInt(row[0]);
            }

            col[0] += colOffset;
            row[0] += rowOffset;
        }

        return status;
    };

    this.latLonToXY = function(lat, lon, x, y) {
        var status = false;

        if (screenGrid) {
            status = screenGrid.forward_grid(lat, lon, x, y);
        }

        return status;
    };

    this.latLonToRowCol = function(lat, lon, row, col) {
        var status = false;

        if (realGrid) {
            status = realGrid.forward_grid(lat, lon, col, row);

            col[0] += colOffset;
            row[0] += rowOffset;
        }

        return status;
    };

    this.rowColToXY = function(row, col, x, y) {
        var status = false;
        var factor;

        if (screenGrid && realGrid) {
            factor = screenGrid.rows / realGrid.rows;
            y[0] = (row-rowOffset) * factor;

            factor = screenGrid.cols / realGrid.cols;
            x[0] = (col-colOffset) * factor;

            status = true;
        }

        return status;
    };
        
    this.rowColToLatLon = function(row, col, lat, lon) {
        var status = false;

        if (realGrid) {
            status = realGrid.inverse_grid(col-colOffset, row-rowOffset, lat, lon);
        }

        return status;
    };

    // Listeners
    this.addLatLonListener = function(newListener) {
        if (rbb) {
            rbb.addLatLonListener(newListener);
        }
    };

    this.addLatLonPolygonListener = function(newListener) {
        if (rbb) {
            rbb.addLatLonPolygonListener(newListener);
        }
    };

    this.addRowColListener = function(newListener) {
        if (rbb) {
            rbb.addRowColListener(newListener);
        }
    };

    this.addMousePosListener = function(newListener) {
        var fullListener = {listener:newListener, object:this};
        
        mousePosListeners.push(fullListener);
    };

    this.addDblClickListener = function(newListener, newObject) {
        var fullListener = {listener:newListener, object:newObject};
    
        dblClickListeners.push(fullListener);
    };
    
    this.addSingleClickListener = function(newListener, newObject) {
    	var fullListener = {listener:newListener, object:newObject};
    	
    	sglClickListeners.push(fullListener);
    };

    this.addImgLoadListener = function(newListener) {
        imgLoadListeners.push(newListener);
    };
    
    function offsetCoords(evt) {    
        var canvas_pos = dojo.coords(mapCont, false);
        var x = evt.clientX - canvas_pos.x;
        var y = evt.clientY - canvas_pos.y;
        
        return {x:x, y:y};
    }
    
    function addFullPointInfo(pt, mcObj) {
    	var lat = new Array(0);
    	var lon = new Array(0);
    	var row = new Array(0);
    	var col = new Array(0);
    	
    	if (mcObj == null) {
    		pt.fullrow = 55;
    		return;
    	}
    	
    	if (pt.lat == null || pt.lon == null) {
    		if (screenGrid) {
    			screenGrid.inverse_grid(pt.x, pt.y, lat, lon);
    			pt.lat = lat[0];
    			pt.lon = lon[0];
    		}
    	}
    	
    	mcObj.xyToRowCol(pt.x, pt.y, row, col);
    	pt.fullrow = row[0];
    	pt.fullcol = col[0];
    	pt.row = parseInt(row[0]);
    	pt.col = parseInt(col[0]);
    	
    	mcObj.getMoreMousePosInfo(pt);
    	
    	return;
    }

    function mousedown(evt) {
        if (downcoords.x == -1) {
            var pt = offsetCoords(evt);
            downcoords.x = pt.x;
            downcoords.y = pt.y;
        }
    }
    
    function mousemove(evt) {
        var lat, lon;
        lat = new Array(0);
        lon = new Array(0);
        row = new Array(0);
        col = new Array(0);

        var pt = offsetCoords(evt);
        
        if (rbb && !dragging && downcoords.x != -1) {
            if (Math.abs(pt.x - downcoords.x) >= 5 || Math.abs(pt.y - downcoords.y) >= 5) {
                rbb.startDrag(downcoords.x, downcoords.y);
                dragging = true;
            }
        }

        if (screenGrid) 
            screenGrid.inverse_grid(pt.x, pt.y, lat, lon);

        if (rbb && dragging) {
            rbb.moveDrag(pt.x, pt.y);
        }


        if (mousePosListeners.length > 0) {
            var fullpoint = {
                x:pt.x,
                y:pt.y,
                lat:lat[0],
                lon:lon[0]
                };

        
            for (var x = 0; x < mousePosListeners.length; x++) {
                var thisobj = mousePosListeners[x].object;
                var thislistener = mousePosListeners[x].listener;
                
                addFullPointInfo(fullpoint, thisobj);
                
                thislistener(fullpoint);
            }
        }
        
        evt.stopPropagation();
    }

    function mouseup(evt) {
    	if (rbb && clearOnClick && !dragging) {
    		rbb.refresh();
    	}
    	
        if (rbb && dragging) {
            var pt = offsetCoords(evt);
            dragging = false;
            rbb.endDrag(pt.x, pt.y);
        }
        downcoords.x = -1;
        downcoords.y = -1;
    }

    function mousedblclick(evt) {
        var pt = offsetCoords(evt);

        for (var x = 0; x < dblClickListeners.length; x++) {
        	var thisListen = dblClickListeners[x];
        	addFullPointInfo(pt, thisListen.object);
            thisListen.listener(pt,thisListen.object);
        }
    }
    
    function mousesglclick(evt) {
        var pt = offsetCoords(evt);
        
        for (var x = 0; x < sglClickListeners.length; x++) {
        	var thisListen = sglClickListeners[x];
        	addFullPointInfo(pt, thisListen.object);
        	thisListen.listener(pt,thisListen.object);
        }
    }

    this.getMoreMousePosInfo = function (fullpoint) {
        return;
    };

    this.getMoreBoxInfo = function (boxinfo) {
        return;
    };

    this.clearBox = function () {
        if (rbb) {
            rbb.refresh();
        }
    };
}
/*====
 * RubberBandBox - A Rubber Band Box for a drawing canvas, using Dojo toolkit
 *
 * 31-Mar-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Javascript version)
 *======*/


function RubberBandBox(newCanvas, newParent) { //  newGrid) {
    var canvas = null;
    var parentMap = null;

    // main XY boundaries
    var x0=-1;
    var y0=-1;
    var x1=-1;
    var y1=-1;

    // which bounding boxes to show
    var showXY = true;
    var showLatLon = false;
    var showSphRect = false;
    var showRowCol = false;

    // dojo shape objects for drawing
    var xyBox;
    var latLonBox;
    var sphRectBox;
    var rowColBox;
    var dragging = false;

    // lat/lon boundary information
    var latLonSteps = 50;
    var latLonCrossesDateline = false;
    var latLonBounds = {
        west  : 0,
        east  : 0,
        north : 0,
        south : 0
        };
    var oLon;
    
    // event listeners
    var xyListeners = new Array();
    var latLonListeners = new Array();
    var latLonPolygonListeners = new Array();
    var rowColListeners = new Array();
    

    // begin constructor code
    if (newCanvas != null) {
        canvas = newCanvas;
        xyBox = canvas.createPolyline()
                      .setStroke({color:[0,0,0,0], width:1})
                      .setFill([0,0,0,0]);
        latLonBox = canvas.createPolyline()
                          .setStroke({color:[0,0,0,0], width:1})
                         .setFill([0,0,0,0]);
        sphRectBox = canvas.createPolyline()
                           .setStroke({color:[0,0,0,0], width:1})
                           .setFill([0,0,0,0]);
        rowColBox = canvas.createPolyline()
                          .setStroke({color:[0,0,0,0], width:1})
                          .setFill([0,0,0,0]);
        
        dojo.connect(xyBox, "ondragstart", dojo, "stopEvent");
        dojo.connect(xyBox, "onselectstart", dojo, "stopEvent");
        dojo.connect(xyBox, "ondrag", dojo, "stopEvent");
        dojo.connect(xyBox, "mousedown", function(evt){if(evt.preventDefault){evt.preventDefault();}});
        
        //dojo.connect(latLonBox, "ondragstart", dojo, "stopEvent");
        //dojo.connect(latLonBox, "onselectstart", dojo, "stopEvent");
        
        //dojo.connect(sphRectBox, "ondragstart", dojo, "stopEvent");
        //dojo.connect(sphRectBox, "onselectstart", dojo, "stopEvent");
        
        //dojo.connect(rowColBox, "ondragstart", dojo, "stopEvent");
        //dojo.connect(rowColBox, "onselectstart", dojo, "stopEvent");
    }
    if (newParent != null) {
        parentMap = newParent;
    }
    // end constructor code


    this.addXYListener = function(newListener) {
        
    };

    this.getXYInfo = function() {
        return getXYBox();
    };

    this.addLatLonListener = function(newListener) {
        latLonListeners.push(newListener);
    };

    this.addLatLonPolygonListener = function(newListener) {
        latLonPolygonListeners.push(newListener);
    };

    this.addRowColListener = function(newListener) {
        rowColListeners.push(newListener);
    };

    this.setShowXY = function(newShow) {
        showXY = newShow;
        if (newShow) {
            drawXY();
        } 
        else {
            hideXY();
        }
    };

    this.setShowLatLon = function(newShow) {
        showLatLon = newShow;
        if (newShow) {
            drawLatLon();
        }
        else {
            hideLatLon();
        }
    };

    this.setShowSphRect = function(newShow) {
        showSphRect = newShow;
        if (newShow) {
            drawSphericalRectangle();
        }
        else {
            hideSphericalRectangle();
        }
    };

    this.setShowRowCol = function(newShow) {
        showRowCol = newShow;
        if (newShow) {
            drawRowCol();
        }
        else {
            hideRowCol();
        }
    };

    this.startDrag = function(x,y) {
        x0 = x1 = x;
        y0 = y1 = y;

        var tlat, tlon;
        tlat = new Array(0);
        tlon = new Array(0);

        parentMap.xyToLatLon(x0, y0, tlat, tlon);
        oLon = tlon[0];
        latLonCrossesDateline = false;
        dragging = true;
    };

    this.moveDrag = function(x,y) {
        x1 = x;
        y1 = y;

        var lat0, lon0, lat1, lon1;
        var tlat, tlon;
        tlat = new Array(0);
        tlon = new Array(0);
    
        parentMap.xyToLatLon(x0, y0, tlat, tlon);
        lat0 = tlat[0]; lon0 = tlon[0];
        parentMap.xyToLatLon(x1, y1, tlat, tlon);
        lat1 = tlat[0]; lon1 = tlon[0];

        if (Math.abs(oLon - lon1) > 300) {
            latLonCrossesDateline = !latLonCrossesDateline;
        }

        oLon = lon1;

        if (latLonCrossesDateline) {
            latLonBounds.west = Math.max(lon0, lon1);
            latLonBounds.east = Math.min(lon0, lon1) + 360;
        }
        else {
            latLonBounds.west = Math.min(lon0, lon1);
            latLonBounds.east = Math.max(lon0, lon1);
        }
        latLonBounds.north = Math.max(lat0, lat1);
        latLonBounds.south = Math.min(lat0, lat1);            

        this.draw();
    };

    this.endDrag = function(x,y) {
        this.moveDrag(x,y);

        updateListeners();

        dragging = false;
        this.draw();
    };


    this.setXYFromLatLonPolygon = function(polygon) {
        if (!polygon) {
            return;
        }

        if (!isArray(polygon)) {
        	polygon = polygon.toString();
            var strformat = /^\s*[+-]?\s*\d*(.\d*)?(\s*,\s*[+-]?\s*\d*(.\d*)?){9}\s*$/;
            if (!polygon.match(strformat)) {
                return;
            }

            polygon = polygon.replace(/\s*/, '');
        
            polygon = polygon.split(",");    
        }

        if (!polygon[0].lat) {
            if (polygon.length != 10) {
                return;
            }

            var tmpPoly = new Array();
            for (i = 0; i < polygon.length; i += 2) {
                var tmpPt = {lat:0,lon:0};
                tmpPt.lat = polygon[i];
                tmpPt.lon = polygon[i+1];

                tmpPoly.push(tmpPt);
            }

            polygon = tmpPoly;
        }

        if (polygon.length != 5) {
            return;
        }

        var tx = new Array(0);
        var ty = new Array(0);

        parentMap.latLonToXY(polygon[0].lat, polygon[0].lon, tx, ty);
        x0 = tx[0];
        y0 = ty[0];
        parentMap.latLonToXY(polygon[2].lat, polygon[2].lon, tx, ty);
        x1 = tx[0];
        y1 = ty[0];

        this.endDrag(x1,y1);
    };


    this.adjustLatLon = function(param, newValue) {
        newValue = parseFloat(newValue);
        switch(param) {
            case 'north':
                newValue = constrainLat(newValue);
                if (newValue < latLonBounds.south) {
                    latLonBounds.north = latLonBounds.south;
                    latLonBounds.south = newValue;
                }
                else {
                    latLonBounds.north = newValue;
                }
                break;
            case 'south':
                newValue = constrainLat(newValue);
                if (newValue > latLonBounds.north) {
                    latLonBounds.south = latLonBounds.north;
                    latLonBounds.north = newValue;
                }
                else {
                    latLonBounds.south = newValue;
                }
                break;
            case 'west' :
                newValue = normalize(newValue);
                latLonBounds.west = newValue;
                break;
            case 'east' :
                newValue = normalize(newValue);
                latLonBounds.east = newValue;
                break;
            default :
                return;
        }

        if (latLonBounds.west > latLonBounds.east) {
            latLonBounds.east += 360;
            latLonCrossesDateline = true;
        }
        else {
            latLonCrossesDateline = false;
        }

        resizeXYfromLatLon();
        this.draw();

        updateListeners();
    };

    this.refresh = function() {
        x0 = -1;
        y0 = -1;
        x1 = -1;
        y1 = -1;
        
        latLonBounds.north = 0;
        latLonBounds.south = 0;
        latLonBounds.east  = 0;
        latLonBounds.west  = 0;
        
        updateListeners();
        this.draw();
    };    
    
    var drawSem = false;    
    this.draw = function () {
        if (!canvas) return;

        if (drawSem) return;
        drawSem = true;

        if (showXY) drawXY();
        if (showLatLon) drawLatLon();
        if (showSphRect) drawSphericalRectangle();
        if (showRowCol) drawRowCol();

        drawSem = false;
    };

    function drawRect(x, y, w, h) {
        var shapedef = new Array(x, y, x+w, y, x+w, y+h, x, y+h, x, y);
        return shapedef;
    }

    var drawXY = function () {
        if (!canvas || !xyBox) return;
        
        var x = Math.min(x0,x1);
        var y = Math.min(y0,y1);
        
        var w = Math.abs(x1-x0);
        var h = Math.abs(y1-y0);

        if (w < 1 || h < 1) {
            hideXY();
            return;
        }

        var shapedef = drawRect(x, y, w, h);
        
        xyBox.setShape(shapedef)
             .setStroke({color:[0,255,0,1]})
             .setFill([0,255,0,.3]);
        
    };

    var hideXY = function () {
        if (xyBox) {
            xyBox.setStroke({color:[0,0,0,0]})
                 .setFill([0,0,0,0]);
        }
    };

    var drawLatLonLine = function(lat0, lon0, lat1, lon1, line) {
        
        var lat_inc, lon_inc;
        var lat, lon;
        var xx, yy;

        lat_inc = (lat1 - lat0) / latLonSteps;
        lon_inc = (lon1 - lon0) / latLonSteps;

        lat = lat0;
        lon = lon0;

        xx = new Array(0);
        yy = new Array(0);

        var dolat, dolon;

        dolat = true;
        dolon = true;

        var count = 0;

        while (dolat || dolon) {
            if (!parentMap.latLonToXY(lat, lon, xx, yy)) return false;
            line.push(xx[0]);
            line.push(yy[0]);

            lat += lat_inc;
            lon += lon_inc;

            if (lat0 < lat1) {
                dolat = (lat < lat1)
            }
            else {
                dolat = (lat > lat1);
            }

            if (lon0 < lon1) {
                dolon = (lon < lon1);
            }
            else {
                dolon = (lon > lon1);
            }

            count ++;
            if (count > latLonSteps) break;
        }
        return true; 
    };

    var drawLatLon = function () {
        if (!canvas || !parentMap || !latLonBox 
            || (latLonBounds.south == latLonBounds.north)
            || (latLonBounds.west  == latLonBounds.east) ) {

            hideLatLon();
            return;
        }

        var lat0,lon0, lat1,lon1;

        var lat_inc, lon_inc;
        var xx, yy, tlat, tlon;

        var shapedef = new Array();
        xx = new Array(0);
        yy = new Array(0);
        tlat = new Array(0);
        tlon = new Array(0);

        lat0 = latLonBounds.north;
        lat1 = latLonBounds.south;
        lon0 = latLonBounds.west;
        lon1 = latLonBounds.east;

        if (!drawLatLonLine(lat0, lon0, lat0, lon1, shapedef)) return false;
        if (!drawLatLonLine(lat0, lon1, lat1, lon1, shapedef)) return false;
        if (!drawLatLonLine(lat1, lon1, lat1, lon0, shapedef)) return false;
        if (!drawLatLonLine(lat1, lon0, lat0, lon0, shapedef)) return false;
                
        if (!parentMap.latLonToXY(lat0, lon0, xx, yy)) return false;
        shapedef.push(xx[0]);
        shapedef.push(yy[0]);

        latLonBox.setShape(shapedef)
                 .setStroke({color:[255,0,0,1]})
                 .setFill([255,0,0,.3]);

    };

    var hideLatLon = function() {
        if (latLonBox) {
            latLonBox.setStroke({color:[0,0,0,0]})
                     .setFill([0,0,0,0]);
        }
    };

    var drawGreatCircleIntermediate = function(lat0, lon0, lat1, lon1, intermed, shapedef) {

        var tlat;
        var tlon;
        var xx = new Array(0);
        var yy = new Array(0);
        var ox, oy;
        var numPoints = intermed.length-1;

        if (!parentMap.latLonToXY(lat0, lon0, xx, yy)) {return false;}
        ox = xx[0];
        oy = yy[0];

        tlon = lon0;
        for (var i = 0; i < numPoints; i++) {
            if (isNaN(tlon)) {
                shapedef.push(ox);
                shapedef.push(oy);
            }
            else {
                tlat = greatCircleLat(lat0, lon0, lat1, lon1, tlon);
                if (isNaN(tlat)) {
                    shapedef.push(ox);
                    shapedef.push(oy);
                }
                else {
                    if (!parentMap.latLonToXY(tlat, tlon, xx, yy)) {return false;}
                    shapedef.push(xx[0]);
                    shapedef.push(yy[0]);
                }
            }
    
            tlon = intermed[i];
        }

        return true;
    }; 

    var drawGreatCircleInterval = function(lat0, lon0, lat1, lon1, numPoints, shapedef) {

        var tlat, tlon;
        var xx = new Array(0);
        var yy = new Array(0);
        var lonInc;

        if (lon0 == lon1)
            lon1 += 0.000001;

        lonInc = (lon1 - lon0)/numPoints;

        tlon = lon0;
        for (var i = 0; i <= numPoints; i++) {
            tlat = greatCircleLat(lat0, lon0, lat1, lon1, tlon);
            
            if (!parentMap.latLonToXY(tlat, tlon, xx, yy)) {return false;}
            shapedef.push(xx[0]);
            shapedef.push(yy[0]);

            tlon += lonInc;
        }

        return true;
    };

    var intermediateLons = function(x0, y0, x1, y1, maxPoints) {
        var lat = new Array(0);
        var lon = new Array(0);
        var numPoints;
        var lons = new Array();
        var xStep, yStep; 

        if (!parentMap.xyToLatLon(x0, y0, lat, lon)) return null;

        if (x0 != x1) {
            numPoints = Math.abs(x1 - x0);
            if (numPoints > maxPoints) numPoints = maxPoints;
            xStep = (x1-x0)/numPoints;
            yStep = 0;            
        }
        else {
            numPoints = Math.abs(y1 - y0);
            if (numPoints > maxPoints) numPoints = maxPoints;
            xStep = 0;
            yStep = (y1-y0)/numPoints;
        }

        for (var i = 1; i <= numPoints; i++) {
            if (!parentMap.xyToLatLon( x0+i*xStep, y0+i*yStep, lat, lon)) return null;
            
            lons.push(lon[0]);
        }

        return lons;
    };

    var drawGreatCircle = function(x0, y0, x1, y1, shapedef) {
        var lat0, lon0, lat1, lon1, latm, lonm, midx, midy;
        var tlat = new Array(0);
        var tlon = new Array(0);
        var dlon;

        var intermed;
        var intPoints = dragging?5:50;

        if (x0 == x1) {
            midx = x0;
            midy = (y0 + y1)/2;
        }
        else {
            midx = (x0 + x1)/2;
            midy = y0;
        }
        
        if (!parentMap.xyToLatLon(x0, y0, tlat, tlon)) { return false; }
        lat0 = tlat[0];
        lon0 = tlon[0];
        if (!parentMap.xyToLatLon(x1, y1, tlat, tlon)) { return false; }
        lat1 = tlat[0];
        lon1 = tlon[0];
        if (!parentMap.xyToLatLon(midx, midy, tlat, tlon)) { return false; }
        latm = tlat[0];
        lonm = tlon[0];

        dlon = Math.abs(lon0 - lon1);

        if (   isNaN(lat0) || isNaN(lon0) 
            || isNaN(lat1) || isNaN(lon1) 
            || isNaN(latm) || isNaN(lonm) ) { return false; }

        if (   (dlon < 20)
            || ( lon0<=lonm && lonm<=lon1 && lon0<=lon1 && dlon!=180 && dlon!=360 )
            || ( lon0>=lonm && lonm>=lon1 && lon0>=lon1 && dlon!=180 && dlon!=360 )
            ) {

            return drawGreatCircleInterval(lat0, lon0, lat1, lon1, intPoints, shapedef);
        }
        else {
            intermed = intermediateLons(x0, y0, x1, y1, intPoints);
            if (!intermed) { return false; }
            return drawGreatCircleIntermediate(lat0, lon0, lat1, lon1, intermed, shapedef);
        }
    };

    var drawSphericalRectangle = function() {
        var lat0, lon0, lat1, lon1, lat2, lon2, lat3, lon3;
        var tlat = new Array(0);
        var tlon = new Array(0);
    
        var intermed;
        var shapedef = new Array();

        if (!canvas || !parentMap || !sphRectBox || ((x0==x1)&&(y0==y1))) {
            hideSphericalRectangle();
            return;
        }

        if (!drawGreatCircle(x0, y0, x0, y1, shapedef)) { hideSphericalRectangle(); return; }
        if (!drawGreatCircle(x0, y1, x1, y1, shapedef)) { hideSphericalRectangle(); return; }
        if (!drawGreatCircle(x1, y1, x1, y0, shapedef)) { hideSphericalRectangle(); return; }
        if (!drawGreatCircle(x1, y0, x0, y0, shapedef)) { hideSphericalRectangle(); return; } 

        shapedef.push(x0);
        shapedef.push(y0);

        sphRectBox.setShape(shapedef)
                  .setStroke({color:[255,255,0,1]})
                  .setFill([255,255,0,.3]);
        
    };

    var hideSphericalRectangle = function() {
        if (sphRectBox) {
            sphRectBox.setStroke({color:[0,0,0,0]})
                      .setFill([0,0,0,0]);
        }        
    };

    var drawRowCol = function() {
        var tx = new Array(0);
        var ty = new Array(0);
        var rcx, rcy, rcw, rch;
        var rcBox; 
        
        if (!canvas || !parentMap || !rowColBox || ((x0==x1)&&(y0==y1))) {
            hideRowCol();
            return;
        }

        rcBox = getRowColBox();
        if (rcBox == null) {hideRowCol(); return; }

        if (!parentMap.rowColToXY(rcBox.ul.row, rcBox.ul.col, tx, ty)) { return; }
        rcx = tx[0]; rcy = ty[0];
        if (!parentMap.rowColToXY(rcBox.lr.row+1, rcBox.lr.col+1, tx, ty)) { return; }
        rcw = tx[0]-rcx; rch = ty[0]-rcy;

        var shapedef = drawRect(rcx, rcy, rcw, rch);

        rowColBox.setShape(shapedef)
                 .setFill([255,255,255,.3]);

    };

    var hideRowCol = function() {
        if (rowColBox) {
            rowColBox.setStroke({color:[0,0,0,0]})
                     .setFill([0,0,0,0]);
        }
    };
    
    var getLatLonBox = function() {
        if ((x1-x0) == 0 && (y1-y0) == 0) {
            return null;
        }
        
    	var box = {
            west  : normalize(latLonBounds.west),
            east  : normalize(latLonBounds.east),
            north : latLonBounds.north,
            south : latLonBounds.south
            };

        return box;
    };

    var getLatLonPolygon = function() {
        var xl,xr,yt,yb;
        var lat, lon;
        var polygon = new Array();
        
        if ((x1-x0) == 0 && (y1-y0) == 0) {
            return null;
        }

        lat = new Array(0);
        lon = new Array(0);

        
        xl = Math.min(x0, x1);
        xr = Math.max(x0, x1);

        yt = Math.min(y0, y1);
        yb = Math.max(y0, y1);

        
        if (!parentMap.xyToLatLon(xl, yt, lat, lon)) return [];
        polygon.push({lat:lat[0], lon:lon[0]});

        if (!parentMap.xyToLatLon(xl, yb, lat, lon)) return [];
        polygon.push({lat:lat[0], lon:lon[0]});

        if (!parentMap.xyToLatLon(xr, yb, lat, lon)) return [];
        polygon.push({lat:lat[0], lon:lon[0]});

        if (!parentMap.xyToLatLon(xr, yt, lat, lon)) return [];
        polygon.push({lat:lat[0], lon:lon[0]});
        
        if (!parentMap.xyToLatLon(xl, yt, lat, lon)) return [];
        polygon.push({lat:lat[0], lon:lon[0]});

        return polygon;
    };

    var getXYBox = function() {
        var tx0, ty0, tx1, ty1;
        
        if ((x1-x0) == 0 && (y1-y0) == 0) {
            return null;
        }

        tx0 = Math.min(x0, x1);
        ty0 = Math.min(y0, y1);
        tx1 = Math.max(x0, x1);
        ty1 = Math.max(y0, y1);
    
        var box = {
            x0: tx0,
            y0: ty0,
            x1: tx1,
            y1: ty1
        };

        return box;
    };
            
    var getRowColBox = function() {
        var tx0, ty0, tx1, ty1;
        var tr = new Array(0);
        var tc = new Array(0);
        
        if ((x1-x0) == 0 && (y1-y0) == 0) {
            return null;
        }
    
        var box = {
            ul:   {row:-1, col:-1, fullrow:-1, fullcol:-1},
            ur:   {row:-1, col:-1, fullrow:-1, fullcol:-1},
            ll:   {row:-1, col:-1, fullrow:-1, fullcol:-1},
            lr:   {row:-1, col:-1, fullrow:-1, fullcol:-1}
            };

        tx0 = Math.min(x0, x1);
        ty0 = Math.min(y0, y1);
        tx1 = Math.max(x0, x1);
        ty1 = Math.max(y0, y1);

        if (!parentMap.xyToRowCol(tx0, ty0, tr, tc)) { return null; }
        box.ul.row = parseInt(tr[0]);
        box.ul.col = parseInt(tc[0]);
        box.ul.fullrow = tr[0];
        box.ul.fullcol = tc[0];
        if (!parentMap.xyToRowCol(tx0, ty1, tr, tc)) { return null; }
        box.ll.row = parseInt(tr[0]);
        box.ll.col = parseInt(tc[0]);
        box.ll.fullrow = tr[0];
        box.ll.fullcol = tc[0];
        if (!parentMap.xyToRowCol(tx1, ty1, tr, tc)) { return null; }
        box.lr.row = parseInt(tr[0]);
        box.lr.col = parseInt(tc[0]);
        box.lr.fullrow = tr[0];
        box.lr.fullcol = tc[0];
        if (!parentMap.xyToRowCol(tx1, ty0, tr, tc)) { return null; }
        box.ur.row = parseInt(tr[0]);
        box.ur.col = parseInt(tc[0]);
        box.ur.fullrow = tr[0];
        box.ur.fullcol = tc[0];

        parentMap.getMoreBoxInfo(box);
        
        return box;
    };
            
    var updateListeners = function() {
        var retLatLon = getLatLonBox();
        var retLatLonPoly = getLatLonPolygon();
        var retRowCol = getRowColBox();

        for (var x = 0; x < latLonListeners.length; x++) {
            latLonListeners[x](retLatLon);
        }
        for (var x = 0; x < latLonPolygonListeners.length; x++) {
            latLonPolygonListeners[x](retLatLonPoly);
        }
        for (var x = 0; x < rowColListeners.length; x++) {
            rowColListeners[x](retRowCol);
        }
    };

    var resizeXYfromLatLon = function() {
        var xx, yy, temp;

        xx = new Array(0);
        yy = new Array(1);

        if (!parentMap.latLonToXY(latLonBounds.north, latLonBounds.west, xx, yy)) return false;
        x0 = xx[0];
        y0 = yy[0];

        if (!parentMap.latLonToXY(latLonBounds.south, latLonBounds.east, xx, yy)) return false;
        x1 = xx[0];
        y1 = yy[0];

        if (x0 > x1) {
            temp = x0;
            x0 = x1;
            x1 = temp;
        }

        if (y0 > y1) {
            temp = y0;
            y0 = y1;
            y1 = temp;
        }
    };
}
/*====
 * WMSMapCanvas - An extension of MapCanvas, allowing for zooming based on a
 *                WMS-generated Map Image
 *
 * 21-Apr-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Javascript version)
 *======*/


WMSMapCanvas.prototype = new MapCanvas();
WMSMapCanvas.prototype.constructor = WMSMapCanvas;
WMSMapCanvas.prototype.parent = MapCanvas.prototype;

function WMSMapCanvas(container, canvasWidth, canvasHeight, inputGrid) {

    var aspects = new Array();
    var curAspect = null;
    var curGrid;
    var curGridOffset = {rowOffset: 0, colOffset: 0};
    var curBounds = {minx:-1, miny:-1, maxx:-1, maxy:-1};
    var zoomFactor = 2;
    var imgSize = {
            width:  (canvasWidth)  ? canvasWidth  : 500,
            height: (canvasHeight) ? canvasHeight : 500
            };
    var zoom_pan_line = [255, 153, 0, 1];
    var zoom_pan_fill = [255, 153, 0, 0.5];

    var dblClickMode = 'NONE'; //ZOOMIN, ZOOMOUT, CENTER, NONE
    
    var buttons = {
            panleft:  null,
            panright: null,
            panup:    null,
            pandown:  null,
            zoomin:   null,
            zoomout:  null
            };

    MapCanvas.call(this, container, inputGrid);

    
    function dblClickAction(pt, myobj) {
        if (dblClickMode == 'ZOOMIN') {
            myobj.zoomRecenterPoint(pt);
        }
        else if (dblClickMode == 'ZOOMOUT') {
            myobj.zoomRecenterPoint(pt, 1/zoomFactor);
        }
        else if (dblClickMode == 'CENTER') {
            myobj.zoomRecenterPoint(pt, 1);
        }
    }


    this.addDblClickListener(dblClickAction, this);


    this.addAspect = function(newAspect) {
        aspects.push(newAspect);

        if (curAspect == null) this.changeAspect(newAspect.name);
    };


    function printBounds(bnds) {
        alert('minx : ' + bnds.minx 
             +', maxx : ' + bnds.maxx
             +'\nminy : ' + bnds.miny
             +', maxy : ' + bnds.maxy
             );
    };


    this.changeAspect = function(aspectName) {
        for (var x = 0; x < aspects.length; x++) {
            if (aspects[x].name == aspectName) {
                curAspect = aspects[x];
                curBounds = curAspect.getBounds();
                adjustBoundsRatio(curBounds, imgSize, true);
                this.adjustGrid(curBounds);
                this.loadWMSImage();
                return true;
            }
        }
        return false;
    };

    
    this.changeDblClickMode = function (newMode) {
        if (  newMode != 'ZOOMIN' && newMode != 'ZOOMOUT' 
            &&newMode != 'CENTER' && newMode != 'NONE') {
            alert('Invalid Double-Click Action selected: ' + newMode);
            return;
        }

        dblClickMode = newMode;
    };


    this.changeZoomFactor = function (newFactor) {
        if (newFactor > 1)
            zoomFactor = newFactor;
        else
            alert('The Zoom Factor must be greater than one!');
    };


    /* DOES THIS NEED TO BE HERE?  YOU MAY BE ABLE TO DELETE IT */
    this.rowColToRS = function(row, col, r, s) {
        var status = false;
        var factor;
        
        return status;
    };


    this.zoom = function(zoomtype, param) {
        
        if (zoomtype == 'in_box') {
            if (param == null)
                param = this.getRBB().getXYInfo();
                    
            this.zoominBox(param);
        }
        else if (zoomtype == 'in_center') {
            this.zoomRecenterPoint();
        }
        else if (zoomtype == 'out_center') {
            this.zoomRecenterPoint(null,1/zoomFactor);
        }

    };


    this.zoominBox = function (xybox) {
        var row = new Array(0);
        var col = new Array(0);
        var r   = new Array(0);
        var s   = new Array(0);

        var imgSize = this.getImageSize();
        var imgRatio;
        var bndsRatio;
        var adjustCenter;
        var adjustAmount;

        var rowOff, colOff;
        var numRow, numCol;
        var newBnds = {minx:-1,miny:-1,maxx:-1,maxy:-1};
        var newGrid;

        this.xyToRowCol(xybox.x0, xybox.y0, row, col);
        curAspect.rowColToRS(row[0], col[0], r, s);
            
        rowOff = row[0];
        colOff= col[0];
        newBnds.minx = r[0];
        newBnds.maxy = s[0];

        this.xyToRowCol(xybox.x1, xybox.y1, row, col);
        curAspect.rowColToRS(row[0], col[0], r, s);

        numRow = (row[0] - rowOff);
        numCol = (col[0] - colOff);
        newBnds.maxx = r[0];
        newBnds.miny = s[0];

        curGridOffset.rowOffset = rowOff;
        curGridOffset.colOffset = colOff;

        adjustBoundsRatio(newBnds, imgSize);

        curAspect.keepInBounds(newBnds);

        this.adjustGrid(newBnds);
        this.loadWMSImage();
    };


   this.zoomRecenterPoint = function (xypoint, factor) {
        var row = new Array(0);
        var col = new Array(0);
        var r   = new Array(0);
        var s   = new Array(0);

        var rowOff, colOff;
        var numRow, numCol;
        var newGrid;

        var curdx, curdy;
        var newdx, newdy;
        
        var newBnds = {minx:-1,miny:-1,maxx:-1,maxy:-1};

        var zfactor = (factor != null)?factor:zoomFactor;

        if (!xypoint) {
            var imgDim = this.getImageSize();

            xypoint = {
                x:imgDim.width/2,
                y:imgDim.height/2
                };
        }
        
        this.xyToRowCol(xypoint.x, xypoint.y, row, col);
        curAspect.rowColToRS(row[0], col[0], r, s);


        curdx = curBounds.maxx - curBounds.minx;
        curdy = curBounds.maxy - curBounds.miny;

        newdx = curdx / zfactor;
        newdy = curdy / zfactor;

        newBnds.minx = r[0] - (newdx/2);
        newBnds.maxx = r[0] + (newdx/2);
        newBnds.miny = s[0] - (newdy/2);
        newBnds.maxy = s[0] + (newdy/2);

        curAspect.keepInBounds(newBnds);

        this.adjustGrid(newBnds);
        this.loadWMSImage();
    };


    this.pan = function(xdir, ydir) {
        var shift_x = (curBounds.maxx - curBounds.minx) * .75 * xdir;
        var shift_y = (curBounds.maxy - curBounds.miny) * .75 * ydir;
        var newBnds = {minx:0,maxx:0,miny:0,maxy:0};
        var rowOff, colOff;
        var row = new Array(0);
        var col = new Array(0);
        var newGrid;

        newBnds.maxx = curBounds.maxx + shift_x;
        newBnds.minx = curBounds.minx + shift_x;
        newBnds.maxy = curBounds.maxy + shift_y;
        newBnds.miny = curBounds.miny + shift_y;

        curAspect.keepInBounds(newBnds);
        this.adjustGrid(newBnds);

        this.loadWMSImage();
    };


    this.loadWMSImage = function() {
        var iH, iW;

        var fullimg = curAspect.url 
                    + '?service=WMS'
                    + '&request=GetMap'
                    + '&version=1.1.1'
                    + '&srs=' + curAspect.srs
                    + '&format=image/jpeg'
                    + '&width='  + imgSize.width
                    + '&height=' + imgSize.height
                    + '&bbox=' + curBounds.minx + ','
                               + curBounds.miny + ','
                               + curBounds.maxx + ','
                               + curBounds.maxy
                    + '&layers=' + curAspect.layers;

        this.loadImage(fullimg, true);
    };
    
    this.setXYFromRSPolygon = function(polygon) {
    	var row = new Array(0);
    	var col = new Array(0);
    	var lat = new Array(0);
    	var lon = new Array(0);
    	var temppoly = new Array();
    	
    	if (curAspect == null) return false;
    	for (i = 0; i < polygon.length; i+=2) {
    		curAspect.rsToRowCol(polygon[i], polygon[i+1], row, col);
    		this.rowColToLatLon(row[0], col[0], lat, lon);
    		temppoly.push(lat[0]);
    		temppoly.push(lon[0]);
    	}
    	
    	return this.setXYFromLatLonPolygon(temppoly);
    };
        
    this.getMoreMousePosInfo = function (fullpoint) {
        var r = new Array(0);
        var s = new Array(0);

        curAspect.rowColToRS(fullpoint.fullrow, fullpoint.fullcol, r, s);
        fullpoint.r = r[0];
        fullpoint.s = s[0];

        return;
    };

    this.getMoreBoxInfo = function (boxinfo) {
        var r = new Array(0);
        var s = new Array(0);

        curAspect.rowColToRS(boxinfo.ul.fullrow, boxinfo.ul.fullcol, r, s);
        boxinfo.ul.r = r[0];
        boxinfo.ul.s = s[0];
        
        curAspect.rowColToRS(boxinfo.ll.fullrow, boxinfo.ll.fullcol, r, s);
        boxinfo.ll.r = r[0];
        boxinfo.ll.s = s[0];
        
        curAspect.rowColToRS(boxinfo.lr.fullrow, boxinfo.lr.fullcol, r, s);
        boxinfo.lr.r = r[0];
        boxinfo.lr.s = s[0];
        
        curAspect.rowColToRS(boxinfo.ur.fullrow, boxinfo.ur.fullcol, r, s);
        boxinfo.ur.r = r[0];
        boxinfo.ur.s = s[0];
    };


    function adjustBoundsRatio(bnds, imgSize, crop) {
        var imgRatio  = imgSize.width/imgSize.height;
        var bndsRatio = (bnds.maxx-bnds.minx)/(bnds.maxy-bnds.miny);

        if (crop) {
            bndsRatio = -bndsRatio;
            imgRatio  = -imgRatio;
        }
           

        if (bndsRatio < imgRatio) {
            adjustAmount = (bnds.maxx-bnds.minx) * (imgRatio / bndsRatio) * 0.5;
            adjustCenter = (bnds.minx+bnds.maxx) / 2;
            bnds.minx = adjustCenter - adjustAmount;
            bnds.maxx = adjustCenter + adjustAmount;
        }
        else if (bndsRatio > imgRatio) {
            adjustAmount = (bnds.maxy-bnds.miny) * (bndsRatio / imgRatio) * 0.5;
            adjustCenter = (bnds.miny+bnds.maxy) / 2;
            bnds.miny = adjustCenter - adjustAmount;
            bnds.maxy = adjustCenter + adjustAmount;
        } 
    }


    this.adjustGrid = function(bnds) {
        var row = new Array(0);
        var col = new Array(0);
        var rowOff;
        var colOff;
        var numRow;
        var numCol;
        var newGrid;
    
        curAspect.rsToRowCol(bnds.minx, bnds.maxy, row, col);
        rowOff = row[0];
        colOff = col[0];
        curAspect.rsToRowCol(bnds.maxx, bnds.miny, row, col);
        numRow = (row[0] - rowOff);
        numCol = (col[0] - colOff);

        curBounds = bnds;

        newGrid = curAspect.regrid(bnds, numRow, numCol);
        this.loadGrid(newGrid, rowOff, colOff);
    }
    
    function adjustPanButtons() {
        if (buttons.panleft == null) return;
        
        var xMid = imgSize.width/2;
        var yMid = imgSize.height/2;
        var xMin = 5;
        var yMin = 5;
        var xMax = imgSize.width-5;
        var yMax = imgSize.height-5;
        
        buttons.panleft.setShape([{x:xMin,y:yMid}, {x:xMin+10,y:yMid-10}, 
                                  {x:xMin+10,y:yMid+10}, {x:xMin,y:yMid}]);
        buttons.panright.setShape([{x:xMax,y:yMid}, {x:xMax-10,y:yMid-10},
                                   {x:xMax-10,y:yMid+10}, {x:xMax,y:yMid}]);
        buttons.panup.setShape([{x:xMid,y:yMin}, {x:xMid-10,y:yMin+10},
                                {x:xMid+10,y:yMin+10}, {x:xMid,y:yMin}]);
        buttons.pandown.setShape([{x:xMid,y:yMax}, {x:xMid-10,y:yMax-10},
                                  {x:xMid+10,y:yMax-10}, {x:xMid,y:yMax}]);
    }
                                
            
    function addPanButtons(mapCanvas, dojoCanvas) {
        if (buttons.panleft != null) return;
        if (dojoCanvas == null) return;
        
        buttons.panleft  = dojoCanvas.createPolyline()
                                 .setStroke({color:zoom_pan_line, width:1})
                                 .setFill(zoom_pan_fill);
        buttons.panleft.connect("onclick", function(evt){mapCanvas.pan(-1,0); evt.stopPropagation();});
        
        buttons.panright = dojoCanvas.createPolyline()
                                 .setStroke({color:zoom_pan_line, width:1})
                                 .setFill(zoom_pan_fill);
        buttons.panright.connect("onclick", function(evt){mapCanvas.pan(1,0); evt.stopPropagation();});
        
        buttons.panup    = dojoCanvas.createPolyline()
                                 .setStroke({color:zoom_pan_line, width:1})
                                 .setFill(zoom_pan_fill);
        buttons.panup.connect("onclick", function(evt){mapCanvas.pan(0,1); evt.stopPropagation();});
        
        buttons.pandown  = dojoCanvas.createPolyline()
                                 .setStroke({color:zoom_pan_line, width:1})
                                 .setFill(zoom_pan_fill);
        buttons.pandown.connect("onclick", function(evt){mapCanvas.pan(0,-1); evt.stopPropagation();});
        
        adjustPanButtons();
    }
    
    
    function addZoomButtons(mapCanvas, dojoCanvas) {
        if (buttons.zoomin != null) return;
        if (dojoCanvas == null) return;
        
        buttons.zoomin = dojoCanvas.createGroup();
        buttons.zoomin.createPolyline()
                      .setStroke({color:zoom_pan_line, width:1})
                      .setFill(zoom_pan_fill)
                      .setShape([{x:5,y:5},{x:21,y:5},{x:21,y:21},{x:5,y:21},{x:5,y:5}]);
        buttons.zoomin.createLine()
                      .setStroke({color:zoom_pan_line, width:2})
                      .setShape({x1:13,y1:8,x2:13,y2:18});
        buttons.zoomin.createLine()
        			  .setStroke({color:zoom_pan_line, width:2})
        			  .setShape({x1:8,y1:13,x2:18,y2:13});
        buttons.zoomin.connect("onclick", function(evt){mapCanvas.zoom('in_center'); evt.stopPropagation(); });
        
        buttons.zoomout = dojoCanvas.createGroup();
        buttons.zoomout.createPolyline()
                       .setStroke({color:zoom_pan_line, width:1})
                       .setFill(zoom_pan_fill)
                       .setShape([{x:5,y:30},{x:21,y:30},{x:21,y:46},{x:5,y:46},{x:5,y:30}]);
        buttons.zoomout.createLine()
        			   .setStroke({color:zoom_pan_line, width:2})
        			   .setShape({x1:8,y1:38,x2:18,y2:38});
        buttons.zoomout.connect("onclick", function(evt){mapCanvas.zoom('out_center'); evt.stopPropagation(); });
    }
    
    
    this.showPanButtons = function() {
    	var canvas = this.getCanvas();
        if (buttons.panleft == null) addPanButtons(this, canvas);
    }
    
    
    this.showZoomButtons = function() {
    	var canvas = this.getCanvas();
    	if (buttons.zoomin == null) addZoomButtons(this, canvas);
    }
}
/*====
 * WMSAspect - An Aspect class for the WMSMapCanvas object
 *
 * 22-Apr-2008 S.Lewis scott.lewis@nsidc.org 303-492-6908
 *      (Javascript version)
 *=====*/

//this.bounds has the following properties:
//  minx
//  miny
//  maxx
//  maxy

function WMSAspect(newName, newURL, newSRS, newBounds, newLayers, newRows, newCols, newProjection) {

    this.name = newName;
    this.url = newURL;
    this.srs = newSRS;
    this.bounds = newBounds;
    this.layers = newLayers;
    this.proj = newProjection;
    this.baseGrid = null;

    this.regrid = function(bnds, rows, cols) {
   
        var origcol = (cols * -bnds.minx) / (bnds.maxx-bnds.minx);
        var origrow = (rows * -bnds.maxy) / (bnds.miny-bnds.maxy);
        var colspmu = cols / (bnds.maxx-bnds.minx);
        var rowspmu = rows / (bnds.maxy-bnds.miny);

        var newGrid = new Grids();

        newGrid.initialize_grid(origcol, origrow, colspmu, rowspmu, 
                                  cols, rows, this.proj);

        return newGrid;
    };

    this.baseGrid = this.regrid(newBounds, newRows, newCols);

    
    this.getBounds = function() {
        var tmpBnds = {
            minx: this.bounds.minx,
            miny: this.bounds.miny,
            maxx: this.bounds.maxx,
            maxy: this.bounds.maxy
            };
        
        return tmpBnds;
    };
    
    this.rowColToRS = function(row, col, r, s) {
        var status = false;
        var factor;
    
        if (this.baseGrid) {
            factor = (this.bounds.maxx - this.bounds.minx) / this.baseGrid.cols;
            r[0] = (col * factor) + this.bounds.minx;

            factor = (this.bounds.miny - this.bounds.maxy) / this.baseGrid.rows;
            s[0] = (row * factor) + this.bounds.maxy;
            
            status = true;
        }

        return status;
    };


    this.rsToRowCol = function(r, s, row, col) {
        var status = false;
        var factor;

        if (this.baseGrid) {
            factor = this.baseGrid.cols / (this.bounds.maxx - this.bounds.minx);
            col[0] = (r - this.bounds.minx) * factor;

            factor = this.baseGrid.rows / (this.bounds.miny - this.bounds.maxy);
            row[0] = (s - this.bounds.maxy) * factor;

            status = true;
        }
        
        return status;
    };


    this.keepInBounds = function(newBnds) {
        var newBnds_dx = newBnds.maxx - newBnds.minx;
        var newBnds_dy = newBnds.maxy - newBnds.miny;
        var thisBnds_dx = this.bounds.maxx - this.bounds.minx;
        var thisBnds_dy = this.bounds.maxy - this.bounds.miny;

        dx_ratio = newBnds_dx / thisBnds_dx;
        dy_ratio = newBnds_dy / thisBnds_dy;

        if (dx_ratio > 1 || dy_ratio > 1) {
            var rescale = (dx_ratio > dy_ratio ? dx_ratio : dy_ratio);

            var ox = newBnds.maxx;
            var oy = newBnds.maxy;
            newBnds.maxx -= (newBnds_dx - (newBnds_dx / rescale));
            newBnds.maxy -= (newBnds_dy - (newBnds_dy / rescale));
        }
        
        if (newBnds.minx < this.bounds.minx) {
            newBnds.maxx += this.bounds.minx - newBnds.minx;
            newBnds.minx = this.bounds.minx;
        }
        if (newBnds.maxx > this.bounds.maxx) {
            newBnds.minx -= newBnds.maxx - this.bounds.maxx;
            newBnds.maxx = this.bounds.maxx;
        }

        if (newBnds.miny < this.bounds.miny) {
            newBnds.maxy += this.bounds.miny - newBnds.miny;
            newBnds.miny = this.bounds.miny;
        }
        if (newBnds.maxy > this.bounds.maxy) {
            newBnds.miny -= newBnds.maxy - this.bounds.maxy;
            newBnds.maxy = this.bounds.maxy;
        }
    };
}

