mgrs.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (factory((global.mgrs = global.mgrs || {})));
  5. }(this, (function (exports) { 'use strict';
  6. /**
  7. * UTM zones are grouped, and assigned to one of a group of 6
  8. * sets.
  9. *
  10. * {int} @private
  11. */
  12. var NUM_100K_SETS = 6;
  13. /**
  14. * The column letters (for easting) of the lower left value, per
  15. * set.
  16. *
  17. * {string} @private
  18. */
  19. var SET_ORIGIN_COLUMN_LETTERS = 'AJSAJS';
  20. /**
  21. * The row letters (for northing) of the lower left value, per
  22. * set.
  23. *
  24. * {string} @private
  25. */
  26. var SET_ORIGIN_ROW_LETTERS = 'AFAFAF';
  27. var A = 65; // A
  28. var I = 73; // I
  29. var O = 79; // O
  30. var V = 86; // V
  31. var Z = 90; // Z
  32. var mgrs = {
  33. forward: forward,
  34. inverse: inverse,
  35. toPoint: toPoint
  36. };
  37. /**
  38. * Conversion of lat/lon to MGRS.
  39. *
  40. * @param {object} ll Object literal with lat and lon properties on a
  41. * WGS84 ellipsoid.
  42. * @param {int} accuracy Accuracy in digits (5 for 1 m, 4 for 10 m, 3 for
  43. * 100 m, 2 for 1000 m or 1 for 10000 m). Optional, default is 5.
  44. * @return {string} the MGRS string for the given location and accuracy.
  45. */
  46. function forward(ll, accuracy) {
  47. accuracy = accuracy || 5; // default accuracy 1m
  48. return encode(LLtoUTM({
  49. lat: ll[1],
  50. lon: ll[0]
  51. }), accuracy);
  52. }
  53. /**
  54. * Conversion of MGRS to lat/lon.
  55. *
  56. * @param {string} mgrs MGRS string.
  57. * @return {array} An array with left (longitude), bottom (latitude), right
  58. * (longitude) and top (latitude) values in WGS84, representing the
  59. * bounding box for the provided MGRS reference.
  60. */
  61. function inverse(mgrs) {
  62. var bbox = UTMtoLL(decode(mgrs.toUpperCase()));
  63. if (bbox.lat && bbox.lon) {
  64. return [bbox.lon, bbox.lat, bbox.lon, bbox.lat];
  65. }
  66. return [bbox.left, bbox.bottom, bbox.right, bbox.top];
  67. }
  68. function toPoint(mgrs) {
  69. var bbox = UTMtoLL(decode(mgrs.toUpperCase()));
  70. if (bbox.lat && bbox.lon) {
  71. return [bbox.lon, bbox.lat];
  72. }
  73. return [(bbox.left + bbox.right) / 2, (bbox.top + bbox.bottom) / 2];
  74. }
  75. /**
  76. * Conversion from degrees to radians.
  77. *
  78. * @private
  79. * @param {number} deg the angle in degrees.
  80. * @return {number} the angle in radians.
  81. */
  82. function degToRad(deg) {
  83. return (deg * (Math.PI / 180.0));
  84. }
  85. /**
  86. * Conversion from radians to degrees.
  87. *
  88. * @private
  89. * @param {number} rad the angle in radians.
  90. * @return {number} the angle in degrees.
  91. */
  92. function radToDeg(rad) {
  93. return (180.0 * (rad / Math.PI));
  94. }
  95. /**
  96. * Converts a set of Longitude and Latitude co-ordinates to UTM
  97. * using the WGS84 ellipsoid.
  98. *
  99. * @private
  100. * @param {object} ll Object literal with lat and lon properties
  101. * representing the WGS84 coordinate to be converted.
  102. * @return {object} Object literal containing the UTM value with easting,
  103. * northing, zoneNumber and zoneLetter properties, and an optional
  104. * accuracy property in digits. Returns null if the conversion failed.
  105. */
  106. function LLtoUTM(ll) {
  107. var Lat = ll.lat;
  108. var Long = ll.lon;
  109. var a = 6378137.0; //ellip.radius;
  110. var eccSquared = 0.00669438; //ellip.eccsq;
  111. var k0 = 0.9996;
  112. var LongOrigin;
  113. var eccPrimeSquared;
  114. var N, T, C, A, M;
  115. var LatRad = degToRad(Lat);
  116. var LongRad = degToRad(Long);
  117. var LongOriginRad;
  118. var ZoneNumber;
  119. // (int)
  120. ZoneNumber = Math.floor((Long + 180) / 6) + 1;
  121. //Make sure the longitude 180.00 is in Zone 60
  122. if (Long === 180) {
  123. ZoneNumber = 60;
  124. }
  125. // Special zone for Norway
  126. if (Lat >= 56.0 && Lat < 64.0 && Long >= 3.0 && Long < 12.0) {
  127. ZoneNumber = 32;
  128. }
  129. // Special zones for Svalbard
  130. if (Lat >= 72.0 && Lat < 84.0) {
  131. if (Long >= 0.0 && Long < 9.0) {
  132. ZoneNumber = 31;
  133. }
  134. else if (Long >= 9.0 && Long < 21.0) {
  135. ZoneNumber = 33;
  136. }
  137. else if (Long >= 21.0 && Long < 33.0) {
  138. ZoneNumber = 35;
  139. }
  140. else if (Long >= 33.0 && Long < 42.0) {
  141. ZoneNumber = 37;
  142. }
  143. }
  144. LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3; //+3 puts origin
  145. // in middle of
  146. // zone
  147. LongOriginRad = degToRad(LongOrigin);
  148. eccPrimeSquared = (eccSquared) / (1 - eccSquared);
  149. N = a / Math.sqrt(1 - eccSquared * Math.sin(LatRad) * Math.sin(LatRad));
  150. T = Math.tan(LatRad) * Math.tan(LatRad);
  151. C = eccPrimeSquared * Math.cos(LatRad) * Math.cos(LatRad);
  152. A = Math.cos(LatRad) * (LongRad - LongOriginRad);
  153. M = a * ((1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad - (3 * eccSquared / 8 + 3 * eccSquared * eccSquared / 32 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(2 * LatRad) + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(4 * LatRad) - (35 * eccSquared * eccSquared * eccSquared / 3072) * Math.sin(6 * LatRad));
  154. var UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6.0 + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120.0) + 500000.0);
  155. var UTMNorthing = (k0 * (M + N * Math.tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24.0 + (61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720.0)));
  156. if (Lat < 0.0) {
  157. UTMNorthing += 10000000.0; //10000000 meter offset for
  158. // southern hemisphere
  159. }
  160. return {
  161. northing: Math.round(UTMNorthing),
  162. easting: Math.round(UTMEasting),
  163. zoneNumber: ZoneNumber,
  164. zoneLetter: getLetterDesignator(Lat)
  165. };
  166. }
  167. /**
  168. * Converts UTM coords to lat/long, using the WGS84 ellipsoid. This is a convenience
  169. * class where the Zone can be specified as a single string eg."60N" which
  170. * is then broken down into the ZoneNumber and ZoneLetter.
  171. *
  172. * @private
  173. * @param {object} utm An object literal with northing, easting, zoneNumber
  174. * and zoneLetter properties. If an optional accuracy property is
  175. * provided (in meters), a bounding box will be returned instead of
  176. * latitude and longitude.
  177. * @return {object} An object literal containing either lat and lon values
  178. * (if no accuracy was provided), or top, right, bottom and left values
  179. * for the bounding box calculated according to the provided accuracy.
  180. * Returns null if the conversion failed.
  181. */
  182. function UTMtoLL(utm) {
  183. var UTMNorthing = utm.northing;
  184. var UTMEasting = utm.easting;
  185. var zoneLetter = utm.zoneLetter;
  186. var zoneNumber = utm.zoneNumber;
  187. // check the ZoneNummber is valid
  188. if (zoneNumber < 0 || zoneNumber > 60) {
  189. return null;
  190. }
  191. var k0 = 0.9996;
  192. var a = 6378137.0; //ellip.radius;
  193. var eccSquared = 0.00669438; //ellip.eccsq;
  194. var eccPrimeSquared;
  195. var e1 = (1 - Math.sqrt(1 - eccSquared)) / (1 + Math.sqrt(1 - eccSquared));
  196. var N1, T1, C1, R1, D, M;
  197. var LongOrigin;
  198. var mu, phi1Rad;
  199. // remove 500,000 meter offset for longitude
  200. var x = UTMEasting - 500000.0;
  201. var y = UTMNorthing;
  202. // We must know somehow if we are in the Northern or Southern
  203. // hemisphere, this is the only time we use the letter So even
  204. // if the Zone letter isn't exactly correct it should indicate
  205. // the hemisphere correctly
  206. if (zoneLetter < 'N') {
  207. y -= 10000000.0; // remove 10,000,000 meter offset used
  208. // for southern hemisphere
  209. }
  210. // There are 60 zones with zone 1 being at West -180 to -174
  211. LongOrigin = (zoneNumber - 1) * 6 - 180 + 3; // +3 puts origin
  212. // in middle of
  213. // zone
  214. eccPrimeSquared = (eccSquared) / (1 - eccSquared);
  215. M = y / k0;
  216. mu = M / (a * (1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256));
  217. phi1Rad = mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * Math.sin(2 * mu) + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * Math.sin(4 * mu) + (151 * e1 * e1 * e1 / 96) * Math.sin(6 * mu);
  218. // double phi1 = ProjMath.radToDeg(phi1Rad);
  219. N1 = a / Math.sqrt(1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad));
  220. T1 = Math.tan(phi1Rad) * Math.tan(phi1Rad);
  221. C1 = eccPrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
  222. R1 = a * (1 - eccSquared) / Math.pow(1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5);
  223. D = x / (N1 * k0);
  224. var lat = phi1Rad - (N1 * Math.tan(phi1Rad) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24 + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720);
  225. lat = radToDeg(lat);
  226. var lon = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) * D * D * D * D * D / 120) / Math.cos(phi1Rad);
  227. lon = LongOrigin + radToDeg(lon);
  228. var result;
  229. if (utm.accuracy) {
  230. var topRight = UTMtoLL({
  231. northing: utm.northing + utm.accuracy,
  232. easting: utm.easting + utm.accuracy,
  233. zoneLetter: utm.zoneLetter,
  234. zoneNumber: utm.zoneNumber
  235. });
  236. result = {
  237. top: topRight.lat,
  238. right: topRight.lon,
  239. bottom: lat,
  240. left: lon
  241. };
  242. }
  243. else {
  244. result = {
  245. lat: lat,
  246. lon: lon
  247. };
  248. }
  249. return result;
  250. }
  251. /**
  252. * Calculates the MGRS letter designator for the given latitude.
  253. *
  254. * @private
  255. * @param {number} lat The latitude in WGS84 to get the letter designator
  256. * for.
  257. * @return {char} The letter designator.
  258. */
  259. function getLetterDesignator(lat) {
  260. //This is here as an error flag to show that the Latitude is
  261. //outside MGRS limits
  262. var LetterDesignator = 'Z';
  263. if ((84 >= lat) && (lat >= 72)) {
  264. LetterDesignator = 'X';
  265. }
  266. else if ((72 > lat) && (lat >= 64)) {
  267. LetterDesignator = 'W';
  268. }
  269. else if ((64 > lat) && (lat >= 56)) {
  270. LetterDesignator = 'V';
  271. }
  272. else if ((56 > lat) && (lat >= 48)) {
  273. LetterDesignator = 'U';
  274. }
  275. else if ((48 > lat) && (lat >= 40)) {
  276. LetterDesignator = 'T';
  277. }
  278. else if ((40 > lat) && (lat >= 32)) {
  279. LetterDesignator = 'S';
  280. }
  281. else if ((32 > lat) && (lat >= 24)) {
  282. LetterDesignator = 'R';
  283. }
  284. else if ((24 > lat) && (lat >= 16)) {
  285. LetterDesignator = 'Q';
  286. }
  287. else if ((16 > lat) && (lat >= 8)) {
  288. LetterDesignator = 'P';
  289. }
  290. else if ((8 > lat) && (lat >= 0)) {
  291. LetterDesignator = 'N';
  292. }
  293. else if ((0 > lat) && (lat >= -8)) {
  294. LetterDesignator = 'M';
  295. }
  296. else if ((-8 > lat) && (lat >= -16)) {
  297. LetterDesignator = 'L';
  298. }
  299. else if ((-16 > lat) && (lat >= -24)) {
  300. LetterDesignator = 'K';
  301. }
  302. else if ((-24 > lat) && (lat >= -32)) {
  303. LetterDesignator = 'J';
  304. }
  305. else if ((-32 > lat) && (lat >= -40)) {
  306. LetterDesignator = 'H';
  307. }
  308. else if ((-40 > lat) && (lat >= -48)) {
  309. LetterDesignator = 'G';
  310. }
  311. else if ((-48 > lat) && (lat >= -56)) {
  312. LetterDesignator = 'F';
  313. }
  314. else if ((-56 > lat) && (lat >= -64)) {
  315. LetterDesignator = 'E';
  316. }
  317. else if ((-64 > lat) && (lat >= -72)) {
  318. LetterDesignator = 'D';
  319. }
  320. else if ((-72 > lat) && (lat >= -80)) {
  321. LetterDesignator = 'C';
  322. }
  323. return LetterDesignator;
  324. }
  325. /**
  326. * Encodes a UTM location as MGRS string.
  327. *
  328. * @private
  329. * @param {object} utm An object literal with easting, northing,
  330. * zoneLetter, zoneNumber
  331. * @param {number} accuracy Accuracy in digits (1-5).
  332. * @return {string} MGRS string for the given UTM location.
  333. */
  334. function encode(utm, accuracy) {
  335. // prepend with leading zeroes
  336. var seasting = "00000" + utm.easting,
  337. snorthing = "00000" + utm.northing;
  338. return utm.zoneNumber + utm.zoneLetter + get100kID(utm.easting, utm.northing, utm.zoneNumber) + seasting.substr(seasting.length - 5, accuracy) + snorthing.substr(snorthing.length - 5, accuracy);
  339. }
  340. /**
  341. * Get the two letter 100k designator for a given UTM easting,
  342. * northing and zone number value.
  343. *
  344. * @private
  345. * @param {number} easting
  346. * @param {number} northing
  347. * @param {number} zoneNumber
  348. * @return the two letter 100k designator for the given UTM location.
  349. */
  350. function get100kID(easting, northing, zoneNumber) {
  351. var setParm = get100kSetForZone(zoneNumber);
  352. var setColumn = Math.floor(easting / 100000);
  353. var setRow = Math.floor(northing / 100000) % 20;
  354. return getLetter100kID(setColumn, setRow, setParm);
  355. }
  356. /**
  357. * Given a UTM zone number, figure out the MGRS 100K set it is in.
  358. *
  359. * @private
  360. * @param {number} i An UTM zone number.
  361. * @return {number} the 100k set the UTM zone is in.
  362. */
  363. function get100kSetForZone(i) {
  364. var setParm = i % NUM_100K_SETS;
  365. if (setParm === 0) {
  366. setParm = NUM_100K_SETS;
  367. }
  368. return setParm;
  369. }
  370. /**
  371. * Get the two-letter MGRS 100k designator given information
  372. * translated from the UTM northing, easting and zone number.
  373. *
  374. * @private
  375. * @param {number} column the column index as it relates to the MGRS
  376. * 100k set spreadsheet, created from the UTM easting.
  377. * Values are 1-8.
  378. * @param {number} row the row index as it relates to the MGRS 100k set
  379. * spreadsheet, created from the UTM northing value. Values
  380. * are from 0-19.
  381. * @param {number} parm the set block, as it relates to the MGRS 100k set
  382. * spreadsheet, created from the UTM zone. Values are from
  383. * 1-60.
  384. * @return two letter MGRS 100k code.
  385. */
  386. function getLetter100kID(column, row, parm) {
  387. // colOrigin and rowOrigin are the letters at the origin of the set
  388. var index = parm - 1;
  389. var colOrigin = SET_ORIGIN_COLUMN_LETTERS.charCodeAt(index);
  390. var rowOrigin = SET_ORIGIN_ROW_LETTERS.charCodeAt(index);
  391. // colInt and rowInt are the letters to build to return
  392. var colInt = colOrigin + column - 1;
  393. var rowInt = rowOrigin + row;
  394. var rollover = false;
  395. if (colInt > Z) {
  396. colInt = colInt - Z + A - 1;
  397. rollover = true;
  398. }
  399. if (colInt === I || (colOrigin < I && colInt > I) || ((colInt > I || colOrigin < I) && rollover)) {
  400. colInt++;
  401. }
  402. if (colInt === O || (colOrigin < O && colInt > O) || ((colInt > O || colOrigin < O) && rollover)) {
  403. colInt++;
  404. if (colInt === I) {
  405. colInt++;
  406. }
  407. }
  408. if (colInt > Z) {
  409. colInt = colInt - Z + A - 1;
  410. }
  411. if (rowInt > V) {
  412. rowInt = rowInt - V + A - 1;
  413. rollover = true;
  414. }
  415. else {
  416. rollover = false;
  417. }
  418. if (((rowInt === I) || ((rowOrigin < I) && (rowInt > I))) || (((rowInt > I) || (rowOrigin < I)) && rollover)) {
  419. rowInt++;
  420. }
  421. if (((rowInt === O) || ((rowOrigin < O) && (rowInt > O))) || (((rowInt > O) || (rowOrigin < O)) && rollover)) {
  422. rowInt++;
  423. if (rowInt === I) {
  424. rowInt++;
  425. }
  426. }
  427. if (rowInt > V) {
  428. rowInt = rowInt - V + A - 1;
  429. }
  430. var twoLetter = String.fromCharCode(colInt) + String.fromCharCode(rowInt);
  431. return twoLetter;
  432. }
  433. /**
  434. * Decode the UTM parameters from a MGRS string.
  435. *
  436. * @private
  437. * @param {string} mgrsString an UPPERCASE coordinate string is expected.
  438. * @return {object} An object literal with easting, northing, zoneLetter,
  439. * zoneNumber and accuracy (in meters) properties.
  440. */
  441. function decode(mgrsString) {
  442. if (mgrsString && mgrsString.length === 0) {
  443. throw ("MGRSPoint coverting from nothing");
  444. }
  445. var length = mgrsString.length;
  446. var hunK = null;
  447. var sb = "";
  448. var testChar;
  449. var i = 0;
  450. // get Zone number
  451. while (!(/[A-Z]/).test(testChar = mgrsString.charAt(i))) {
  452. if (i >= 2) {
  453. throw ("MGRSPoint bad conversion from: " + mgrsString);
  454. }
  455. sb += testChar;
  456. i++;
  457. }
  458. var zoneNumber = parseInt(sb, 10);
  459. if (i === 0 || i + 3 > length) {
  460. // A good MGRS string has to be 4-5 digits long,
  461. // ##AAA/#AAA at least.
  462. throw ("MGRSPoint bad conversion from: " + mgrsString);
  463. }
  464. var zoneLetter = mgrsString.charAt(i++);
  465. // Should we check the zone letter here? Why not.
  466. if (zoneLetter <= 'A' || zoneLetter === 'B' || zoneLetter === 'Y' || zoneLetter >= 'Z' || zoneLetter === 'I' || zoneLetter === 'O') {
  467. throw ("MGRSPoint zone letter " + zoneLetter + " not handled: " + mgrsString);
  468. }
  469. hunK = mgrsString.substring(i, i += 2);
  470. var set = get100kSetForZone(zoneNumber);
  471. var east100k = getEastingFromChar(hunK.charAt(0), set);
  472. var north100k = getNorthingFromChar(hunK.charAt(1), set);
  473. // We have a bug where the northing may be 2000000 too low.
  474. // How
  475. // do we know when to roll over?
  476. while (north100k < getMinNorthing(zoneLetter)) {
  477. north100k += 2000000;
  478. }
  479. // calculate the char index for easting/northing separator
  480. var remainder = length - i;
  481. if (remainder % 2 !== 0) {
  482. throw ("MGRSPoint has to have an even number \nof digits after the zone letter and two 100km letters - front \nhalf for easting meters, second half for \nnorthing meters" + mgrsString);
  483. }
  484. var sep = remainder / 2;
  485. var sepEasting = 0.0;
  486. var sepNorthing = 0.0;
  487. var accuracyBonus, sepEastingString, sepNorthingString, easting, northing;
  488. if (sep > 0) {
  489. accuracyBonus = 100000.0 / Math.pow(10, sep);
  490. sepEastingString = mgrsString.substring(i, i + sep);
  491. sepEasting = parseFloat(sepEastingString) * accuracyBonus;
  492. sepNorthingString = mgrsString.substring(i + sep);
  493. sepNorthing = parseFloat(sepNorthingString) * accuracyBonus;
  494. }
  495. easting = sepEasting + east100k;
  496. northing = sepNorthing + north100k;
  497. return {
  498. easting: easting,
  499. northing: northing,
  500. zoneLetter: zoneLetter,
  501. zoneNumber: zoneNumber,
  502. accuracy: accuracyBonus
  503. };
  504. }
  505. /**
  506. * Given the first letter from a two-letter MGRS 100k zone, and given the
  507. * MGRS table set for the zone number, figure out the easting value that
  508. * should be added to the other, secondary easting value.
  509. *
  510. * @private
  511. * @param {char} e The first letter from a two-letter MGRS 100´k zone.
  512. * @param {number} set The MGRS table set for the zone number.
  513. * @return {number} The easting value for the given letter and set.
  514. */
  515. function getEastingFromChar(e, set) {
  516. // colOrigin is the letter at the origin of the set for the
  517. // column
  518. var curCol = SET_ORIGIN_COLUMN_LETTERS.charCodeAt(set - 1);
  519. var eastingValue = 100000.0;
  520. var rewindMarker = false;
  521. while (curCol !== e.charCodeAt(0)) {
  522. curCol++;
  523. if (curCol === I) {
  524. curCol++;
  525. }
  526. if (curCol === O) {
  527. curCol++;
  528. }
  529. if (curCol > Z) {
  530. if (rewindMarker) {
  531. throw ("Bad character: " + e);
  532. }
  533. curCol = A;
  534. rewindMarker = true;
  535. }
  536. eastingValue += 100000.0;
  537. }
  538. return eastingValue;
  539. }
  540. /**
  541. * Given the second letter from a two-letter MGRS 100k zone, and given the
  542. * MGRS table set for the zone number, figure out the northing value that
  543. * should be added to the other, secondary northing value. You have to
  544. * remember that Northings are determined from the equator, and the vertical
  545. * cycle of letters mean a 2000000 additional northing meters. This happens
  546. * approx. every 18 degrees of latitude. This method does *NOT* count any
  547. * additional northings. You have to figure out how many 2000000 meters need
  548. * to be added for the zone letter of the MGRS coordinate.
  549. *
  550. * @private
  551. * @param {char} n Second letter of the MGRS 100k zone
  552. * @param {number} set The MGRS table set number, which is dependent on the
  553. * UTM zone number.
  554. * @return {number} The northing value for the given letter and set.
  555. */
  556. function getNorthingFromChar(n, set) {
  557. if (n > 'V') {
  558. throw ("MGRSPoint given invalid Northing " + n);
  559. }
  560. // rowOrigin is the letter at the origin of the set for the
  561. // column
  562. var curRow = SET_ORIGIN_ROW_LETTERS.charCodeAt(set - 1);
  563. var northingValue = 0.0;
  564. var rewindMarker = false;
  565. while (curRow !== n.charCodeAt(0)) {
  566. curRow++;
  567. if (curRow === I) {
  568. curRow++;
  569. }
  570. if (curRow === O) {
  571. curRow++;
  572. }
  573. // fixing a bug making whole application hang in this loop
  574. // when 'n' is a wrong character
  575. if (curRow > V) {
  576. if (rewindMarker) { // making sure that this loop ends
  577. throw ("Bad character: " + n);
  578. }
  579. curRow = A;
  580. rewindMarker = true;
  581. }
  582. northingValue += 100000.0;
  583. }
  584. return northingValue;
  585. }
  586. /**
  587. * The function getMinNorthing returns the minimum northing value of a MGRS
  588. * zone.
  589. *
  590. * Ported from Geotrans' c Lattitude_Band_Value structure table.
  591. *
  592. * @private
  593. * @param {char} zoneLetter The MGRS zone to get the min northing for.
  594. * @return {number}
  595. */
  596. function getMinNorthing(zoneLetter) {
  597. var northing;
  598. switch (zoneLetter) {
  599. case 'C':
  600. northing = 1100000.0;
  601. break;
  602. case 'D':
  603. northing = 2000000.0;
  604. break;
  605. case 'E':
  606. northing = 2800000.0;
  607. break;
  608. case 'F':
  609. northing = 3700000.0;
  610. break;
  611. case 'G':
  612. northing = 4600000.0;
  613. break;
  614. case 'H':
  615. northing = 5500000.0;
  616. break;
  617. case 'J':
  618. northing = 6400000.0;
  619. break;
  620. case 'K':
  621. northing = 7300000.0;
  622. break;
  623. case 'L':
  624. northing = 8200000.0;
  625. break;
  626. case 'M':
  627. northing = 9100000.0;
  628. break;
  629. case 'N':
  630. northing = 0.0;
  631. break;
  632. case 'P':
  633. northing = 800000.0;
  634. break;
  635. case 'Q':
  636. northing = 1700000.0;
  637. break;
  638. case 'R':
  639. northing = 2600000.0;
  640. break;
  641. case 'S':
  642. northing = 3500000.0;
  643. break;
  644. case 'T':
  645. northing = 4400000.0;
  646. break;
  647. case 'U':
  648. northing = 5300000.0;
  649. break;
  650. case 'V':
  651. northing = 6200000.0;
  652. break;
  653. case 'W':
  654. northing = 7000000.0;
  655. break;
  656. case 'X':
  657. northing = 7900000.0;
  658. break;
  659. default:
  660. northing = -1.0;
  661. }
  662. if (northing >= 0.0) {
  663. return northing;
  664. }
  665. else {
  666. throw ("Invalid zone letter: " + zoneLetter);
  667. }
  668. }
  669. exports['default'] = mgrs;
  670. exports.forward = forward;
  671. exports.inverse = inverse;
  672. exports.toPoint = toPoint;
  673. Object.defineProperty(exports, '__esModule', { value: true });
  674. })));