GeographicLib  1.49
DMS.cpp
Go to the documentation of this file.
1 /**
2  * \file DMS.cpp
3  * \brief Implementation for GeographicLib::DMS class
4  *
5  * Copyright (c) Charles Karney (2008-2017) <charles@karney.com> and licensed
6  * under the MIT/X11 License. For more information, see
7  * https://geographiclib.sourceforge.io/
8  **********************************************************************/
9 
10 #include <GeographicLib/DMS.hpp>
12 
13 #if defined(_MSC_VER)
14 // Squelch warnings about constant conditional expressions
15 # pragma warning (disable: 4127)
16 #endif
17 
18 namespace GeographicLib {
19 
20  using namespace std;
21 
22  const char* const DMS::hemispheres_ = "SNWE";
23  const char* const DMS::signs_ = "-+";
24  const char* const DMS::digits_ = "0123456789";
25  const char* const DMS::dmsindicators_ = "D'\":";
26  const char* const DMS::components_[] = {"degrees", "minutes", "seconds"};
27 
28  Math::real DMS::Decode(const std::string& dms, flag& ind) {
29  string dmsa = dms;
30  replace(dmsa, "\xc2\xb0", 'd'); // U+00b0 degree symbol
31  replace(dmsa, "\xc2\xba", 'd'); // U+00ba alt symbol
32  replace(dmsa, "\xe2\x81\xb0", 'd'); // U+2070 sup zero
33  replace(dmsa, "\xcb\x9a", 'd'); // U+02da ring above
34  replace(dmsa, "\xe2\x80\xb2", '\''); // U+2032 prime
35  replace(dmsa, "\xc2\xb4", '\''); // U+00b4 acute accent
36  replace(dmsa, "\xe2\x80\x99", '\''); // U+2019 right single quote
37  replace(dmsa, "\xe2\x80\xb3", '"'); // U+2033 double prime
38  replace(dmsa, "\xe2\x80\x9d", '"'); // U+201d right double quote
39  replace(dmsa, "\xe2\x88\x92", '-'); // U+2212 minus sign
40  replace(dmsa, "\xb0", 'd'); // 0xb0 bare degree symbol
41  replace(dmsa, "\xba", 'd'); // 0xba bare alt symbol
42  replace(dmsa, "\xb4", '\''); // 0xb4 bare acute accent
43  replace(dmsa, "''", '"'); // '' -> "
44  string::size_type
45  beg = 0,
46  end = unsigned(dmsa.size());
47  while (beg < end && isspace(dmsa[beg]))
48  ++beg;
49  while (beg < end && isspace(dmsa[end - 1]))
50  --end;
51  // The trimmed string in [beg, end)
52  real v = 0;
53  int i = 0;
54  flag ind1 = NONE;
55  // p is pointer to the next piece that needs decoding
56  for (string::size_type p = beg, pb; p < end; p = pb, ++i) {
57  string::size_type pa = p;
58  // Skip over initial hemisphere letter (for i == 0)
59  if (i == 0 && Utility::lookup(hemispheres_, dmsa[pa]) >= 0)
60  ++pa;
61  // Skip over initial sign (checking for it if i == 0)
62  if (i > 0 || (pa < end && Utility::lookup(signs_, dmsa[pa]) >= 0))
63  ++pa;
64  // Find next sign
65  pb = min(dmsa.find_first_of(signs_, pa), end);
66  flag ind2 = NONE;
67  v += InternalDecode(dmsa.substr(p, pb - p), ind2);
68  if (ind1 == NONE)
69  ind1 = ind2;
70  else if (!(ind2 == NONE || ind1 == ind2))
71  throw GeographicErr("Incompatible hemisphere specifies in " +
72  dmsa.substr(beg, pb - beg));
73  }
74  if (i == 0)
75  throw GeographicErr("Empty or incomplete DMS string " +
76  dmsa.substr(beg, end - beg));
77  ind = ind1;
78  return v;
79  }
80 
81  Math::real DMS::InternalDecode(const std::string& dmsa, flag& ind) {
82  string errormsg;
83  do { // Executed once (provides the ability to break)
84  int sign = 1;
85  unsigned
86  beg = 0,
87  end = unsigned(dmsa.size());
88  flag ind1 = NONE;
89  int k = -1;
90  if (end > beg && (k = Utility::lookup(hemispheres_, dmsa[beg])) >= 0) {
91  ind1 = (k / 2) ? LONGITUDE : LATITUDE;
92  sign = k % 2 ? 1 : -1;
93  ++beg;
94  }
95  if (end > beg && (k = Utility::lookup(hemispheres_, dmsa[end-1])) >= 0) {
96  if (k >= 0) {
97  if (ind1 != NONE) {
98  if (toupper(dmsa[beg - 1]) == toupper(dmsa[end - 1]))
99  errormsg = "Repeated hemisphere indicators "
100  + Utility::str(dmsa[beg - 1])
101  + " in " + dmsa.substr(beg - 1, end - beg + 1);
102  else
103  errormsg = "Contradictory hemisphere indicators "
104  + Utility::str(dmsa[beg - 1]) + " and "
105  + Utility::str(dmsa[end - 1]) + " in "
106  + dmsa.substr(beg - 1, end - beg + 1);
107  break;
108  }
109  ind1 = (k / 2) ? LONGITUDE : LATITUDE;
110  sign = k % 2 ? 1 : -1;
111  --end;
112  }
113  }
114  if (end > beg && (k = Utility::lookup(signs_, dmsa[beg])) >= 0) {
115  if (k >= 0) {
116  sign *= k ? 1 : -1;
117  ++beg;
118  }
119  }
120  if (end == beg) {
121  errormsg = "Empty or incomplete DMS string " + dmsa;
122  break;
123  }
124  real ipieces[] = {0, 0, 0};
125  real fpieces[] = {0, 0, 0};
126  unsigned npiece = 0;
127  real icurrent = 0;
128  real fcurrent = 0;
129  unsigned ncurrent = 0, p = beg;
130  bool pointseen = false;
131  unsigned digcount = 0, intcount = 0;
132  while (p < end) {
133  char x = dmsa[p++];
134  if ((k = Utility::lookup(digits_, x)) >= 0) {
135  ++ncurrent;
136  if (digcount > 0)
137  ++digcount; // Count of decimal digits
138  else {
139  icurrent = 10 * icurrent + k;
140  ++intcount;
141  }
142  } else if (x == '.') {
143  if (pointseen) {
144  errormsg = "Multiple decimal points in "
145  + dmsa.substr(beg, end - beg);
146  break;
147  }
148  pointseen = true;
149  digcount = 1;
150  } else if ((k = Utility::lookup(dmsindicators_, x)) >= 0) {
151  if (k >= 3) {
152  if (p == end) {
153  errormsg = "Illegal for : to appear at the end of " +
154  dmsa.substr(beg, end - beg);
155  break;
156  }
157  k = npiece;
158  }
159  if (unsigned(k) == npiece - 1) {
160  errormsg = "Repeated " + string(components_[k]) +
161  " component in " + dmsa.substr(beg, end - beg);
162  break;
163  } else if (unsigned(k) < npiece) {
164  errormsg = string(components_[k]) + " component follows "
165  + string(components_[npiece - 1]) + " component in "
166  + dmsa.substr(beg, end - beg);
167  break;
168  }
169  if (ncurrent == 0) {
170  errormsg = "Missing numbers in " + string(components_[k]) +
171  " component of " + dmsa.substr(beg, end - beg);
172  break;
173  }
174  if (digcount > 0) {
175  istringstream s(dmsa.substr(p - intcount - digcount - 1,
176  intcount + digcount));
177  s >> fcurrent;
178  icurrent = 0;
179  }
180  ipieces[k] = icurrent;
181  fpieces[k] = icurrent + fcurrent;
182  if (p < end) {
183  npiece = k + 1;
184  icurrent = fcurrent = 0;
185  ncurrent = digcount = intcount = 0;
186  }
187  } else if (Utility::lookup(signs_, x) >= 0) {
188  errormsg = "Internal sign in DMS string "
189  + dmsa.substr(beg, end - beg);
190  break;
191  } else {
192  errormsg = "Illegal character " + Utility::str(x) + " in DMS string "
193  + dmsa.substr(beg, end - beg);
194  break;
195  }
196  }
197  if (!errormsg.empty())
198  break;
199  if (Utility::lookup(dmsindicators_, dmsa[p - 1]) < 0) {
200  if (npiece >= 3) {
201  errormsg = "Extra text following seconds in DMS string "
202  + dmsa.substr(beg, end - beg);
203  break;
204  }
205  if (ncurrent == 0) {
206  errormsg = "Missing numbers in trailing component of "
207  + dmsa.substr(beg, end - beg);
208  break;
209  }
210  if (digcount > 0) {
211  istringstream s(dmsa.substr(p - intcount - digcount,
212  intcount + digcount));
213  s >> fcurrent;
214  icurrent = 0;
215  }
216  ipieces[npiece] = icurrent;
217  fpieces[npiece] = icurrent + fcurrent;
218  }
219  if (pointseen && digcount == 0) {
220  errormsg = "Decimal point in non-terminal component of "
221  + dmsa.substr(beg, end - beg);
222  break;
223  }
224  // Note that we accept 59.999999... even though it rounds to 60.
225  if (ipieces[1] >= 60 || fpieces[1] > 60 ) {
226  errormsg = "Minutes " + Utility::str(fpieces[1])
227  + " not in range [0, 60)";
228  break;
229  }
230  if (ipieces[2] >= 60 || fpieces[2] > 60) {
231  errormsg = "Seconds " + Utility::str(fpieces[2])
232  + " not in range [0, 60)";
233  break;
234  }
235  ind = ind1;
236  // Assume check on range of result is made by calling routine (which
237  // might be able to offer a better diagnostic).
238  return real(sign) *
239  ( fpieces[2] != 0 ?
240  (60*(60*fpieces[0] + fpieces[1]) + fpieces[2]) / 3600 :
241  ( fpieces[1] != 0 ?
242  (60*fpieces[0] + fpieces[1]) / 60 : fpieces[0] ) );
243  } while (false);
244  real val = Utility::nummatch<real>(dmsa);
245  if (val == 0)
246  throw GeographicErr(errormsg);
247  else
248  ind = NONE;
249  return val;
250  }
251 
252  void DMS::DecodeLatLon(const std::string& stra, const std::string& strb,
253  real& lat, real& lon,
254  bool longfirst) {
255  real a, b;
256  flag ia, ib;
257  a = Decode(stra, ia);
258  b = Decode(strb, ib);
259  if (ia == NONE && ib == NONE) {
260  // Default to lat, long unless longfirst
261  ia = longfirst ? LONGITUDE : LATITUDE;
262  ib = longfirst ? LATITUDE : LONGITUDE;
263  } else if (ia == NONE)
264  ia = flag(LATITUDE + LONGITUDE - ib);
265  else if (ib == NONE)
266  ib = flag(LATITUDE + LONGITUDE - ia);
267  if (ia == ib)
268  throw GeographicErr("Both " + stra + " and "
269  + strb + " interpreted as "
270  + (ia == LATITUDE ? "latitudes" : "longitudes"));
271  real
272  lat1 = ia == LATITUDE ? a : b,
273  lon1 = ia == LATITUDE ? b : a;
274  if (abs(lat1) > 90)
275  throw GeographicErr("Latitude " + Utility::str(lat1)
276  + "d not in [-90d, 90d]");
277  lat = lat1;
278  lon = lon1;
279  }
280 
281  Math::real DMS::DecodeAngle(const std::string& angstr) {
282  flag ind;
283  real ang = Decode(angstr, ind);
284  if (ind != NONE)
285  throw GeographicErr("Arc angle " + angstr
286  + " includes a hemisphere, N/E/W/S");
287  return ang;
288  }
289 
290  Math::real DMS::DecodeAzimuth(const std::string& azistr) {
291  flag ind;
292  real azi = Decode(azistr, ind);
293  if (ind == LATITUDE)
294  throw GeographicErr("Azimuth " + azistr
295  + " has a latitude hemisphere, N/S");
296  return Math::AngNormalize(azi);
297  }
298 
299  string DMS::Encode(real angle, component trailing, unsigned prec, flag ind,
300  char dmssep) {
301  // Assume check on range of input angle has been made by calling
302  // routine (which might be able to offer a better diagnostic).
303  if (!Math::isfinite(angle))
304  return angle < 0 ? string("-inf") :
305  (angle > 0 ? string("inf") : string("nan"));
306 
307  // 15 - 2 * trailing = ceiling(log10(2^53/90/60^trailing)).
308  // This suffices to give full real precision for numbers in [-90,90]
309  prec = min(15 + Math::extra_digits() - 2 * unsigned(trailing), prec);
310  real scale = 1;
311  for (unsigned i = 0; i < unsigned(trailing); ++i)
312  scale *= 60;
313  for (unsigned i = 0; i < prec; ++i)
314  scale *= 10;
315  if (ind == AZIMUTH)
316  angle -= floor(angle/360) * 360;
317  int sign = angle < 0 ? -1 : 1;
318  angle *= sign;
319 
320  // Break off integer part to preserve precision in manipulation of
321  // fractional part.
322  real
323  idegree = floor(angle),
324  fdegree = (angle - idegree) * scale + real(0.5);
325  {
326  // Implement the "round ties to even" rule
327  real f = floor(fdegree);
328  fdegree = (f == fdegree && fmod(f, real(2)) == 1) ? f - 1 : f;
329  }
330  fdegree /= scale;
331  if (fdegree >= 1) {
332  idegree += 1;
333  fdegree -= 1;
334  }
335  real pieces[3] = {fdegree, 0, 0};
336  for (unsigned i = 1; i <= unsigned(trailing); ++i) {
337  real
338  ip = floor(pieces[i - 1]),
339  fp = pieces[i - 1] - ip;
340  pieces[i] = fp * 60;
341  pieces[i - 1] = ip;
342  }
343  pieces[0] += idegree;
344  ostringstream s;
345  s << fixed << setfill('0');
346  if (ind == NONE && sign < 0)
347  s << '-';
348  switch (trailing) {
349  case DEGREE:
350  if (ind != NONE)
351  s << setw(1 + min(int(ind), 2) + prec + (prec ? 1 : 0));
352  s << Utility::str(pieces[0], prec);
353  // Don't include degree designator (d) if it is the trailing component.
354  break;
355  default:
356  if (ind != NONE)
357  s << setw(1 + min(int(ind), 2));
358  s << int(pieces[0])
359  << (dmssep ? dmssep : char(tolower(dmsindicators_[0])));
360  switch (trailing) {
361  case MINUTE:
362  s << setw(2 + prec + (prec ? 1 : 0)) << Utility::str(pieces[1], prec);
363  if (!dmssep)
364  s << char(tolower(dmsindicators_[1]));
365  break;
366  case SECOND:
367  s << setw(2)
368  << int(pieces[1])
369  << (dmssep ? dmssep : char(tolower(dmsindicators_[1])))
370  << setw(2 + prec + (prec ? 1 : 0)) << Utility::str(pieces[2], prec);
371  if (!dmssep)
372  s << char(tolower(dmsindicators_[2]));
373  break;
374  default:
375  break;
376  }
377  }
378  if (ind != NONE && ind != AZIMUTH)
379  s << hemispheres_[(ind == LATITUDE ? 0 : 2) + (sign < 0 ? 0 : 1)];
380  return s.str();
381  }
382 
383 } // namespace GeographicLib
static T AngNormalize(T x)
Definition: Math.hpp:440
static Math::real DecodeAngle(const std::string &angstr)
Definition: DMS.cpp:281
GeographicLib::Math::real real
Definition: GeodSolve.cpp:31
Header for GeographicLib::Utility class.
static bool isfinite(T x)
Definition: Math.hpp:806
static int extra_digits()
Definition: Math.hpp:187
static std::string Encode(real angle, component trailing, unsigned prec, flag ind=NONE, char dmssep=char(0))
Definition: DMS.cpp:299
static Math::real DecodeAzimuth(const std::string &azistr)
Definition: DMS.cpp:290
static Math::real Decode(const std::string &dms, flag &ind)
Definition: DMS.cpp:28
Namespace for GeographicLib.
Definition: Accumulator.cpp:12
static std::string str(T x, int p=-1)
Definition: Utility.hpp:276
static void DecodeLatLon(const std::string &dmsa, const std::string &dmsb, real &lat, real &lon, bool longfirst=false)
Definition: DMS.cpp:252
Exception handling for GeographicLib.
Definition: Constants.hpp:389
static int lookup(const std::string &s, char c)
Definition: Utility.hpp:459
Header for GeographicLib::DMS class.