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