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