var HALF_PI = Math.PI/2;
|
var PJD_3PARAM = 1;
|
var PJD_7PARAM = 2;
|
var PJD_GRIDSHIFT = 3;
|
var PJD_WGS84 = 4; // WGS84 or equivalent
|
var PJD_NODATUM = 5; // WGS84 or equivalent
|
var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
|
var AD_C = 1.0026000;
|
var COS_67P5 = 0.38268343236508977;
|
var datum = function(proj) {
|
if (!(this instanceof datum)) {
|
return new datum(proj);
|
}
|
this.datum_type = PJD_WGS84; //default setting
|
if (!proj) {
|
return;
|
}
|
if (proj.datumCode && proj.datumCode === 'none') {
|
this.datum_type = PJD_NODATUM;
|
}
|
|
if (proj.datum_params) {
|
this.datum_params = proj.datum_params.map(parseFloat);
|
if (this.datum_params[0] !== 0 || this.datum_params[1] !== 0 || this.datum_params[2] !== 0) {
|
this.datum_type = PJD_3PARAM;
|
}
|
if (this.datum_params.length > 3) {
|
if (this.datum_params[3] !== 0 || this.datum_params[4] !== 0 || this.datum_params[5] !== 0 || this.datum_params[6] !== 0) {
|
this.datum_type = PJD_7PARAM;
|
this.datum_params[3] *= SEC_TO_RAD;
|
this.datum_params[4] *= SEC_TO_RAD;
|
this.datum_params[5] *= SEC_TO_RAD;
|
this.datum_params[6] = (this.datum_params[6] / 1000000.0) + 1.0;
|
}
|
}
|
}
|
|
// DGR 2011-03-21 : nadgrids support
|
this.datum_type = proj.grids ? PJD_GRIDSHIFT : this.datum_type;
|
|
this.a = proj.a; //datum object also uses these values
|
this.b = proj.b;
|
this.es = proj.es;
|
this.ep2 = proj.ep2;
|
if (this.datum_type === PJD_GRIDSHIFT) {
|
this.grids = proj.grids;
|
}
|
};
|
datum.prototype = {
|
|
|
/****************************************************************/
|
// cs_compare_datums()
|
// Returns TRUE if the two datums match, otherwise FALSE.
|
compare_datums: function(dest) {
|
if (this.datum_type !== dest.datum_type) {
|
return false; // false, datums are not equal
|
}
|
else if (this.a !== dest.a || Math.abs(this.es - dest.es) > 0.000000000050) {
|
// the tolerence for es is to ensure that GRS80 and WGS84
|
// are considered identical
|
return false;
|
}
|
else if (this.datum_type === PJD_3PARAM) {
|
return (this.datum_params[0] === dest.datum_params[0] && this.datum_params[1] === dest.datum_params[1] && this.datum_params[2] === dest.datum_params[2]);
|
}
|
else if (this.datum_type === PJD_7PARAM) {
|
return (this.datum_params[0] === dest.datum_params[0] && this.datum_params[1] === dest.datum_params[1] && this.datum_params[2] === dest.datum_params[2] && this.datum_params[3] === dest.datum_params[3] && this.datum_params[4] === dest.datum_params[4] && this.datum_params[5] === dest.datum_params[5] && this.datum_params[6] === dest.datum_params[6]);
|
}
|
else if (this.datum_type === PJD_GRIDSHIFT || dest.datum_type === PJD_GRIDSHIFT) {
|
//alert("ERROR: Grid shift transformations are not implemented.");
|
//return false
|
//DGR 2012-07-29 lazy ...
|
return this.nadgrids === dest.nadgrids;
|
}
|
else {
|
return true; // datums are equal
|
}
|
}, // cs_compare_datums()
|
|
/*
|
* The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
|
* (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
|
* according to the current ellipsoid parameters.
|
*
|
* Latitude : Geodetic latitude in radians (input)
|
* Longitude : Geodetic longitude in radians (input)
|
* Height : Geodetic height, in meters (input)
|
* X : Calculated Geocentric X coordinate, in meters (output)
|
* Y : Calculated Geocentric Y coordinate, in meters (output)
|
* Z : Calculated Geocentric Z coordinate, in meters (output)
|
*
|
*/
|
geodetic_to_geocentric: function(p) {
|
var Longitude = p.x;
|
var Latitude = p.y;
|
var Height = p.z ? p.z : 0; //Z value not always supplied
|
var X; // output
|
var Y;
|
var Z;
|
|
var Error_Code = 0; // GEOCENT_NO_ERROR;
|
var Rn; /* Earth radius at location */
|
var Sin_Lat; /* Math.sin(Latitude) */
|
var Sin2_Lat; /* Square of Math.sin(Latitude) */
|
var Cos_Lat; /* Math.cos(Latitude) */
|
|
/*
|
** Don't blow up if Latitude is just a little out of the value
|
** range as it may just be a rounding issue. Also removed longitude
|
** test, it should be wrapped by Math.cos() and Math.sin(). NFW for PROJ.4, Sep/2001.
|
*/
|
if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) {
|
Latitude = -HALF_PI;
|
}
|
else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) {
|
Latitude = HALF_PI;
|
}
|
else if ((Latitude < -HALF_PI) || (Latitude > HALF_PI)) {
|
/* Latitude out of range */
|
//..reportError('geocent:lat out of range:' + Latitude);
|
return null;
|
}
|
|
if (Longitude > Math.PI) {
|
Longitude -= (2 * Math.PI);
|
}
|
Sin_Lat = Math.sin(Latitude);
|
Cos_Lat = Math.cos(Latitude);
|
Sin2_Lat = Sin_Lat * Sin_Lat;
|
Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat));
|
X = (Rn + Height) * Cos_Lat * Math.cos(Longitude);
|
Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude);
|
Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat;
|
|
p.x = X;
|
p.y = Y;
|
p.z = Z;
|
return Error_Code;
|
}, // cs_geodetic_to_geocentric()
|
|
|
geocentric_to_geodetic: function(p) {
|
/* local defintions and variables */
|
/* end-criterium of loop, accuracy of sin(Latitude) */
|
var genau = 1e-12;
|
var genau2 = (genau * genau);
|
var maxiter = 30;
|
|
var P; /* distance between semi-minor axis and location */
|
var RR; /* distance between center and location */
|
var CT; /* sin of geocentric latitude */
|
var ST; /* cos of geocentric latitude */
|
var RX;
|
var RK;
|
var RN; /* Earth radius at location */
|
var CPHI0; /* cos of start or old geodetic latitude in iterations */
|
var SPHI0; /* sin of start or old geodetic latitude in iterations */
|
var CPHI; /* cos of searched geodetic latitude */
|
var SPHI; /* sin of searched geodetic latitude */
|
var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
|
var At_Pole; /* indicates location is in polar region */
|
var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */
|
|
var X = p.x;
|
var Y = p.y;
|
var Z = p.z ? p.z : 0.0; //Z value not always supplied
|
var Longitude;
|
var Latitude;
|
var Height;
|
|
At_Pole = false;
|
P = Math.sqrt(X * X + Y * Y);
|
RR = Math.sqrt(X * X + Y * Y + Z * Z);
|
|
/* special cases for latitude and longitude */
|
if (P / this.a < genau) {
|
|
/* special case, if P=0. (X=0., Y=0.) */
|
At_Pole = true;
|
Longitude = 0.0;
|
|
/* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
|
* of ellipsoid (=center of mass), Latitude becomes PI/2 */
|
if (RR / this.a < genau) {
|
Latitude = HALF_PI;
|
Height = -this.b;
|
return;
|
}
|
}
|
else {
|
/* ellipsoidal (geodetic) longitude
|
* interval: -PI < Longitude <= +PI */
|
Longitude = Math.atan2(Y, X);
|
}
|
|
/* --------------------------------------------------------------
|
* Following iterative algorithm was developped by
|
* "Institut for Erdmessung", University of Hannover, July 1988.
|
* Internet: www.ife.uni-hannover.de
|
* Iterative computation of CPHI,SPHI and Height.
|
* Iteration of CPHI and SPHI to 10**-12 radian resp.
|
* 2*10**-7 arcsec.
|
* --------------------------------------------------------------
|
*/
|
CT = Z / RR;
|
ST = P / RR;
|
RX = 1.0 / Math.sqrt(1.0 - this.es * (2.0 - this.es) * ST * ST);
|
CPHI0 = ST * (1.0 - this.es) * RX;
|
SPHI0 = CT * RX;
|
iter = 0;
|
|
/* loop to find sin(Latitude) resp. Latitude
|
* until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
|
do {
|
iter++;
|
RN = this.a / Math.sqrt(1.0 - this.es * SPHI0 * SPHI0);
|
|
/* ellipsoidal (geodetic) height */
|
Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - this.es * SPHI0 * SPHI0);
|
|
RK = this.es * RN / (RN + Height);
|
RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST);
|
CPHI = ST * (1.0 - RK) * RX;
|
SPHI = CT * RX;
|
SDPHI = SPHI * CPHI0 - CPHI * SPHI0;
|
CPHI0 = CPHI;
|
SPHI0 = SPHI;
|
}
|
while (SDPHI * SDPHI > genau2 && iter < maxiter);
|
|
/* ellipsoidal (geodetic) latitude */
|
Latitude = Math.atan(SPHI / Math.abs(CPHI));
|
|
p.x = Longitude;
|
p.y = Latitude;
|
p.z = Height;
|
return p;
|
}, // cs_geocentric_to_geodetic()
|
|
/** Convert_Geocentric_To_Geodetic
|
* The method used here is derived from 'An Improved Algorithm for
|
* Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996
|
*/
|
geocentric_to_geodetic_noniter: function(p) {
|
var X = p.x;
|
var Y = p.y;
|
var Z = p.z ? p.z : 0; //Z value not always supplied
|
var Longitude;
|
var Latitude;
|
var Height;
|
|
var W; /* distance from Z axis */
|
var W2; /* square of distance from Z axis */
|
var T0; /* initial estimate of vertical component */
|
var T1; /* corrected estimate of vertical component */
|
var S0; /* initial estimate of horizontal component */
|
var S1; /* corrected estimate of horizontal component */
|
var Sin_B0; /* Math.sin(B0), B0 is estimate of Bowring aux variable */
|
var Sin3_B0; /* cube of Math.sin(B0) */
|
var Cos_B0; /* Math.cos(B0) */
|
var Sin_p1; /* Math.sin(phi1), phi1 is estimated latitude */
|
var Cos_p1; /* Math.cos(phi1) */
|
var Rn; /* Earth radius at location */
|
var Sum; /* numerator of Math.cos(phi1) */
|
var At_Pole; /* indicates location is in polar region */
|
|
X = parseFloat(X); // cast from string to float
|
Y = parseFloat(Y);
|
Z = parseFloat(Z);
|
|
At_Pole = false;
|
if (X !== 0.0) {
|
Longitude = Math.atan2(Y, X);
|
}
|
else {
|
if (Y > 0) {
|
Longitude = HALF_PI;
|
}
|
else if (Y < 0) {
|
Longitude = -HALF_PI;
|
}
|
else {
|
At_Pole = true;
|
Longitude = 0.0;
|
if (Z > 0.0) { /* north pole */
|
Latitude = HALF_PI;
|
}
|
else if (Z < 0.0) { /* south pole */
|
Latitude = -HALF_PI;
|
}
|
else { /* center of earth */
|
Latitude = HALF_PI;
|
Height = -this.b;
|
return;
|
}
|
}
|
}
|
W2 = X * X + Y * Y;
|
W = Math.sqrt(W2);
|
T0 = Z * AD_C;
|
S0 = Math.sqrt(T0 * T0 + W2);
|
Sin_B0 = T0 / S0;
|
Cos_B0 = W / S0;
|
Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0;
|
T1 = Z + this.b * this.ep2 * Sin3_B0;
|
Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0;
|
S1 = Math.sqrt(T1 * T1 + Sum * Sum);
|
Sin_p1 = T1 / S1;
|
Cos_p1 = Sum / S1;
|
Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1);
|
if (Cos_p1 >= COS_67P5) {
|
Height = W / Cos_p1 - Rn;
|
}
|
else if (Cos_p1 <= -COS_67P5) {
|
Height = W / -Cos_p1 - Rn;
|
}
|
else {
|
Height = Z / Sin_p1 + Rn * (this.es - 1.0);
|
}
|
if (At_Pole === false) {
|
Latitude = Math.atan(Sin_p1 / Cos_p1);
|
}
|
|
p.x = Longitude;
|
p.y = Latitude;
|
p.z = Height;
|
return p;
|
}, // geocentric_to_geodetic_noniter()
|
|
/****************************************************************/
|
// pj_geocentic_to_wgs84( p )
|
// p = point to transform in geocentric coordinates (x,y,z)
|
geocentric_to_wgs84: function(p) {
|
|
if (this.datum_type === PJD_3PARAM) {
|
// if( x[io] === HUGE_VAL )
|
// continue;
|
p.x += this.datum_params[0];
|
p.y += this.datum_params[1];
|
p.z += this.datum_params[2];
|
|
}
|
else if (this.datum_type === PJD_7PARAM) {
|
var Dx_BF = this.datum_params[0];
|
var Dy_BF = this.datum_params[1];
|
var Dz_BF = this.datum_params[2];
|
var Rx_BF = this.datum_params[3];
|
var Ry_BF = this.datum_params[4];
|
var Rz_BF = this.datum_params[5];
|
var M_BF = this.datum_params[6];
|
// if( x[io] === HUGE_VAL )
|
// continue;
|
var x_out = M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF;
|
var y_out = M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF;
|
var z_out = M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF;
|
p.x = x_out;
|
p.y = y_out;
|
p.z = z_out;
|
}
|
}, // cs_geocentric_to_wgs84
|
|
/****************************************************************/
|
// pj_geocentic_from_wgs84()
|
// coordinate system definition,
|
// point to transform in geocentric coordinates (x,y,z)
|
geocentric_from_wgs84: function(p) {
|
|
if (this.datum_type === PJD_3PARAM) {
|
//if( x[io] === HUGE_VAL )
|
// continue;
|
p.x -= this.datum_params[0];
|
p.y -= this.datum_params[1];
|
p.z -= this.datum_params[2];
|
|
}
|
else if (this.datum_type === PJD_7PARAM) {
|
var Dx_BF = this.datum_params[0];
|
var Dy_BF = this.datum_params[1];
|
var Dz_BF = this.datum_params[2];
|
var Rx_BF = this.datum_params[3];
|
var Ry_BF = this.datum_params[4];
|
var Rz_BF = this.datum_params[5];
|
var M_BF = this.datum_params[6];
|
var x_tmp = (p.x - Dx_BF) / M_BF;
|
var y_tmp = (p.y - Dy_BF) / M_BF;
|
var z_tmp = (p.z - Dz_BF) / M_BF;
|
//if( x[io] === HUGE_VAL )
|
// continue;
|
|
p.x = x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp;
|
p.y = -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp;
|
p.z = Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp;
|
} //cs_geocentric_from_wgs84()
|
}
|
};
|
|
/** point object, nothing fancy, just allows values to be
|
passed back and forth by reference rather than by value.
|
Other point classes may be used as long as they have
|
x and y properties, which will get modified in the transform method.
|
*/
|
module.exports = datum;
|