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