UTMUPS.hpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /**
  2. * \file UTMUPS.hpp
  3. * \brief Header for GeographicLib::UTMUPS class
  4. *
  5. * Copyright (c) Charles Karney (2008-2020) <charles@karney.com> and licensed
  6. * under the MIT/X11 License. For more information, see
  7. * https://geographiclib.sourceforge.io/
  8. **********************************************************************/
  9. #if !defined(GEOGRAPHICLIB_UTMUPS_HPP)
  10. #define GEOGRAPHICLIB_UTMUPS_HPP 1
  11. #include <GeographicLib/Constants.hpp>
  12. namespace GeographicLib {
  13. /**
  14. * \brief Convert between geographic coordinates and UTM/UPS
  15. *
  16. * UTM and UPS are defined
  17. * - J. W. Hager, J. F. Behensky, and B. W. Drew,
  18. * <a href="https://web.archive.org/web/20161214054445/http://earth-info.nga.mil/GandG/publications/tm8358.2/TM8358_2.pdf">
  19. * The Universal Grids: Universal Transverse Mercator (UTM) and Universal
  20. * Polar Stereographic (UPS)</a>, Defense Mapping Agency, Technical Manual
  21. * TM8358.2 (1989).
  22. * .
  23. * Section 2-3 defines UTM and section 3-2.4 defines UPS. This document also
  24. * includes approximate algorithms for the computation of the underlying
  25. * transverse Mercator and polar stereographic projections. Here we
  26. * substitute much more accurate algorithms given by
  27. * GeographicLib:TransverseMercator and GeographicLib:PolarStereographic.
  28. * These are the algorithms recommended by the NGA document
  29. * - <a href="https://earth-info.nga.mil/php/download.php?file=coord-utmups">
  30. * The Universal Grids and the Transverse Mercator and Polar Stereographic
  31. * Map Projections</a>, NGA.SIG.0012 (2014).
  32. *
  33. * In this implementation, the conversions are closed, i.e., output from
  34. * Forward is legal input for Reverse and vice versa. The error is about 5nm
  35. * in each direction. However, the conversion from legal UTM/UPS coordinates
  36. * to geographic coordinates and back might throw an error if the initial
  37. * point is within 5nm of the edge of the allowed range for the UTM/UPS
  38. * coordinates.
  39. *
  40. * The simplest way to guarantee the closed property is to define allowed
  41. * ranges for the eastings and northings for UTM and UPS coordinates. The
  42. * UTM boundaries are the same for all zones. (The only place the
  43. * exceptional nature of the zone boundaries is evident is when converting to
  44. * UTM/UPS coordinates requesting the standard zone.) The MGRS lettering
  45. * scheme imposes natural limits on UTM/UPS coordinates which may be
  46. * converted into MGRS coordinates. For the conversion to/from geographic
  47. * coordinates these ranges have been extended by 100km in order to provide a
  48. * generous overlap between UTM and UPS and between UTM zones.
  49. *
  50. * The <a href="http://www.nga.mil">NGA</a> software package
  51. * <a href="https://earth-info.nga.mil/index.php?dir=wgs84&action=wgs84#tab_geotrans">geotrans</a>
  52. * also provides conversions to and from UTM and UPS. Version 2.4.2 (and
  53. * earlier) suffers from some drawbacks:
  54. * - Inconsistent rules are used to determine the whether a particular UTM or
  55. * UPS coordinate is legal. A more systematic approach is taken here.
  56. * - The underlying projections are not very accurately implemented.
  57. *
  58. * The GeographicLib::UTMUPS::EncodeZone encodes the UTM zone and hemisphere
  59. * to allow UTM/UPS coordinated to be displayed as, for example, "38N 444500
  60. * 3688500". According to NGA.SIG.0012_2.0.0_UTMUPS the use of "N" to denote
  61. * "north" in the context is not allowed (since a upper case letter in this
  62. * context denotes the MGRS latitude band). Consequently, as of version
  63. * 1.36, EncodeZone uses the lower case letters "n" and "s" to denote the
  64. * hemisphere. In addition EncodeZone accepts an optional final argument \e
  65. * abbrev, which, if false, results in the hemisphere being spelled out as in
  66. * "38north".
  67. *
  68. * Example of use:
  69. * \include example-UTMUPS.cpp
  70. **********************************************************************/
  71. class GEOGRAPHICLIB_EXPORT UTMUPS {
  72. private:
  73. typedef Math::real real;
  74. static const int falseeasting_[4];
  75. static const int falsenorthing_[4];
  76. static const int mineasting_[4];
  77. static const int maxeasting_[4];
  78. static const int minnorthing_[4];
  79. static const int maxnorthing_[4];
  80. static const int epsg01N = 32601; // EPSG code for UTM 01N
  81. static const int epsg60N = 32660; // EPSG code for UTM 60N
  82. static const int epsgN = 32661; // EPSG code for UPS N
  83. static const int epsg01S = 32701; // EPSG code for UTM 01S
  84. static const int epsg60S = 32760; // EPSG code for UTM 60S
  85. static const int epsgS = 32761; // EPSG code for UPS S
  86. static real CentralMeridian(int zone)
  87. { return real(6 * zone - 183); }
  88. // Throw an error if easting or northing are outside standard ranges. If
  89. // throwp = false, return bool instead.
  90. static bool CheckCoords(bool utmp, bool northp, real x, real y,
  91. bool msgrlimits = false, bool throwp = true);
  92. UTMUPS(); // Disable constructor
  93. public:
  94. /**
  95. * In this class we bring together the UTM and UPS coordinates systems.
  96. * The UTM divides the earth between latitudes &minus;80&deg; and 84&deg;
  97. * into 60 zones numbered 1 thru 60. Zone assign zone number 0 to the UPS
  98. * regions, covering the two poles. Within UTMUPS, non-negative zone
  99. * numbers refer to one of the "physical" zones, 0 for UPS and [1, 60] for
  100. * UTM. Negative "pseudo-zone" numbers are used to select one of the
  101. * physical zones.
  102. **********************************************************************/
  103. enum zonespec {
  104. /**
  105. * The smallest pseudo-zone number.
  106. **********************************************************************/
  107. MINPSEUDOZONE = -4,
  108. /**
  109. * A marker for an undefined or invalid zone. Equivalent to NaN.
  110. **********************************************************************/
  111. INVALID = -4,
  112. /**
  113. * If a coordinate already include zone information (e.g., it is an MGRS
  114. * coordinate), use that, otherwise apply the UTMUPS::STANDARD rules.
  115. **********************************************************************/
  116. MATCH = -3,
  117. /**
  118. * Apply the standard rules for UTM zone assigment extending the UTM zone
  119. * to each pole to give a zone number in [1, 60]. For example, use UTM
  120. * zone 38 for longitude in [42&deg;, 48&deg;). The rules include the
  121. * Norway and Svalbard exceptions.
  122. **********************************************************************/
  123. UTM = -2,
  124. /**
  125. * Apply the standard rules for zone assignment to give a zone number in
  126. * [0, 60]. If the latitude is not in [&minus;80&deg;, 84&deg;), then
  127. * use UTMUPS::UPS = 0, otherwise apply the rules for UTMUPS::UTM. The
  128. * tests on latitudes and longitudes are all closed on the lower end open
  129. * on the upper. Thus for UTM zone 38, latitude is in [&minus;80&deg;,
  130. * 84&deg;) and longitude is in [42&deg;, 48&deg;).
  131. **********************************************************************/
  132. STANDARD = -1,
  133. /**
  134. * The largest pseudo-zone number.
  135. **********************************************************************/
  136. MAXPSEUDOZONE = -1,
  137. /**
  138. * The smallest physical zone number.
  139. **********************************************************************/
  140. MINZONE = 0,
  141. /**
  142. * The zone number used for UPS
  143. **********************************************************************/
  144. UPS = 0,
  145. /**
  146. * The smallest UTM zone number.
  147. **********************************************************************/
  148. MINUTMZONE = 1,
  149. /**
  150. * The largest UTM zone number.
  151. **********************************************************************/
  152. MAXUTMZONE = 60,
  153. /**
  154. * The largest physical zone number.
  155. **********************************************************************/
  156. MAXZONE = 60,
  157. };
  158. /**
  159. * The standard zone.
  160. *
  161. * @param[in] lat latitude (degrees).
  162. * @param[in] lon longitude (degrees).
  163. * @param[in] setzone zone override (optional). If omitted, use the
  164. * standard rules for picking the zone. If \e setzone is given then use
  165. * that zone if it is non-negative, otherwise apply the rules given in
  166. * UTMUPS::zonespec.
  167. * @exception GeographicErr if \e setzone is outside the range
  168. * [UTMUPS::MINPSEUDOZONE, UTMUPS::MAXZONE] = [&minus;4, 60].
  169. *
  170. * This is exact.
  171. **********************************************************************/
  172. static int StandardZone(real lat, real lon, int setzone = STANDARD);
  173. /**
  174. * Forward projection, from geographic to UTM/UPS.
  175. *
  176. * @param[in] lat latitude of point (degrees).
  177. * @param[in] lon longitude of point (degrees).
  178. * @param[out] zone the UTM zone (zero means UPS).
  179. * @param[out] northp hemisphere (true means north, false means south).
  180. * @param[out] x easting of point (meters).
  181. * @param[out] y northing of point (meters).
  182. * @param[out] gamma meridian convergence at point (degrees).
  183. * @param[out] k scale of projection at point.
  184. * @param[in] setzone zone override (optional).
  185. * @param[in] mgrslimits if true enforce the stricter MGRS limits on the
  186. * coordinates (default = false).
  187. * @exception GeographicErr if \e lat is not in [&minus;90&deg;,
  188. * 90&deg;].
  189. * @exception GeographicErr if the resulting \e x or \e y is out of allowed
  190. * range (see Reverse); in this case, these arguments are unchanged.
  191. *
  192. * If \e setzone is omitted, use the standard rules for picking the zone.
  193. * If \e setzone is given then use that zone if it is non-negative,
  194. * otherwise apply the rules given in UTMUPS::zonespec. The accuracy of
  195. * the conversion is about 5nm.
  196. *
  197. * The northing \e y jumps by UTMUPS::UTMShift() when crossing the equator
  198. * in the southerly direction. Sometimes it is useful to remove this
  199. * discontinuity in \e y by extending the "northern" hemisphere using
  200. * UTMUPS::Transfer:
  201. * \code
  202. double lat = -1, lon = 123;
  203. int zone;
  204. bool northp;
  205. double x, y, gamma, k;
  206. GeographicLib::UTMUPS::Forward(lat, lon, zone, northp, x, y, gamma, k);
  207. GeographicLib::UTMUPS::Transfer(zone, northp, x, y,
  208. zone, true, x, y, zone);
  209. northp = true;
  210. \endcode
  211. **********************************************************************/
  212. static void Forward(real lat, real lon,
  213. int& zone, bool& northp, real& x, real& y,
  214. real& gamma, real& k,
  215. int setzone = STANDARD, bool mgrslimits = false);
  216. /**
  217. * Reverse projection, from UTM/UPS to geographic.
  218. *
  219. * @param[in] zone the UTM zone (zero means UPS).
  220. * @param[in] northp hemisphere (true means north, false means south).
  221. * @param[in] x easting of point (meters).
  222. * @param[in] y northing of point (meters).
  223. * @param[out] lat latitude of point (degrees).
  224. * @param[out] lon longitude of point (degrees).
  225. * @param[out] gamma meridian convergence at point (degrees).
  226. * @param[out] k scale of projection at point.
  227. * @param[in] mgrslimits if true enforce the stricter MGRS limits on the
  228. * coordinates (default = false).
  229. * @exception GeographicErr if \e zone, \e x, or \e y is out of allowed
  230. * range; this this case the arguments are unchanged.
  231. *
  232. * The accuracy of the conversion is about 5nm.
  233. *
  234. * UTM eastings are allowed to be in the range [0km, 1000km], northings are
  235. * allowed to be in in [0km, 9600km] for the northern hemisphere and in
  236. * [900km, 10000km] for the southern hemisphere. However UTM northings
  237. * can be continued across the equator. So the actual limits on the
  238. * northings are [-9100km, 9600km] for the "northern" hemisphere and
  239. * [900km, 19600km] for the "southern" hemisphere.
  240. *
  241. * UPS eastings and northings are allowed to be in the range [1200km,
  242. * 2800km] in the northern hemisphere and in [700km, 3300km] in the
  243. * southern hemisphere.
  244. *
  245. * These ranges are 100km larger than allowed for the conversions to MGRS.
  246. * (100km is the maximum extra padding consistent with eastings remaining
  247. * non-negative.) This allows generous overlaps between zones and UTM and
  248. * UPS. If \e mgrslimits = true, then all the ranges are shrunk by 100km
  249. * so that they agree with the stricter MGRS ranges. No checks are
  250. * performed besides these (e.g., to limit the distance outside the
  251. * standard zone boundaries).
  252. **********************************************************************/
  253. static void Reverse(int zone, bool northp, real x, real y,
  254. real& lat, real& lon, real& gamma, real& k,
  255. bool mgrslimits = false);
  256. /**
  257. * UTMUPS::Forward without returning convergence and scale.
  258. **********************************************************************/
  259. static void Forward(real lat, real lon,
  260. int& zone, bool& northp, real& x, real& y,
  261. int setzone = STANDARD, bool mgrslimits = false) {
  262. real gamma, k;
  263. Forward(lat, lon, zone, northp, x, y, gamma, k, setzone, mgrslimits);
  264. }
  265. /**
  266. * UTMUPS::Reverse without returning convergence and scale.
  267. **********************************************************************/
  268. static void Reverse(int zone, bool northp, real x, real y,
  269. real& lat, real& lon, bool mgrslimits = false) {
  270. real gamma, k;
  271. Reverse(zone, northp, x, y, lat, lon, gamma, k, mgrslimits);
  272. }
  273. /**
  274. * Transfer UTM/UPS coordinated from one zone to another.
  275. *
  276. * @param[in] zonein the UTM zone for \e xin and \e yin (or zero for UPS).
  277. * @param[in] northpin hemisphere for \e xin and \e yin (true means north,
  278. * false means south).
  279. * @param[in] xin easting of point (meters) in \e zonein.
  280. * @param[in] yin northing of point (meters) in \e zonein.
  281. * @param[in] zoneout the requested UTM zone for \e xout and \e yout (or
  282. * zero for UPS).
  283. * @param[in] northpout hemisphere for \e xout output and \e yout.
  284. * @param[out] xout easting of point (meters) in \e zoneout.
  285. * @param[out] yout northing of point (meters) in \e zoneout.
  286. * @param[out] zone the actual UTM zone for \e xout and \e yout (or zero
  287. * for UPS); this equals \e zoneout if \e zoneout &ge; 0.
  288. * @exception GeographicErr if \e zonein is out of range (see below).
  289. * @exception GeographicErr if \e zoneout is out of range (see below).
  290. * @exception GeographicErr if \e xin or \e yin fall outside their allowed
  291. * ranges (see UTMUPS::Reverse).
  292. * @exception GeographicErr if \e xout or \e yout fall outside their
  293. * allowed ranges (see UTMUPS::Reverse).
  294. *
  295. * \e zonein must be in the range [UTMUPS::MINZONE, UTMUPS::MAXZONE] = [0,
  296. * 60] with \e zonein = UTMUPS::UPS, 0, indicating UPS. \e zonein may
  297. * also be UTMUPS::INVALID.
  298. *
  299. * \e zoneout must be in the range [UTMUPS::MINPSEUDOZONE, UTMUPS::MAXZONE]
  300. * = [-4, 60]. If \e zoneout &lt; UTMUPS::MINZONE then the rules give in
  301. * the documentation of UTMUPS::zonespec are applied, and \e zone is set to
  302. * the actual zone used for output.
  303. *
  304. * (\e xout, \e yout) can overlap with (\e xin, \e yin).
  305. **********************************************************************/
  306. static void Transfer(int zonein, bool northpin, real xin, real yin,
  307. int zoneout, bool northpout, real& xout, real& yout,
  308. int& zone);
  309. /**
  310. * Decode a UTM/UPS zone string.
  311. *
  312. * @param[in] zonestr string representation of zone and hemisphere.
  313. * @param[out] zone the UTM zone (zero means UPS).
  314. * @param[out] northp hemisphere (true means north, false means south).
  315. * @exception GeographicErr if \e zonestr is malformed.
  316. *
  317. * For UTM, \e zonestr has the form of a zone number in the range
  318. * [UTMUPS::MINUTMZONE, UTMUPS::MAXUTMZONE] = [1, 60] followed by a
  319. * hemisphere letter, n or s (or "north" or "south" spelled out). For UPS,
  320. * it consists just of the hemisphere letter (or the spelled out
  321. * hemisphere). The returned value of \e zone is UTMUPS::UPS = 0 for UPS.
  322. * Note well that "38s" indicates the southern hemisphere of zone 38 and
  323. * not latitude band S, 32&deg; &le; \e lat &lt; 40&deg;. n, 01s, 2n, 38s,
  324. * south, 3north are legal. 0n, 001s, +3n, 61n, 38P are illegal. INV is a
  325. * special value for which the returned value of \e is UTMUPS::INVALID.
  326. **********************************************************************/
  327. static void DecodeZone(const std::string& zonestr,
  328. int& zone, bool& northp);
  329. /**
  330. * Encode a UTM/UPS zone string.
  331. *
  332. * @param[in] zone the UTM zone (zero means UPS).
  333. * @param[in] northp hemisphere (true means north, false means south).
  334. * @param[in] abbrev if true (the default) use abbreviated (n/s) notation
  335. * for hemisphere; otherwise spell out the hemisphere (north/south)
  336. * @exception GeographicErr if \e zone is out of range (see below).
  337. * @exception std::bad_alloc if memoy for the string can't be allocated.
  338. * @return string representation of zone and hemisphere.
  339. *
  340. * \e zone must be in the range [UTMUPS::MINZONE, UTMUPS::MAXZONE] = [0,
  341. * 60] with \e zone = UTMUPS::UPS, 0, indicating UPS (but the resulting
  342. * string does not contain "0"). \e zone may also be UTMUPS::INVALID, in
  343. * which case the returned string is "inv". This reverses
  344. * UTMUPS::DecodeZone.
  345. **********************************************************************/
  346. static std::string EncodeZone(int zone, bool northp, bool abbrev = true);
  347. /**
  348. * Decode EPSG.
  349. *
  350. * @param[in] epsg the EPSG code.
  351. * @param[out] zone the UTM zone (zero means UPS).
  352. * @param[out] northp hemisphere (true means north, false means south).
  353. *
  354. * EPSG (European Petroleum Survery Group) codes are a way to refer to many
  355. * different projections. DecodeEPSG decodes those referring to UTM or UPS
  356. * projections for the WGS84 ellipsoid. If the code does not refer to one
  357. * of these projections, \e zone is set to UTMUPS::INVALID. See
  358. * https://www.spatialreference.org/ref/epsg/
  359. **********************************************************************/
  360. static void DecodeEPSG(int epsg, int& zone, bool& northp);
  361. /**
  362. * Encode zone as EPSG.
  363. *
  364. * @param[in] zone the UTM zone (zero means UPS).
  365. * @param[in] northp hemisphere (true means north, false means south).
  366. * @return EPSG code (or -1 if \e zone is not in the range
  367. * [UTMUPS::MINZONE, UTMUPS::MAXZONE] = [0, 60])
  368. *
  369. * Convert \e zone and \e northp to the corresponding EPSG (European
  370. * Petroleum Survery Group) codes
  371. **********************************************************************/
  372. static int EncodeEPSG(int zone, bool northp);
  373. /**
  374. * @return shift (meters) necessary to align north and south halves of a
  375. * UTM zone (10<sup>7</sup>).
  376. **********************************************************************/
  377. static Math::real UTMShift();
  378. /** \name Inspector functions
  379. **********************************************************************/
  380. ///@{
  381. /**
  382. * @return \e a the equatorial radius of the WGS84 ellipsoid (meters).
  383. *
  384. * (The WGS84 value is returned because the UTM and UPS projections are
  385. * based on this ellipsoid.)
  386. **********************************************************************/
  387. static Math::real EquatorialRadius()
  388. { return Constants::WGS84_a(); }
  389. /**
  390. * @return \e f the flattening of the WGS84 ellipsoid.
  391. *
  392. * (The WGS84 value is returned because the UTM and UPS projections are
  393. * based on this ellipsoid.)
  394. **********************************************************************/
  395. static Math::real Flattening()
  396. { return Constants::WGS84_f(); }
  397. /**
  398. * \deprecated An old name for EquatorialRadius().
  399. **********************************************************************/
  400. GEOGRAPHICLIB_DEPRECATED("Use EquatorialRadius()")
  401. static Math::real MajorRadius() { return EquatorialRadius(); }
  402. ///@}
  403. };
  404. } // namespace GeographicLib
  405. #endif // GEOGRAPHICLIB_UTMUPS_HPP