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