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