DMS.hpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /**
  2. * \file DMS.hpp
  3. * \brief Header for GeographicLib::DMS 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_DMS_HPP)
  10. #define GEOGRAPHICLIB_DMS_HPP 1
  11. #include <GeographicLib/Constants.hpp>
  12. #include <GeographicLib/Utility.hpp>
  13. #if defined(_MSC_VER)
  14. // Squelch warnings about dll vs vector and constant conditional expressions
  15. # pragma warning (push)
  16. # pragma warning (disable: 4251 4127)
  17. #endif
  18. namespace GeographicLib {
  19. /**
  20. * \brief Convert between degrees and the %DMS representation
  21. *
  22. * Parse a string representing degree, minutes, and seconds and return the
  23. * angle in degrees and format an angle in degrees as degree, minutes, and
  24. * seconds. In addition, handle NANs and infinities on input and output.
  25. *
  26. * Example of use:
  27. * \include example-DMS.cpp
  28. **********************************************************************/
  29. class GEOGRAPHICLIB_EXPORT DMS {
  30. public:
  31. /**
  32. * Indicator for presence of hemisphere indicator (N/S/E/W) on latitudes
  33. * and longitudes.
  34. **********************************************************************/
  35. enum flag {
  36. /**
  37. * No indicator present.
  38. * @hideinitializer
  39. **********************************************************************/
  40. NONE = 0,
  41. /**
  42. * Latitude indicator (N/S) present.
  43. * @hideinitializer
  44. **********************************************************************/
  45. LATITUDE = 1,
  46. /**
  47. * Longitude indicator (E/W) present.
  48. * @hideinitializer
  49. **********************************************************************/
  50. LONGITUDE = 2,
  51. /**
  52. * Used in Encode to indicate output of an azimuth in [000, 360) with no
  53. * letter indicator.
  54. * @hideinitializer
  55. **********************************************************************/
  56. AZIMUTH = 3,
  57. /**
  58. * Used in Encode to indicate output of a plain number.
  59. * @hideinitializer
  60. **********************************************************************/
  61. NUMBER = 4,
  62. };
  63. /**
  64. * Indicator for trailing units on an angle.
  65. **********************************************************************/
  66. enum component {
  67. /**
  68. * Trailing unit is degrees.
  69. * @hideinitializer
  70. **********************************************************************/
  71. DEGREE = 0,
  72. /**
  73. * Trailing unit is arc minutes.
  74. * @hideinitializer
  75. **********************************************************************/
  76. MINUTE = 1,
  77. /**
  78. * Trailing unit is arc seconds.
  79. * @hideinitializer
  80. **********************************************************************/
  81. SECOND = 2,
  82. };
  83. private:
  84. typedef Math::real real;
  85. // Replace all occurrences of pat by c. If c is NULL remove pat.
  86. static void replace(std::string& s, const std::string& pat, char c) {
  87. std::string::size_type p = 0;
  88. int count = c ? 1 : 0;
  89. while (true) {
  90. p = s.find(pat, p);
  91. if (p == std::string::npos)
  92. break;
  93. s.replace(p, pat.length(), count, c);
  94. }
  95. }
  96. static const char* const hemispheres_;
  97. static const char* const signs_;
  98. static const char* const digits_;
  99. static const char* const dmsindicators_;
  100. static const char* const components_[3];
  101. static Math::real NumMatch(const std::string& s);
  102. static Math::real InternalDecode(const std::string& dmsa, flag& ind);
  103. DMS(); // Disable constructor
  104. public:
  105. /**
  106. * Convert a string in DMS to an angle.
  107. *
  108. * @param[in] dms string input.
  109. * @param[out] ind a DMS::flag value signaling the presence of a
  110. * hemisphere indicator.
  111. * @exception GeographicErr if \e dms is malformed (see below).
  112. * @return angle (degrees).
  113. *
  114. * Degrees, minutes, and seconds are indicated by the characters d, '
  115. * (single quote), &quot; (double quote), and these components may only be
  116. * given in this order. Any (but not all) components may be omitted and
  117. * other symbols (e.g., the &deg; symbol for degrees and the unicode prime
  118. * and double prime symbols for minutes and seconds) may be substituted;
  119. * two single quotes can be used instead of &quot;. The last component
  120. * indicator may be omitted and is assumed to be the next smallest unit
  121. * (thus 33d10 is interpreted as 33d10'). The final component may be a
  122. * decimal fraction but the non-final components must be integers. Instead
  123. * of using d, ', and &quot; to indicate degrees, minutes, and seconds, :
  124. * (colon) may be used to <i>separate</i> these components (numbers must
  125. * appear before and after each colon); thus 50d30'10.3&quot; may be
  126. * written as 50:30:10.3, 5.5' may be written 0:5.5, and so on. The
  127. * integer parts of the minutes and seconds components must be less
  128. * than 60. A single leading sign is permitted. A hemisphere designator
  129. * (N, E, W, S) may be added to the beginning or end of the string. The
  130. * result is multiplied by the implied sign of the hemisphere designator
  131. * (negative for S and W). In addition \e ind is set to DMS::LATITUDE if N
  132. * or S is present, to DMS::LONGITUDE if E or W is present, and to
  133. * DMS::NONE otherwise. Throws an error on a malformed string. No check
  134. * is performed on the range of the result. Examples of legal and illegal
  135. * strings are
  136. * - <i>LEGAL</i> (all the entries on each line are equivalent)
  137. * - -20.51125, 20d30'40.5&quot;S, -20&deg;30'40.5, -20d30.675,
  138. * N-20d30'40.5&quot;, -20:30:40.5
  139. * - 4d0'9, 4d9&quot;, 4d9'', 4:0:9, 004:00:09, 4.0025, 4.0025d, 4d0.15,
  140. * 04:.15
  141. * - 4:59.99999999999999, 4:60.0, 4:59:59.9999999999999, 4:59:60.0, 5
  142. * - <i>ILLEGAL</i> (the exception thrown explains the problem)
  143. * - 4d5&quot;4', 4::5, 4:5:, :4:5, 4d4.5'4&quot;, -N20.5, 1.8e2d, 4:60,
  144. * 4:59:60
  145. *
  146. * The decoding operation can also perform addition and subtraction
  147. * operations. If the string includes <i>internal</i> signs (i.e., not at
  148. * the beginning nor immediately after an initial hemisphere designator),
  149. * then the string is split immediately before such signs and each piece is
  150. * decoded according to the above rules and the results added; thus
  151. * <code>S3-2.5+4.1N</code> is parsed as the sum of <code>S3</code>,
  152. * <code>-2.5</code>, <code>+4.1N</code>. Any piece can include a
  153. * hemisphere designator; however, if multiple designators are given, they
  154. * must compatible; e.g., you cannot mix N and E. In addition, the
  155. * designator can appear at the beginning or end of the first piece, but
  156. * must be at the end of all subsequent pieces (a hemisphere designator is
  157. * not allowed after the initial sign). Examples of legal and illegal
  158. * combinations are
  159. * - <i>LEGAL</i> (these are all equivalent)
  160. * - 070:00:45, 70:01:15W+0:0.5, 70:01:15W-0:0:30W, W70:01:15+0:0:30E
  161. * - <i>ILLEGAL</i> (the exception thrown explains the problem)
  162. * - 70:01:15W+0:0:15N, W70:01:15+W0:0:15
  163. *
  164. * \warning The "exponential" notation is not recognized. Thus
  165. * <code>7.0E1</code> is illegal, while <code>7.0E+1</code> is parsed as
  166. * <code>(7.0E) + (+1)</code>, yielding the same result as
  167. * <code>8.0E</code>.
  168. *
  169. * \note At present, all the string handling in the C++ implementation of
  170. * %GeographicLib is with 8-bit characters. The support for unicode
  171. * symbols for degrees, minutes, and seconds is therefore via the
  172. * <a href="https://en.wikipedia.org/wiki/UTF-8">UTF-8</a> encoding. (The
  173. * JavaScript implementation of this class uses unicode natively, of
  174. * course.)
  175. *
  176. * Here is the list of Unicode symbols supported for degrees, minutes,
  177. * seconds, and the plus and minus signs; various symbols denoting variants
  178. * of a space, which may separate the components of a DMS string, are
  179. * removed:
  180. * - degrees:
  181. * - d, D lower and upper case letters
  182. * - U+00b0 degree symbol (&deg;)
  183. * - U+00ba masculine ordinal indicator (&ordm;)
  184. * - U+2070 superscript zero (⁰)
  185. * - U+02da ring above (˚)
  186. * - U+2218 compose function (∘)
  187. * - * the <a href="https://grid.nga.mil">GRiD</a> symbol for degrees
  188. * - minutes:
  189. * - ' apostrophe
  190. * - ` grave accent
  191. * - U+2032 prime (&prime;)
  192. * - U+2035 back prime (‵)
  193. * - U+00b4 acute accent (&acute;)
  194. * - U+2018 left single quote (&lsquo;)
  195. * - U+2019 right single quote (&rsquo;)
  196. * - U+201b reversed-9 single quote (‛)
  197. * - U+02b9 modifier letter prime (ʹ)
  198. * - U+02ca modifier letter acute accent (ˊ)
  199. * - U+02cb modifier letter grave accent (ˋ)
  200. * - seconds:
  201. * - &quot; quotation mark
  202. * - U+2033 double prime (&Prime;)
  203. * - U+2036 reversed double prime (‶)
  204. * + U+02dd double acute accent (˝)
  205. * - U+201c left double quote (&ldquo;)
  206. * - U+201d right double quote (&rdquo;)
  207. * - U+201f reversed-9 double quote (‟)
  208. * - U+02ba modifier letter double prime (ʺ)
  209. * - '&nbsp;' any two consecutive symbols for minutes
  210. * - plus sign:
  211. * - + plus
  212. * - U+2795 heavy plus (➕)
  213. * - U+2064 invisible plus (|⁤|)
  214. * - minus sign:
  215. * - - hyphen
  216. * - U+2010 dash (‐)
  217. * - U+2011 non-breaking hyphen (‑)
  218. * - U+2013 en dash (&ndash;)
  219. * - U+2014 em dash (&mdash;)
  220. * - U+2212 minus sign (&minus;)
  221. * - U+2796 heavy minus (➖)
  222. * - ignored spaces:
  223. * - U+00a0 non-breaking space
  224. * - U+2007 figure space (| |)
  225. * - U+2009 thin space (|&thinsp;|)
  226. * - U+200a hair space ( | |)
  227. * - U+200b invisible space (|​|)
  228. * - U+202f narrow space ( | |)
  229. * - U+2063 invisible separator (|⁣|)
  230. * .
  231. * The codes with a leading zero byte, e.g., U+00b0, are accepted in their
  232. * UTF-8 coded form 0xc2 0xb0 and as a single byte 0xb0.
  233. **********************************************************************/
  234. static Math::real Decode(const std::string& dms, flag& ind);
  235. /**
  236. * Convert DMS to an angle.
  237. *
  238. * @param[in] d degrees.
  239. * @param[in] m arc minutes.
  240. * @param[in] s arc seconds.
  241. * @return angle (degrees)
  242. *
  243. * This does not propagate the sign on \e d to the other components,
  244. * so -3d20' would need to be represented as - DMS::Decode(3.0, 20.0) or
  245. * DMS::Decode(-3.0, -20.0).
  246. **********************************************************************/
  247. static Math::real Decode(real d, real m = 0, real s = 0)
  248. { return d + (m + s / 60) / 60; }
  249. /**
  250. * Convert a pair of strings to latitude and longitude.
  251. *
  252. * @param[in] dmsa first string.
  253. * @param[in] dmsb second string.
  254. * @param[out] lat latitude (degrees).
  255. * @param[out] lon longitude (degrees).
  256. * @param[in] longfirst if true assume longitude is given before latitude
  257. * in the absence of hemisphere designators (default false).
  258. * @exception GeographicErr if \e dmsa or \e dmsb is malformed.
  259. * @exception GeographicErr if \e dmsa and \e dmsb are both interpreted as
  260. * latitudes.
  261. * @exception GeographicErr if \e dmsa and \e dmsb are both interpreted as
  262. * longitudes.
  263. * @exception GeographicErr if decoded latitude is not in [&minus;90&deg;,
  264. * 90&deg;].
  265. *
  266. * By default, the \e lat (resp., \e lon) is assigned to the results of
  267. * decoding \e dmsa (resp., \e dmsb). However this is overridden if either
  268. * \e dmsa or \e dmsb contain a latitude or longitude hemisphere designator
  269. * (N, S, E, W). If an exception is thrown, \e lat and \e lon are
  270. * unchanged.
  271. **********************************************************************/
  272. static void DecodeLatLon(const std::string& dmsa, const std::string& dmsb,
  273. real& lat, real& lon,
  274. bool longfirst = false);
  275. /**
  276. * Convert a string to an angle in degrees.
  277. *
  278. * @param[in] angstr input string.
  279. * @exception GeographicErr if \e angstr is malformed.
  280. * @exception GeographicErr if \e angstr includes a hemisphere designator.
  281. * @return angle (degrees)
  282. *
  283. * No hemisphere designator is allowed and no check is done on the range of
  284. * the result.
  285. **********************************************************************/
  286. static Math::real DecodeAngle(const std::string& angstr);
  287. /**
  288. * Convert a string to an azimuth in degrees.
  289. *
  290. * @param[in] azistr input string.
  291. * @exception GeographicErr if \e azistr is malformed.
  292. * @exception GeographicErr if \e azistr includes a N/S designator.
  293. * @return azimuth (degrees) reduced to the range [&minus;180&deg;,
  294. * 180&deg;].
  295. *
  296. * A hemisphere designator E/W can be used; the result is multiplied by
  297. * &minus;1 if W is present.
  298. **********************************************************************/
  299. static Math::real DecodeAzimuth(const std::string& azistr);
  300. /**
  301. * Convert angle (in degrees) into a DMS string (using d, ', and &quot;).
  302. *
  303. * @param[in] angle input angle (degrees)
  304. * @param[in] trailing DMS::component value indicating the trailing units
  305. * of the string (this component is given as a decimal number if
  306. * necessary).
  307. * @param[in] prec the number of digits after the decimal point for the
  308. * trailing component.
  309. * @param[in] ind DMS::flag value indicating additional formatting.
  310. * @param[in] dmssep if non-null, use as the DMS separator character
  311. * (instead of d, ', &quot; delimiters).
  312. * @exception std::bad_alloc if memory for the string can't be allocated.
  313. * @return formatted string
  314. *
  315. * The interpretation of \e ind is as follows:
  316. * - ind == DMS::NONE, signed result no leading zeros on degrees except in
  317. * the units place, e.g., -8d03'.
  318. * - ind == DMS::LATITUDE, trailing N or S hemisphere designator, no sign,
  319. * pad degrees to 2 digits, e.g., 08d03'S.
  320. * - ind == DMS::LONGITUDE, trailing E or W hemisphere designator, no
  321. * sign, pad degrees to 3 digits, e.g., 008d03'W.
  322. * - ind == DMS::AZIMUTH, convert to the range [0, 360&deg;), no
  323. * sign, pad degrees to 3 digits, e.g., 351d57'.
  324. * .
  325. * The integer parts of the minutes and seconds components are always given
  326. * with 2 digits.
  327. **********************************************************************/
  328. static std::string Encode(real angle, component trailing, unsigned prec,
  329. flag ind = NONE, char dmssep = char(0));
  330. /**
  331. * Convert angle into a DMS string (using d, ', and &quot;) selecting the
  332. * trailing component based on the precision.
  333. *
  334. * @param[in] angle input angle (degrees)
  335. * @param[in] prec the precision relative to 1 degree.
  336. * @param[in] ind DMS::flag value indicated additional formatting.
  337. * @param[in] dmssep if non-null, use as the DMS separator character
  338. * (instead of d, ', &quot; delimiters).
  339. * @exception std::bad_alloc if memory for the string can't be allocated.
  340. * @return formatted string
  341. *
  342. * \e prec indicates the precision relative to 1 degree, e.g., \e prec = 3
  343. * gives a result accurate to 0.1' and \e prec = 4 gives a result accurate
  344. * to 1&quot;. \e ind is interpreted as in DMS::Encode with the additional
  345. * facility that DMS::NUMBER represents \e angle as a number in fixed
  346. * format with precision \e prec.
  347. **********************************************************************/
  348. static std::string Encode(real angle, unsigned prec, flag ind = NONE,
  349. char dmssep = char(0)) {
  350. return ind == NUMBER ? Utility::str(angle, int(prec)) :
  351. Encode(angle,
  352. prec < 2 ? DEGREE : (prec < 4 ? MINUTE : SECOND),
  353. prec < 2 ? prec : (prec < 4 ? prec - 2 : prec - 4),
  354. ind, dmssep);
  355. }
  356. /**
  357. * Split angle into degrees and minutes
  358. *
  359. * @param[in] ang angle (degrees)
  360. * @param[out] d degrees (an integer returned as a real)
  361. * @param[out] m arc minutes.
  362. **********************************************************************/
  363. static void Encode(real ang, real& d, real& m) {
  364. d = int(ang); m = 60 * (ang - d);
  365. }
  366. /**
  367. * Split angle into degrees and minutes and seconds.
  368. *
  369. * @param[in] ang angle (degrees)
  370. * @param[out] d degrees (an integer returned as a real)
  371. * @param[out] m arc minutes (an integer returned as a real)
  372. * @param[out] s arc seconds.
  373. **********************************************************************/
  374. static void Encode(real ang, real& d, real& m, real& s) {
  375. d = int(ang); ang = 60 * (ang - d);
  376. m = int(ang); s = 60 * (ang - m);
  377. }
  378. };
  379. } // namespace GeographicLib
  380. #if defined(_MSC_VER)
  381. # pragma warning (pop)
  382. #endif
  383. #endif // GEOGRAPHICLIB_DMS_HPP