mgrs.js 20 KB

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