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