Source: x-cartesian.js

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/* ECEF cartesian coordinates                                         (c) Chris Veness 2005-2016  */
/*                                                                                   MIT Licence  */
/* www.movable-type.co.uk/scripts/latlong.html                                                    */
/* www.movable-type.co.uk/scripts/geodesy/docs/module-cartesian.html                              */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */


'use strict';
if (typeof module!='undefined' && module.exports) var LatLon = require('./latlon-ellipsoidal.js'); // ≡ import LatLon from 'latlon-ellipsoidal.js'


/**
 * A cartesian coordinate is an x/y/z value representing the position of a point relative to the
 * centre of the earth (ECEF or earth-centric earth-fixed point). It can be used as a vector with
 * units of metres.
 *
 * @module   cartesian
 * @requires latlon-ellipsoidal (tightly coupled; cyclic dependencies between toLatLon/toCartesian)
 */


/**
 * Creates cartesian coordinate representing ECEF (earth-centric earth-fixed) point.
 *
 * @constructor
 * @param {number} x - x coordinate in metres.
 * @param {number} y - y coordinate in metres.
 * @param {number} z - z coordinate in metres.
 *
 * @example
 *     var p1 = new Cartesian(3980581.210, -111.159, 4966824.522);
 */
function Cartesian(x, y, z) {
    // allow instantiation without 'new'
    if (!(this instanceof Cartesian)) return new Cartesian(x, y, z);

    // fixup Node.js cyclic references (won't be needed once es6 modules are native...)
    if (typeof module!='undefined' && Object.keys(LatLon).length==0) LatLon = require('./latlon-ellipsoidal.js');

    this.x = Number(x);
    this.y = Number(y);
    this.z = Number(z);
}


/**
 * Converts ‘this’ (geocentric) cartesian (x/y/z) coordinate to (ellipsoidal geodetic)
 * latitude/longitude point on specified datum.
 *
 * Uses Bowring’s (1985) formulation for μm precision.
 *
 * @param {LatLon.datum.transform} [datum=WGS84] - Datum to use when converting point.
 */
Cartesian.prototype.toLatLon = function(datum) {
    if (datum === undefined) datum = LatLon.datum.WGS84;

    var x = this.x, y = this.y, z = this.z;
    var a = datum.ellipsoid.a, b = datum.ellipsoid.b;

    var e2 = (a*a-b*b) / (a*a);   // 1st eccentricity squared
    var ε2 = (a*a-b*b) / (b*b);   // 2nd eccentricity squared
    var p = Math.sqrt(x*x + y*y); // distance from minor axis
    var R = Math.sqrt(p*p + z*z); // polar radius

    // parametric latitude (Bowring eqn 17, replacing tanβ = z·a / p·b)
    var tanβ = (b*z)/(a*p) * (1+ε2*b/R);
    var sinβ = tanβ / Math.sqrt(1+tanβ*tanβ);
    var cosβ = sinβ / tanβ;

    // geodetic latitude (Bowring eqn 18: tanφ = z+ε²bsin³β / p−e²cos³β)
    var φ = Math.atan2(z + ε2*b*sinβ*sinβ*sinβ, p - e2*a*cosβ*cosβ*cosβ);

    // longitude
    var λ = Math.atan2(y, x);

    // height above ellipsoid (Bowring eqn 7)
    var sinφ = Math.sin(φ), cosφ = Math.cos(φ);
    var ν = a / Math.sqrt(1-e2*sinφ*sinφ); // length of the normal terminated by the minor axis
    var h = p*cosφ + z*sinφ - (a*a/ν);

    var point = new LatLon(φ.toDegrees(), λ.toDegrees(), h, datum);

    return point;
};

/**
 * Applies Helmert (seven-parameter) transformation to ‘this’ coordinate using transform parameters t.
 *
 * @param {LatLon.datum.transform} t - Transformation to apply to this coordinate.
 */
Cartesian.prototype.applyTransform = function(t)   {
    var x1 = this.x, y1 = this.y, z1 = this.z;

    var tx = t.tx, ty = t.ty, tz = t.tz;
    var rx = (t.rx/3600).toRadians(); // normalise seconds to radians
    var ry = (t.ry/3600).toRadians(); // normalise seconds to radians
    var rz = (t.rz/3600).toRadians(); // normalise seconds to radians
    var s1 = t.s/1e6 + 1;             // normalise ppm to (s+1)

    // apply transform
    var x2 = tx + x1*s1 - y1*rz + z1*ry;
    var y2 = ty + x1*rz + y1*s1 - z1*rx;
    var z2 = tz - x1*ry + y1*rx + z1*s1;

    var point = new Cartesian(x2, y2, z2);

    return point;
};


/**
 * Returns a string representation of ‘this’ cartesian point.
 *
 * @param   {number} [dp=0|2|4] - Number of decimal places to use - default 0 for dms, 2 for dm, 4 for d.
 * @returns {string} Comma-separated latitude/longitude.
 */
Cartesian.prototype.toString = function(dp) {
    if (dp == undefined) dp = 0; // default 0 decimals
    dp = Number(dp);

    return '['+this.x.toFixed(dp)+','+this.y.toFixed(dp)+','+this.z.toFixed(dp)+','+']';
};


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/** Extend Number object with method to convert numeric degrees to radians */
if (Number.prototype.toRadians === undefined) {
    Number.prototype.toRadians = function() { return this * Math.PI / 180; };
}

/** Extend Number object with method to convert radians to numeric (signed) degrees */
if (Number.prototype.toDegrees === undefined) {
    Number.prototype.toDegrees = function() { return this * 180 / Math.PI; };
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
if (typeof module != 'undefined' && module.exports) module.exports = Cartesian; // ≡ export default Cartesian