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