// JavaScript Document

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Latitude/longitude spherical geodesy formulae & scripts (c) Chris Veness 2002-2010            */
/*   - www.movable-type.co.uk/scripts/latlong.html                                                */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */


/**
* Creates a point on the earth's surface at the supplied latitude / longitude
*
* @constructor
* @param {Number} ew_lat: latitude in numeric degrees
* @param {Number} ew_lon: longitude in numeric degrees
* @param {Number} [ew_rad=6371]: radius of earth if different value is required from standard 6,371km
*/
function ew_LatLon(ew_lat, ew_lon, ew_rad) {
    if (typeof ew_rad == 'undefined') ew_rad = 6371;  // earth's mean radius in km
    this._ew_lat = ew_lat;
    this._ew_lon = ew_lon;
    this._ew_radius = ew_rad;
}


/**
* Returns the distance from this point to the supplied point, in km 
* (using Haversine formula)
*
* from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine",
*       Sky and Telescope, vol 68, no 2, 1984
*
* @param   {ew_LatLon} ew_point: Latitude/longitude of destination point
* @param   {ew_Number} [ew_precision=4]: no of significant digits to use for returned value
* @returns {ew_Number} Distance in km between this point and destination point
*/
ew_LatLon.prototype.ew_distanceTo = function(ew_point, ew_precision) {
    // default 4 sig figs reflects typical 0.3% accuracy of spherical model
    if (typeof ew_precision == 'undefined') ew_precision = 4;  

    var ew_R = this._ew_radius;
    var ew_lat1 = this._ew_lat.ew_toRad(), ew_lon1 = this._ew_lon.ew_toRad();
    var ew_lat2 = ew_point._ew_lat.ew_toRad(), ew_lon2 = ew_point._ew_lon.ew_toRad();
    var ew_dLat = ew_lat2 - ew_lat1;
    var ew_dLon = ew_lon2 - ew_lon1;

    var ew_a = Math.sin(ew_dLat/2) * Math.sin(ew_dLat/2) +
    Math.cos(ew_lat1) * Math.cos(ew_lat2) * 
    Math.sin(ew_dLon/2) * Math.sin(ew_dLon/2);
    var ew_c = 2 * Math.atan2(Math.sqrt(ew_a), Math.sqrt(1-ew_a));
    var ew_d = ew_R * ew_c;
    return ew_d.ew_toPrecisionFixed(ew_precision);
}


/**
* Returns the (initial) bearing from this point to the supplied point, in degrees
*   see http://williams.best.vwh.net/avform.htm#Crs
*
* @param   {ew_LatLon} ew_point: Latitude/longitude of destination point
* @returns {ew_Number} Initial bearing in degrees from North
*/
ew_LatLon.prototype.ew_bearingTo = function(ew_point) {
    var ew_lat1 = this._ew_lat.ew_toRad(), ew_lat2 = point._ew_lat.ew_toRad();
    var ew_dLon = (ew_point._ew_lon-this._ew_lon).ew_toRad();

    var ew_y = Math.sin(ew_dLon) * Math.cos(ew_lat2);
    var ew_x = Math.cos(ew_lat1)*Math.sin(ew_lat2) -
    Math.sin(ew_lat1)*Math.cos(ew_lat2)*Math.cos(ew_dLon);
    var ew_brng = Math.atan2(ew_y, ew_x);

    return (ew_brng.ew_toDeg()+360) % 360;
}


/**
* Returns final bearing arriving at supplied destination point from this point; the final bearing 
* will differ from the initial bearing by varying degrees according to distance and latitude
*
* @param   {ew_LatLon} ew_point: Latitude/longitude of destination point
* @returns {ew_Number} Final bearing in degrees from North
*/
ew_LatLon.prototype.ew_finalBearingTo = function(ew_point) {
    // get initial bearing from supplied point back to this point...
    var ew_lat1 = ew_point._ew_lat.ew_toRad(), ew_lat2 = this._ew_lat.ew_toRad();
    var ew_dLon = (this._ew_lon-ew_point._ew_lon).ew_toRad();

    var ew_y = Math.sin(ew_dLon) * Math.cos(ew_lat2);
    var ew_x = Math.cos(ew_lat1)*Math.sin(ew_lat2) -
    Math.sin(ew_lat1)*Math.cos(ew_lat2)*Math.cos(ew_dLon);
    var ew_brng = Math.atan2(ew_y, ew_x);

    // ... & reverse it by adding 180°
    return (ew_brng.ew_toDeg()+180) % 360;
}


/**
* Returns the midpoint between this point and the supplied point.
*   see http://mathforum.org/library/drmath/view/51822.html for derivation
*
* @param   {ew_LatLon} ew_point: Latitude/longitude of destination point
* @returns {ew_LatLon} Midpoint between this point and the supplied point
*/
ew_LatLon.prototype.ew_midpointTo = function(ew_point) {
    ew_lat1 = this._ew_lat.ew_toRad(), ew_lon1 = this._ew_lon.ew_toRad();
    ew_lat2 = ew_point._ew_lat.ew_toRad();
    var ew_dLon = (ew_point._ew_lon-this._ew_lon).ew_toRad();

    var ew_Bx = Math.cos(ew_lat2) * Math.cos(ew_dLon);
    var ew_By = Math.cos(ew_lat2) * Math.sin(ew_dLon);

    ew_lat3 = Math.atan2(Math.sin(ew_lat1)+Math.sin(ew_lat2),
    Math.sqrt( (Math.cos(ew_lat1)+ew_Bx)*(Math.cos(ew_lat1)+ew_Bx) + ew_By*ew_By) );
    ew_lon3 = ew_lon1 + Math.atan2(ew_By, Math.cos(ew_lat1) + ew_Bx);

    return new ew_LatLon(ew_lat3.toDeg(), ew_lon3.toDeg());
}


/**
* Returns the destination point from this point having travelled the given distance (in km) on the 
* given initial bearing (bearing may vary before destination is reached)
*
*   see http://williams.best.vwh.net/avform.htm#LL
*
* @param   {Number} brng: Initial bearing in degrees
* @param   {Number} dist: Distance in km
* @returns {LatLon} Destination point
*/
ew_LatLon.prototype.destinationPoint = function(ew_brng, ew_dist) {
    ew_dist = ew_dist/this._ew_radius;  // convert dist to angular distance in radians
    ew_brng = ew_brng.ew_toRad();  // 
    var ew_lat1 = this._ew_lat.ew_toRad(), ew_lon1 = this._ew_lon.ew_toRad();

    var ew_lat2 = Math.asin( Math.sin(ew_lat1)*Math.cos(ew_dist) + 
    Math.cos(ew_lat1)*Math.sin(ew_dist)*Math.cos(ew_brng) );
    var ew_lon2 = ew_lon1 + Math.atan2(Math.sin(ew_brng)*Math.sin(ew_dist)*Math.cos(ew_lat1), 
    Math.cos(ew_dist)-Math.sin(ew_lat1)*Math.sin(ew_lat2));
    ew_lon2 = (ew_lon2+3*Math.PI)%(2*Math.PI) - Math.PI;  // normalise to -180...+180

    if (isNaN(ew_lat2) || isNaN(ew_lon2)) return null;
    return new ew_LatLon(ew_lat2.ew_toDeg(), ew_lon2.ew_toDeg());
}


/**
* Returns the point of intersection of two paths defined by point and bearing
*
*   see http://williams.best.vwh.net/avform.htm#Intersection
*
* @param   {ew_LatLon} p1: First point
* @param   {ew_Number} brng1: Initial bearing from first point
* @param   {ew_LatLon} p2: Second point
* @param   {ew_Number} brng2: Initial bearing from second point
* @returns {ew_LatLon} Destination point (null if no unique intersection defined)
*/
ew_LatLon.ew_intersection = function(ew_p1, ew_brng1, ew_p2, ew_brng2) {
    ew_lat1 = ew_p1._lat.toRad(), ew_lon1 = ew_p1._ew_lon.ew_toRad();
    ew_lat2 = ew_p2._lat.toRad(), ew_lon2 = ew_p2._lon.ew_toRad();
    ew_brng13 = ew_brng1.ew_toRad(), ew_brng23 = ew_brng2.ew_toRad();
    ew_dLat = ew_lat2-ew_lat1, ew_dLon = ew_lon2-ew_lon1;

    ew_dist12 = 2*Math.asin( Math.sqrt( Math.sin(ew_dLat/2)*Math.sin(ew_dLat/2) + 
    Math.cos(ew_lat1)*Math.cos(ew_lat2)*Math.sin(ew_dLon/2)*Math.sin(ew_dLon/2) ) );
    if (ew_dist12 == 0) return null;

    // initial/final bearings between points
    ew_brngA = Math.acos( ( Math.sin(ew_lat2) - Math.sin(ew_lat1)*Math.cos(ew_dist12) ) / 
    ( Math.sin(ew_dist12)*Math.cos(ew_lat1) ) );
    if (isNaN(ew_brngA)) ew_brngA = 0;  // protect against rounding
    ew_brngB = Math.acos( ( Math.sin(ew_lat1) - Math.sin(ew_lat2)*Math.cos(ew_dist12) ) / 
    ( Math.sin(ew_dist12)*Math.cos(ew_lat2) ) );

    if (Math.sin(ew_lon2-ew_lon1) > 0) {
        ew_brng12 = ew_brngA;
        ew_brng21 = 2*Math.PI - ew_brngB;
    } else {
        ew_brng12 = 2*Math.PI - ew_brngA;
        ew_brng21 = ew_brngB;
    }

    ew_alpha1 = (ew_brng13 - ew_brng12 + Math.PI) % (2*Math.PI) - Math.PI;  // ew_angle 2-1-3
    ew_alpha2 = (ew_brng21 - ew_brng23 + Math.PI) % (2*Math.PI) - Math.PI;  // ew_angle 1-2-3

    if (Math.sin(ew_alpha1)==0 && Math.sin(ew_alpha2)==0) return null;  // infinite intersections
    if (Math.sin(ew_alpha1)*Math.sin(ew_alpha2) < 0) return null;       // ambiguous intersection

    //ew_alpha1 = Math.abs(ew_alpha1);
    //ew_alpha2 = Math.abs(ew_alpha2);
    // ... Ed Williams takes abs of alpha1/alpha2, but seems to break calculation?

    ew_alpha3 = Math.acos( -Math.cos(ew_alpha1)*Math.cos(ew_alpha2) + 
    Math.sin(ew_alpha1)*Math.sin(ew_alpha2)*Math.cos(ew_dist12) );
    ew_dist13 = Math.atan2( Math.sin(ew_dist12)*Math.sin(ew_alpha1)*Math.sin(ew_alpha2), 
    Math.cos(ew_alpha2)+Math.cos(ew_alpha1)*Math.cos(ew_alpha3) )
    ew_lat3 = Math.asin( Math.sin(ew_lat1)*Math.cos(ew_dist13) + 
    Math.cos(ew_lat1)*Math.sin(ew_dist13)*Math.cos(ew_brng13) );
    ew_dLon13 = Math.atan2( Math.sin(ew_brng13)*Math.sin(ew_dist13)*Math.cos(ew_lat1), 
    Math.cos(ew_dist13)-Math.sin(ew_lat1)*Math.sin(ew_lat3) );
    ew_lon3 = ew_lon1+ew_dLon13;
    ew_lon3 = (ew_lon3+Math.PI) % (2*Math.PI) - Math.PI;  // normalise to -180..180º

    return new ew_LatLon(ew_lat3.ew_toDeg(), ew_lon3.ew_toDeg());
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/**
* Returns the distance from this point to the supplied point, in km, travelling along a rhumb line
*
*   see http://williams.best.vwh.net/avform.htm#Rhumb
*
* @param   {ew_LatLon} point: Latitude/longitude of destination point
* @returns {ew_Number} Distance in km between this point and destination point
*/
ew_LatLon.prototype.ew_rhumbDistanceTo = function(ew_point) {
    var ew_R = this._ew_radius;
    var ew_lat1 = this._ew_lat.ew_toRad(), ew_lat2 = ew_point._ew_lat.ew_toRad();
    var ew_dLat = (ew_point._ew_lat-this._ew_lat).ew_toRad();
    var ew_dLon = Math.abs(ew_point._ew_lon-this._ew_lon).ew_toRad();

    var ew_dPhi = Math.log(Math.tan(ew_lat2/2+Math.PI/4)/Math.tan(ew_lat1/2+Math.PI/4));
    var ew_q = (!isNaN(ew_dLat/ew_dPhi)) ? ew_dLat/ew_dPhi : Math.cos(ew_lat1);  // E-W line gives ew_dPhi=0
    // if ew_dLon over 180° take shorter rhumb across 180° meridian:
    if (ew_dLon > Math.PI) ew_dLon = 2*Math.PI - ew_dLon;
    var ew_dist = Math.sqrt(ew_dLat*ew_dLat + ew_q*ew_q*ew_dLon*ew_dLon) * ew_R; 

    return ew_dist.ew_toPrecisionFixed(4);  // 4 sig figs reflects typical 0.3% accuracy of spherical model
}

/**
* Returns the bearing from this point to the supplied point along a rhumb line, in degrees
*
* @param   {ew_LatLon} point: Latitude/longitude of destination point
* @returns {ew_Number} Bearing in degrees from North
*/
ew_LatLon.prototype.ew_rhumbBearingTo = function(ew_point) {
    var ew_lat1 = this._ew_lat.ew_toRad(), ew_lat2 = ew_point._ew_lat.ew_toRad();
    var ew_dLon = (ew_point._ew_lon-this._ew_lon).ew_toRad();

    var ew_dPhi = Math.log(Math.tan(ew_lat2/2+Math.PI/4)/Math.tan(ew_lat1/2+Math.PI/4));
    if (Math.abs(ew_dLon) > Math.PI) ew_dLon = ew_dLon>0 ? -(2*Math.PI-ew_dLon) : (2*Math.PI+ew_dLon);
    var ew_brng = Math.atan2(ew_dLon, ew_dPhi);

    return (ew_brng.ew_toDeg()+360) % 360;
}

/**
* Returns the destination point from this point having travelled the given distance (in km) on the 
* given bearing along a rhumb line
*
* @param   {ew_Number} brng: Bearing in degrees from North
* @param   {ew_Number} dist: Distance in km
* @returns {ew_LatLon} Destination point
*/
ew_LatLon.prototype.ew_rhumbDestinationPoint = function(ew_brng, ew_dist) {
    var ew_R = this._ew_radius;
    var ew_d = parseFloat(ew_dist)/ew_R;  // ew_d = angular distance covered on earth's surface
    var ew_lat1 = this._ew_lat.ew_toRad(), ew_lon1 = this._ew_lon.ew_toRad();
    ew_brng = ew_brng.ew_toRad();

    var ew_lat2 = ew_lat1 + ew_d*Math.cos(ew_brng);
    var ew_dLat = ew_lat2-ew_lat1;
    var ew_dPhi = Math.log(Math.tan(ew_lat2/2+Math.PI/4)/Math.tan(ew_lat1/2+Math.PI/4));
    var ew_q = (!isNaN(ew_dLat/ew_dPhi)) ? ew_dLat/ew_dPhi : Math.cos(ew_lat1);  // ew_E-ew_W line gives ew_dPhi=0
    var ew_dLon = ew_d*Math.sin(ew_brng)/ew_q;
    // check for some daft bugger going past the pole
    if (Math.abs(ew_lat2) > Math.PI/2) ew_lat2 = ew_lat2>0 ? Math.PI-ew_lat2 : -(Math.PI-ew_lat2);
    ew_lon2 = (ew_lon1+ew_dLon+3*Math.PI)%(2*Math.PI) - Math.PI;

    return new ew_LatLon(ew_lat2.ew_toDeg(), ew_lon2.ew_toDeg());
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */


/**
* Returns the latitude of this point; signed numeric degrees if no format, otherwise format & dp 
* as per ew_Geo.ew_toLat()
*
* @param   {ew_String} [format]: Return value as 'd', 'dm', 'dms'
* @param   {ew_Number} [ew_dp=0|2|4]: No of decimal places to display
* @returns {ew_Number|String} Numeric degrees if no format specified, otherwise deg/min/sec
*
* @requires ew_Geo
*/
ew_LatLon.prototype.ew_lat = function(ew_format, ew_dp) {
    if (typeof format == 'undefined') return this._ew_lat;

    return ew_Geo.ew_toLat(this._ew_lat, ew_format, ew_dp);
}

/**
* Returns the longitude of this point; signed numeric degrees if no format, otherwise format & dp 
* as per ew_Geo.toLon()
*
* @param   {String} [format]: Return value as 'd', 'dm', 'dms'
* @param   {Number} [dp=0|2|4]: No of decimal places to display
* @returns {Number|String} Numeric degrees if no format specified, otherwise deg/min/sec
*
* @requires ew_Geo
*/
ew_LatLon.prototype.ew_lon = function(ew_format, ew_dp) {
    if (typeof ew_format == 'undefined') return this._ew_lon;

    return ew_Geo.ew_toLon(this._ew_lon, ew_format, ew_dp);
}

/**
* Returns a string representation of this ew_point; ew_format and ew_dp as per ew_lat()/ew_lon()
*
* @param   {ew_String} [ew_format]: Return value as 'd', 'dm', 'dms'
* @param   {ew_Number} [dp=0|2|4]: No of decimal places to display
* @returns {ew_String} Comma-separated latitude/longitude
*
* @requires ew_Geo
*/
ew_LatLon.prototype.ew_toString = function(ew_format, ew_dp) {
    if (typeof ew_format == 'undefined') ew_format = 'dms';

    return ew_Geo.ew_toLat(this._ew_lat, ew_format, ew_dp) + ', ' + ew_Geo.ew_toLon(this._ew_lon, ew_format, ew_dp);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

// extend Number object with methods for converting degrees/radians

/** Convert numeric degrees to radians */
if (typeof(String.prototype.ew_toRad) === "undefined") {
    Number.prototype.ew_toRad = function() {
        return this * Math.PI / 180;
    }
}

/** Convert radians to numeric (signed) degrees */
if (typeof(String.prototype.ew_toDeg) === "undefined") {
    Number.prototype.ew_toDeg = function() {
        return this * 180 / Math.PI;
    }
}

/** 
* Format the significant digits of a number, using only fixed-point notation (no exponential)
* 
* @param   {Number} precision: Number of significant digits to appear in the returned string
* @returns {String} A string representation of number which contains precision significant digits
*/
if (typeof(String.prototype.ew_toDeg) === "undefined") {
    Number.prototype.ew_toPrecisionFixed = function(ew_precision) {
        var ew_numb = this < 0 ? -this : this;  // can't take log of -ve number...
        var ew_sign = this < 0 ? '-' : '';

        if (ew_numb == 0) { ew_n = '0.'; while (ew_precision--) ew_n += '0'; return ew_n };  // can't take log of zero

        var ew_scale = Math.ceil(Math.log(ew_numb)*Math.LOG10E);  // no of digits before decimal
        var ew_n = String(Math.round(ew_numb * Math.pow(10, ew_precision-ew_scale)));
        if (ew_scale > 0) {  // add trailing zeros & insert decimal as required
            ew_l = ew_scale - ew_n.length;
            while (ew_l-- > 0) ew_n = ew_n + '0';
            if (ew_scale < ew_n.length) ew_n = ew_n.slice(0,ew_scale) + '.' + ew_n.slice(ew_scale);
        } else {          // prefix decimal and leading zeros if required
            while (ew_scale++ < 0) ew_n = '0' + ew_n;
            ew_n = '0.' + ew_n;
        }
        return ew_sign + ew_n;
    }
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Geodesy representation conversion functions (c) Chris Veness 2002-2010                        */
/*   - www.movable-type.co.uk/scripts/latlong.html                                                */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var ew_Geo = {};  // Geo namespace, representing static class

/**
* Parses string representing degrees/minutes/seconds into numeric degrees
*
* This is very flexible on formats, allowing signed decimal degrees, or deg-min-sec optionally
* suffixed by compass direction (NSEW). A variety of separators are accepted (eg 3º 37' 09"W) 
* or fixed-width format without separators (eg 0033709W). Seconds and minutes may be omitted. 
* (Note minimal validation is done).
*
* @param   {String|Number} dmsStr: Degrees or deg/min/sec in variety of formats
* @returns {Number} Degrees as decimal number
* @throws  {TypeError} dmsStr is an object, perhaps DOM object without .value?
*/
ew_Geo.ew_parseDMS = function(ew_dmsStr) {
    if (typeof ew_deg == 'object') throw new TypeError('ew_Geo.parseDMS - ew_dmsStr is [DOM?] object');

    if (!isNaN(ew_dmsStr)) return Number(ew_dmsStr);  // ... signed decimal degrees without NSEW

    // strip off any sign or compass dir'n & split out separate d/m/s
    var ew_dms = String(ew_dmsStr).trim().replace(/^-/,'').replace(/[NSEW]$/i,'').split(/[^0-9.,]+/);
    if (ew_dms[ew_dms.length-1]=='') ew_dms.splice(ew_dms.length-1);  // from trailing symbol

    if (ew_dms == '') return NaN;

    // and convert to decimal degrees...
    switch (ew_dms.length) {
        case 3:  // interpret 3-part result as d/m/s
            var ew_deg = ew_dms[0]/1 + ew_dms[1]/60 + ew_dms[2]/3600; 
            break;
        case 2:  // interpret 2-part result as d/m
            var ew_deg = ew_dms[0]/1 + ew_dms[1]/60; 
            break;
        case 1:  // just d (possibly decimal) or non-separated dddmmss
            var ew_deg = ew_dms[0];
            // check for fixed-width unseparated format eg 0033709W
            if (/[NS]/i.test(ew_dmsStr)) ew_deg = '0' + ew_deg;  // - normalise N/S to 3-digit degrees
            if (/[0-9]{7}/.test(ew_deg)) ew_deg = ew_deg.slice(0,3)/1 + ew_deg.slice(3,5)/60 + ew_deg.slice(5)/3600; 
            break;
        default:
            return NaN;
    }
    if (/^-|[WS]$/i.test(ew_dmsStr.trim())) ew_deg = -ew_deg; // take '-', west and south as -ve
    return Number(ew_deg);
}

/**
* Convert decimal degrees to deg/min/sec format
*  - degree, prime, double-prime symbols are added, but sign is discarded, though no compass
*    direction is added
*
* @private
* @param   {Number} deg: Degrees
* @param   {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* @param   {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* @returns {String} deg formatted as deg/min/secs according to specified format
* @throws  {TypeError} deg is an object, perhaps DOM object without .value?
*/
ew_Geo.ew_toDMS = function(ew_deg, ew_format, ew_dp) {
    if (typeof ew_deg == 'object') throw new TypeError('ew_Geo.toDMS - ew_deg is [DOM?] object');
    if (isNaN(ew_deg)) return '';  // give up here if we can't make a number from deg

    // default values
    if (typeof ew_format == 'undefined') format = 'dms';
    if (typeof ew_dp == 'undefined') {
        switch (ew_format) {
            case 'd': ew_dp = 4; break;
            case 'dm': ew_dp = 2; break;
            case 'dms': ew_dp = 0; break;
            default: ew_format = 'dms'; ew_dp = 0;  // be forgiving on invalid format
        }
    }

    deg = Math.abs(deg);  // (unsigned result ready for appending compass dir'n)

    switch (ew_format) {
        case 'd':
            ew_d = ew_deg.ew_toFixed(ew_dp);     // round degrees
            if (ew_d<100) ew_d = '0' + ew_d;  // pad with leading zeros
            if (ew_d<10) ew_d = '0' + ew_d;
            ew_dms = ew_d + '\u00B0';      // add º symbol
            break;
        case 'dm':
            var ew_min = (deg*60).ew_toFixed(ew_dp);  // convert degrees to minutes & round
            var ew_d = Math.floor(ew_min / 60);    // get component deg/min
            var ew_m = (ew_min % 60).ew_toFixed(ew_dp);  // pad with trailing zeros
            if (ew_d<100) ew_d = '0' + ew_d;          // pad with leading zeros
            if (ew_d<10) ew_d = '0' + ew_d;
            if (ew_m<10) ew_m = '0' + ew_m;
            ew_dms = ew_d + '\u00B0' + ew_m + '\u2032';  // add º, ' symbols
            break;
        case 'dms':
            var ew_sec = (ew_deg*3600).ew_toFixed(ew_dp);  // convert degrees to seconds & round
            var ew_d = Math.floor(ew_sec / 3600);    // get component deg/min/sec
            var ew_m = Math.floor(ew_sec/60) % 60;
            var ew_s = (ew_sec % 60).ew_toFixed(ew_dp);    // pad with trailing zeros
            if (ew_d<100) ew_d = '0' + ew_d;            // pad with leading zeros
            if (ew_d<10) ew_d = '0' + ew_d;
            if (ew_m<10) ew_m = '0' + ew_m;
            if (ew_s<10) ew_s = '0' + ew_s;
            ew_dms = ew_d + '\u00B0' + ew_m + '\u2032' + ew_s + '\u2033';  // add º, ', " symbols
            break;
    }

    return ew_dms;
}

/**
* Convert numeric degrees to deg/min/sec latitude (suffixed with N/S)
*
* @param   {Number} deg: Degrees
* @param   {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* @param   {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* @returns {String} Deg/min/seconds
*/
ew_Geo.toLat = function(ew_deg, ew_format, ew_dp) {
    var ew_lat = ew_Geo.toDMS(ew_deg, ew_format, ew_dp);
    return ew_lat=='' ? '' : ew_lat.slice(1) + (ew_deg<0 ? 'S' : 'N');  // knock off initial '0' for lat!
}

/**
* Convert numeric degrees to deg/min/sec longitude (suffixed with E/W)
*
* @param   {Number} deg: Degrees
* @param   {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* @param   {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* @returns {String} Deg/min/seconds
*/
ew_Geo.toLon = function(ew_deg, ew_format, ew_dp) {
    var ew_lon = ew_Geo.toDMS(ew_deg, ew_format, ew_dp);
    return ew_lon=='' ? '' : ew_lon + (ew_deg<0 ? 'W' : 'E');
}

/**
* Convert numeric degrees to deg/min/sec as a bearing (0º..360º)
*
* @param   {Number} deg: Degrees
* @param   {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* @param   {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* @returns {String} Deg/min/seconds
*/
ew_Geo.toBrng = function(deg, format, dp) {
    ew_deg = (Number(ew_deg)+360) % 360;  // normalise -ve values to 180º..360º
    var ew_brng =  ew_Geo.toDMS(ew_deg, ew_format, ew_dp);
    return ew_brng.replace('360', '0');  // just in case rounding took us up to 360º!
}



/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
