datum.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. var HALF_PI = Math.PI/2;
  2. var PJD_3PARAM = 1;
  3. var PJD_7PARAM = 2;
  4. var PJD_GRIDSHIFT = 3;
  5. var PJD_WGS84 = 4; // WGS84 or equivalent
  6. var PJD_NODATUM = 5; // WGS84 or equivalent
  7. var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
  8. var AD_C = 1.0026000;
  9. var COS_67P5 = 0.38268343236508977;
  10. var datum = function(proj) {
  11. if (!(this instanceof datum)) {
  12. return new datum(proj);
  13. }
  14. this.datum_type = PJD_WGS84; //default setting
  15. if (!proj) {
  16. return;
  17. }
  18. if (proj.datumCode && proj.datumCode === 'none') {
  19. this.datum_type = PJD_NODATUM;
  20. }
  21. if (proj.datum_params) {
  22. for (var i = 0; i < proj.datum_params.length; i++) {
  23. proj.datum_params[i] = parseFloat(proj.datum_params[i]);
  24. }
  25. if (proj.datum_params[0] !== 0 || proj.datum_params[1] !== 0 || proj.datum_params[2] !== 0) {
  26. this.datum_type = PJD_3PARAM;
  27. }
  28. if (proj.datum_params.length > 3) {
  29. if (proj.datum_params[3] !== 0 || proj.datum_params[4] !== 0 || proj.datum_params[5] !== 0 || proj.datum_params[6] !== 0) {
  30. this.datum_type = PJD_7PARAM;
  31. proj.datum_params[3] *= SEC_TO_RAD;
  32. proj.datum_params[4] *= SEC_TO_RAD;
  33. proj.datum_params[5] *= SEC_TO_RAD;
  34. proj.datum_params[6] = (proj.datum_params[6] / 1000000.0) + 1.0;
  35. }
  36. }
  37. }
  38. // DGR 2011-03-21 : nadgrids support
  39. this.datum_type = proj.grids ? PJD_GRIDSHIFT : this.datum_type;
  40. this.a = proj.a; //datum object also uses these values
  41. this.b = proj.b;
  42. this.es = proj.es;
  43. this.ep2 = proj.ep2;
  44. this.datum_params = proj.datum_params;
  45. if (this.datum_type === PJD_GRIDSHIFT) {
  46. this.grids = proj.grids;
  47. }
  48. };
  49. datum.prototype = {
  50. /****************************************************************/
  51. // cs_compare_datums()
  52. // Returns TRUE if the two datums match, otherwise FALSE.
  53. compare_datums: function(dest) {
  54. if (this.datum_type !== dest.datum_type) {
  55. return false; // false, datums are not equal
  56. }
  57. else if (this.a !== dest.a || Math.abs(this.es - dest.es) > 0.000000000050) {
  58. // the tolerence for es is to ensure that GRS80 and WGS84
  59. // are considered identical
  60. return false;
  61. }
  62. else if (this.datum_type === PJD_3PARAM) {
  63. 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]);
  64. }
  65. else if (this.datum_type === PJD_7PARAM) {
  66. 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]);
  67. }
  68. else if (this.datum_type === PJD_GRIDSHIFT || dest.datum_type === PJD_GRIDSHIFT) {
  69. //alert("ERROR: Grid shift transformations are not implemented.");
  70. //return false
  71. //DGR 2012-07-29 lazy ...
  72. return this.nadgrids === dest.nadgrids;
  73. }
  74. else {
  75. return true; // datums are equal
  76. }
  77. }, // cs_compare_datums()
  78. /*
  79. * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
  80. * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
  81. * according to the current ellipsoid parameters.
  82. *
  83. * Latitude : Geodetic latitude in radians (input)
  84. * Longitude : Geodetic longitude in radians (input)
  85. * Height : Geodetic height, in meters (input)
  86. * X : Calculated Geocentric X coordinate, in meters (output)
  87. * Y : Calculated Geocentric Y coordinate, in meters (output)
  88. * Z : Calculated Geocentric Z coordinate, in meters (output)
  89. *
  90. */
  91. geodetic_to_geocentric: function(p) {
  92. var Longitude = p.x;
  93. var Latitude = p.y;
  94. var Height = p.z ? p.z : 0; //Z value not always supplied
  95. var X; // output
  96. var Y;
  97. var Z;
  98. var Error_Code = 0; // GEOCENT_NO_ERROR;
  99. var Rn; /* Earth radius at location */
  100. var Sin_Lat; /* Math.sin(Latitude) */
  101. var Sin2_Lat; /* Square of Math.sin(Latitude) */
  102. var Cos_Lat; /* Math.cos(Latitude) */
  103. /*
  104. ** Don't blow up if Latitude is just a little out of the value
  105. ** range as it may just be a rounding issue. Also removed longitude
  106. ** test, it should be wrapped by Math.cos() and Math.sin(). NFW for PROJ.4, Sep/2001.
  107. */
  108. if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) {
  109. Latitude = -HALF_PI;
  110. }
  111. else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) {
  112. Latitude = HALF_PI;
  113. }
  114. else if ((Latitude < -HALF_PI) || (Latitude > HALF_PI)) {
  115. /* Latitude out of range */
  116. //..reportError('geocent:lat out of range:' + Latitude);
  117. return null;
  118. }
  119. if (Longitude > Math.PI) {
  120. Longitude -= (2 * Math.PI);
  121. }
  122. Sin_Lat = Math.sin(Latitude);
  123. Cos_Lat = Math.cos(Latitude);
  124. Sin2_Lat = Sin_Lat * Sin_Lat;
  125. Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat));
  126. X = (Rn + Height) * Cos_Lat * Math.cos(Longitude);
  127. Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude);
  128. Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat;
  129. p.x = X;
  130. p.y = Y;
  131. p.z = Z;
  132. return Error_Code;
  133. }, // cs_geodetic_to_geocentric()
  134. geocentric_to_geodetic: function(p) {
  135. /* local defintions and variables */
  136. /* end-criterium of loop, accuracy of sin(Latitude) */
  137. var genau = 1e-12;
  138. var genau2 = (genau * genau);
  139. var maxiter = 30;
  140. var P; /* distance between semi-minor axis and location */
  141. var RR; /* distance between center and location */
  142. var CT; /* sin of geocentric latitude */
  143. var ST; /* cos of geocentric latitude */
  144. var RX;
  145. var RK;
  146. var RN; /* Earth radius at location */
  147. var CPHI0; /* cos of start or old geodetic latitude in iterations */
  148. var SPHI0; /* sin of start or old geodetic latitude in iterations */
  149. var CPHI; /* cos of searched geodetic latitude */
  150. var SPHI; /* sin of searched geodetic latitude */
  151. var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
  152. var At_Pole; /* indicates location is in polar region */
  153. var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */
  154. var X = p.x;
  155. var Y = p.y;
  156. var Z = p.z ? p.z : 0.0; //Z value not always supplied
  157. var Longitude;
  158. var Latitude;
  159. var Height;
  160. At_Pole = false;
  161. P = Math.sqrt(X * X + Y * Y);
  162. RR = Math.sqrt(X * X + Y * Y + Z * Z);
  163. /* special cases for latitude and longitude */
  164. if (P / this.a < genau) {
  165. /* special case, if P=0. (X=0., Y=0.) */
  166. At_Pole = true;
  167. Longitude = 0.0;
  168. /* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
  169. * of ellipsoid (=center of mass), Latitude becomes PI/2 */
  170. if (RR / this.a < genau) {
  171. Latitude = HALF_PI;
  172. Height = -this.b;
  173. return;
  174. }
  175. }
  176. else {
  177. /* ellipsoidal (geodetic) longitude
  178. * interval: -PI < Longitude <= +PI */
  179. Longitude = Math.atan2(Y, X);
  180. }
  181. /* --------------------------------------------------------------
  182. * Following iterative algorithm was developped by
  183. * "Institut for Erdmessung", University of Hannover, July 1988.
  184. * Internet: www.ife.uni-hannover.de
  185. * Iterative computation of CPHI,SPHI and Height.
  186. * Iteration of CPHI and SPHI to 10**-12 radian resp.
  187. * 2*10**-7 arcsec.
  188. * --------------------------------------------------------------
  189. */
  190. CT = Z / RR;
  191. ST = P / RR;
  192. RX = 1.0 / Math.sqrt(1.0 - this.es * (2.0 - this.es) * ST * ST);
  193. CPHI0 = ST * (1.0 - this.es) * RX;
  194. SPHI0 = CT * RX;
  195. iter = 0;
  196. /* loop to find sin(Latitude) resp. Latitude
  197. * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
  198. do {
  199. iter++;
  200. RN = this.a / Math.sqrt(1.0 - this.es * SPHI0 * SPHI0);
  201. /* ellipsoidal (geodetic) height */
  202. Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - this.es * SPHI0 * SPHI0);
  203. RK = this.es * RN / (RN + Height);
  204. RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST);
  205. CPHI = ST * (1.0 - RK) * RX;
  206. SPHI = CT * RX;
  207. SDPHI = SPHI * CPHI0 - CPHI * SPHI0;
  208. CPHI0 = CPHI;
  209. SPHI0 = SPHI;
  210. }
  211. while (SDPHI * SDPHI > genau2 && iter < maxiter);
  212. /* ellipsoidal (geodetic) latitude */
  213. Latitude = Math.atan(SPHI / Math.abs(CPHI));
  214. p.x = Longitude;
  215. p.y = Latitude;
  216. p.z = Height;
  217. return p;
  218. }, // cs_geocentric_to_geodetic()
  219. /** Convert_Geocentric_To_Geodetic
  220. * The method used here is derived from 'An Improved Algorithm for
  221. * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996
  222. */
  223. geocentric_to_geodetic_noniter: function(p) {
  224. var X = p.x;
  225. var Y = p.y;
  226. var Z = p.z ? p.z : 0; //Z value not always supplied
  227. var Longitude;
  228. var Latitude;
  229. var Height;
  230. var W; /* distance from Z axis */
  231. var W2; /* square of distance from Z axis */
  232. var T0; /* initial estimate of vertical component */
  233. var T1; /* corrected estimate of vertical component */
  234. var S0; /* initial estimate of horizontal component */
  235. var S1; /* corrected estimate of horizontal component */
  236. var Sin_B0; /* Math.sin(B0), B0 is estimate of Bowring aux variable */
  237. var Sin3_B0; /* cube of Math.sin(B0) */
  238. var Cos_B0; /* Math.cos(B0) */
  239. var Sin_p1; /* Math.sin(phi1), phi1 is estimated latitude */
  240. var Cos_p1; /* Math.cos(phi1) */
  241. var Rn; /* Earth radius at location */
  242. var Sum; /* numerator of Math.cos(phi1) */
  243. var At_Pole; /* indicates location is in polar region */
  244. X = parseFloat(X); // cast from string to float
  245. Y = parseFloat(Y);
  246. Z = parseFloat(Z);
  247. At_Pole = false;
  248. if (X !== 0.0) {
  249. Longitude = Math.atan2(Y, X);
  250. }
  251. else {
  252. if (Y > 0) {
  253. Longitude = HALF_PI;
  254. }
  255. else if (Y < 0) {
  256. Longitude = -HALF_PI;
  257. }
  258. else {
  259. At_Pole = true;
  260. Longitude = 0.0;
  261. if (Z > 0.0) { /* north pole */
  262. Latitude = HALF_PI;
  263. }
  264. else if (Z < 0.0) { /* south pole */
  265. Latitude = -HALF_PI;
  266. }
  267. else { /* center of earth */
  268. Latitude = HALF_PI;
  269. Height = -this.b;
  270. return;
  271. }
  272. }
  273. }
  274. W2 = X * X + Y * Y;
  275. W = Math.sqrt(W2);
  276. T0 = Z * AD_C;
  277. S0 = Math.sqrt(T0 * T0 + W2);
  278. Sin_B0 = T0 / S0;
  279. Cos_B0 = W / S0;
  280. Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0;
  281. T1 = Z + this.b * this.ep2 * Sin3_B0;
  282. Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0;
  283. S1 = Math.sqrt(T1 * T1 + Sum * Sum);
  284. Sin_p1 = T1 / S1;
  285. Cos_p1 = Sum / S1;
  286. Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1);
  287. if (Cos_p1 >= COS_67P5) {
  288. Height = W / Cos_p1 - Rn;
  289. }
  290. else if (Cos_p1 <= -COS_67P5) {
  291. Height = W / -Cos_p1 - Rn;
  292. }
  293. else {
  294. Height = Z / Sin_p1 + Rn * (this.es - 1.0);
  295. }
  296. if (At_Pole === false) {
  297. Latitude = Math.atan(Sin_p1 / Cos_p1);
  298. }
  299. p.x = Longitude;
  300. p.y = Latitude;
  301. p.z = Height;
  302. return p;
  303. }, // geocentric_to_geodetic_noniter()
  304. /****************************************************************/
  305. // pj_geocentic_to_wgs84( p )
  306. // p = point to transform in geocentric coordinates (x,y,z)
  307. geocentric_to_wgs84: function(p) {
  308. if (this.datum_type === PJD_3PARAM) {
  309. // if( x[io] === HUGE_VAL )
  310. // continue;
  311. p.x += this.datum_params[0];
  312. p.y += this.datum_params[1];
  313. p.z += this.datum_params[2];
  314. }
  315. else if (this.datum_type === PJD_7PARAM) {
  316. var Dx_BF = this.datum_params[0];
  317. var Dy_BF = this.datum_params[1];
  318. var Dz_BF = this.datum_params[2];
  319. var Rx_BF = this.datum_params[3];
  320. var Ry_BF = this.datum_params[4];
  321. var Rz_BF = this.datum_params[5];
  322. var M_BF = this.datum_params[6];
  323. // if( x[io] === HUGE_VAL )
  324. // continue;
  325. var x_out = M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF;
  326. var y_out = M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF;
  327. var z_out = M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF;
  328. p.x = x_out;
  329. p.y = y_out;
  330. p.z = z_out;
  331. }
  332. }, // cs_geocentric_to_wgs84
  333. /****************************************************************/
  334. // pj_geocentic_from_wgs84()
  335. // coordinate system definition,
  336. // point to transform in geocentric coordinates (x,y,z)
  337. geocentric_from_wgs84: function(p) {
  338. if (this.datum_type === PJD_3PARAM) {
  339. //if( x[io] === HUGE_VAL )
  340. // continue;
  341. p.x -= this.datum_params[0];
  342. p.y -= this.datum_params[1];
  343. p.z -= this.datum_params[2];
  344. }
  345. else if (this.datum_type === PJD_7PARAM) {
  346. var Dx_BF = this.datum_params[0];
  347. var Dy_BF = this.datum_params[1];
  348. var Dz_BF = this.datum_params[2];
  349. var Rx_BF = this.datum_params[3];
  350. var Ry_BF = this.datum_params[4];
  351. var Rz_BF = this.datum_params[5];
  352. var M_BF = this.datum_params[6];
  353. var x_tmp = (p.x - Dx_BF) / M_BF;
  354. var y_tmp = (p.y - Dy_BF) / M_BF;
  355. var z_tmp = (p.z - Dz_BF) / M_BF;
  356. //if( x[io] === HUGE_VAL )
  357. // continue;
  358. p.x = x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp;
  359. p.y = -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp;
  360. p.z = Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp;
  361. } //cs_geocentric_from_wgs84()
  362. }
  363. };
  364. /** point object, nothing fancy, just allows values to be
  365. passed back and forth by reference rather than by value.
  366. Other point classes may be used as long as they have
  367. x and y properties, which will get modified in the transform method.
  368. */
  369. module.exports = datum;