DMS.cpp

Go to the documentation of this file.
00001 /**
00002  * \file DMS.cpp
00003  * \brief Implementation for GeographicLib::DMS class
00004  *
00005  * Copyright (c) Charles Karney (2008, 2009, 2010) <charles@karney.com>
00006  * and licensed under the LGPL.  For more information, see
00007  * http://geographiclib.sourceforge.net/
00008  **********************************************************************/
00009 
00010 #include "GeographicLib/DMS.hpp"
00011 
00012 #define GEOGRAPHICLIB_DMS_CPP "$Id: DMS.cpp 6827 2010-05-20 19:56:18Z karney $"
00013 
00014 RCSID_DECL(GEOGRAPHICLIB_DMS_CPP)
00015 RCSID_DECL(GEOGRAPHICLIB_DMS_HPP)
00016 
00017 namespace GeographicLib {
00018 
00019   using namespace std;
00020 
00021   const string DMS::hemispheres = "SNWE";
00022   const string DMS::signs = "-+";
00023   const string DMS::digits = "0123456789";
00024   const string DMS::dmsindicators = "D'\"";
00025   const string DMS::components[] = {"degrees", "minutes", "seconds"};
00026 
00027   Math::real DMS::Decode(const std::string& dms, flag& ind) {
00028     int sign = 1;
00029     unsigned
00030       beg = 0,
00031       end = unsigned(dms.size());
00032     while (beg < end && isspace(dms[beg]))
00033       ++beg;
00034     while (beg < end && isspace(dms[end - 1]))
00035       --end;
00036     flag ind1 = NONE;
00037     int k = -1;
00038     if (end > beg && (k = lookup(hemispheres, dms[beg])) >= 0) {
00039       ind1 = (k / 2) ? LONGITUDE : LATITUDE;
00040       sign = k % 2 ? 1 : -1;
00041       ++beg;
00042     }
00043     if (end > beg && (k = lookup(hemispheres, dms[end-1])) >= 0) {
00044       if (k >= 0) {
00045         if (ind1 != NONE) {
00046           if (toupper(dms[beg - 1]) == toupper(dms[end - 1]))
00047             throw GeographicErr("Repeated hemisphere indicators "
00048                                 + str(dms[beg - 1]) + " in "
00049                                 + dms.substr(beg - 1, end - beg + 1));
00050           else
00051             throw GeographicErr("Contradictory hemisphere indicators "
00052                                 + str(dms[beg - 1]) + " and "
00053                                 + str(dms[end - 1]) + " in "
00054                                 + dms.substr(beg - 1, end - beg + 1));
00055         }
00056         ind1 = (k / 2) ? LONGITUDE : LATITUDE;
00057         sign = k % 2 ? 1 : -1;
00058         --end;
00059       }
00060     }
00061     if (end > beg && (k = lookup(signs, dms[beg])) >= 0) {
00062       if (k >= 0) {
00063         sign *= k ? 1 : -1;
00064         ++beg;
00065       }
00066     }
00067     if (end == beg)
00068       throw GeographicErr("Empty or incomplete DMS string " + dms);
00069     real ipieces[] = {0, 0, 0};
00070     real fpieces[] = {0, 0, 0};
00071     unsigned npiece = 0;
00072     real icurrent = 0;
00073     real fcurrent = 0;
00074     unsigned ncurrent = 0, p = beg;
00075     bool pointseen = false;
00076     unsigned digcount = 0;
00077     while (p < end) {
00078       char x = dms[p++];
00079       if ((k = lookup(digits, x)) >= 0) {
00080         ++ncurrent;
00081         if (digcount > 0)
00082           ++digcount;           // Count of decimal digits
00083         else
00084           icurrent = 10 * icurrent + k;
00085       } else if (x == '.') {
00086         if (pointseen)
00087           throw GeographicErr("Multiple decimal points in "
00088                               + dms.substr(beg, end - beg));
00089         pointseen = true;
00090         digcount = 1;
00091       } else if ((k = lookup(dmsindicators, x)) >= 0) {
00092         if (unsigned(k) == npiece - 1)
00093           throw GeographicErr("Repeated " + components[k]
00094                               + " component in " + dms.substr(beg, end - beg));
00095         else if (unsigned(k) < npiece)
00096           throw GeographicErr(components[k] + " component follows "
00097                               + components[npiece - 1] + " component in "
00098                               + dms.substr(beg, end - beg));
00099         if (ncurrent == 0)
00100           throw GeographicErr("Missing numbers in " + components[k]
00101                               + " component of " + dms.substr(beg, end - beg));
00102         if (digcount > 1) {
00103           istringstream s(dms.substr(p - digcount - 1, digcount));
00104           s >> fcurrent;
00105         }
00106         ipieces[k] = icurrent;
00107         fpieces[k] = icurrent + fcurrent;
00108         if (p < end) {
00109           npiece = k + 1;
00110           icurrent = fcurrent = 0;
00111           ncurrent = digcount = 0;
00112         }
00113       } else if (lookup(signs, x) >= 0)
00114         throw GeographicErr("Internal sign in DMS string "
00115                             + dms.substr(beg, end - beg));
00116       else
00117         throw GeographicErr("Illegal character " + str(x)
00118                             + " in DMS string "
00119                             + dms.substr(beg, end - beg));
00120     }
00121     if (lookup(dmsindicators, dms[p - 1]) < 0) {
00122       if (npiece >= 3)
00123         throw GeographicErr("Extra text following seconds in DMS string "
00124                             + dms.substr(beg, end - beg));
00125       if (ncurrent == 0)
00126         throw GeographicErr("Missing numbers in " + components[k]
00127                             + " component of " + dms.substr(beg, end - beg));
00128       if (digcount > 1) {
00129         istringstream s(dms.substr(p - digcount, digcount));
00130         s >> fcurrent;
00131       }
00132       ipieces[npiece] = icurrent;
00133       fpieces[npiece] = icurrent + fcurrent;
00134     }
00135     if (pointseen && digcount == 0)
00136       throw GeographicErr("Decimal point in non-terminal component of "
00137                           + dms.substr(beg, end - beg));
00138     // Note that we accept 59.999999... even though it rounds to 60.
00139     if (ipieces[1] >= 60)
00140       throw GeographicErr("Minutes " + str(fpieces[1])
00141                           + " not in range [0, 60)");
00142     if (ipieces[2] >= 60)
00143       throw GeographicErr("Seconds " + str(fpieces[2])
00144                           + " not in range [0, 60)");
00145     ind = ind1;
00146     // Assume check on range of result is made by calling routine (which might
00147     // be able to offer a better diagnostic).
00148     return real(sign) * (fpieces[0] + (fpieces[1] + fpieces[2] / 60) / 60);
00149   }
00150 
00151   Math::real DMS::Decode(const std::string& str) {
00152     std::istringstream is(str);
00153     real num;
00154     if (!(is >> num))
00155       throw GeographicErr("Could not read number: " + str);
00156     // On some platforms, is >> num gobbles final E in 1234E, so look for last
00157     // character which is legal as the final character in a number (digit or
00158     // period).
00159     int pos = std::min(int(is.tellg()),
00160                        int(str.find_last_of("0123456789.")) + 1);
00161     if (pos != int(str.size()))
00162       throw GeographicErr("Extra text " + str.substr(pos) +
00163                           " in number " + str);
00164     return num;
00165   }
00166 
00167   void DMS::DecodeLatLon(const std::string& stra, const std::string& strb,
00168                          real& lat, real& lon) {
00169       real a, b;
00170       flag ia, ib;
00171       a = Decode(stra, ia);
00172       b = Decode(strb, ib);
00173       if (ia == NONE && ib == NONE) {
00174         // Default to lat, long
00175         ia = LATITUDE;
00176         ib = LONGITUDE;
00177       } else if (ia == NONE)
00178         ia = flag(LATITUDE + LONGITUDE - ib);
00179       else if (ib == NONE)
00180         ib = flag(LATITUDE + LONGITUDE - ia);
00181       if (ia == ib)
00182         throw GeographicErr("Both " + stra + " and "
00183                             + strb + " interpreted as "
00184                             + (ia == LATITUDE ? "latitudes" : "longitudes"));
00185       real
00186         lat1 = ia == LATITUDE ? a : b,
00187         lon1 = ia == LATITUDE ? b : a;
00188       if (! (lat1 >= -90 && lat1 <= 90))
00189         throw GeographicErr("Latitude " + str(lat1) + "d not in [-90d, 90d]");
00190       if (! (lon1 >= -180 && lon1 <= 360))
00191         throw GeographicErr("Latitude " + str(lon1)
00192                             + "d not in [-180d, 360d]");
00193       if (lon1 >= 180)
00194         lon1 -= 360;
00195       lat = lat1;
00196       lon = lon1;
00197   }
00198 
00199   Math::real DMS::DecodeAngle(const std::string& angstr) {
00200     DMS::flag ind;
00201     real ang = Decode(angstr, ind);
00202     if (ind != DMS::NONE)
00203       throw GeographicErr("Arc angle " + angstr
00204                           + " includes a hemisphere, N/E/W/S");
00205     return ang;
00206   }
00207 
00208   Math::real DMS::DecodeAzimuth(const std::string& azistr) {
00209     DMS::flag ind;
00210     real azi = Decode(azistr, ind);
00211     if (ind == DMS::LATITUDE)
00212       throw GeographicErr("Azimuth " + azistr
00213                           + " has a latitude hemisphere, N/S");
00214     if (!(azi >= -180 && azi <= 360))
00215       throw GeographicErr("Azimuth " + azistr + " not in range [-180,360]");
00216     if (azi >= 180) azi -= 360;
00217     return azi;
00218   }
00219 
00220   string DMS::Encode(real angle, component trailing, unsigned prec, flag ind) {
00221     // Assume check on range of input angle has been made by calling
00222     // routine (which might be able to offer a better diagnostic).
00223     //
00224     // 15 - 2 * trailing = ceiling(log10(2^53/90/60^trailing)).
00225     // This suffices to give full real precision for numbers in [-90,90]
00226     prec = min(15 - 2 * unsigned(trailing), prec);
00227     real scale = 1;
00228     for (unsigned i = 0; i < unsigned(trailing); ++i)
00229       scale *= 60;
00230     for (unsigned i = 0; i < prec; ++i)
00231       scale *= 10;
00232     if (ind == AZIMUTH)
00233       angle -= floor(angle/360) * 360;
00234     int sign = angle < 0 ? -1 : 1;
00235     angle *= sign;
00236 
00237     // Break off integer part to preserve precision in manipulation of
00238     // fractional part.
00239     real
00240       idegree = floor(angle),
00241       fdegree = floor((angle - idegree) * scale + real(0.5)) / scale;
00242     if (fdegree >= 1) {
00243       idegree += 1;
00244       fdegree -= 1;
00245     }
00246     real pieces[3] = {fdegree, 0, 0};
00247     for (unsigned i = 1; i <= unsigned(trailing); ++i) {
00248       real
00249         ip = floor(pieces[i - 1]),
00250         fp = pieces[i - 1] - ip;
00251       pieces[i] = fp * 60;
00252       pieces[i - 1] = ip;
00253     }
00254     pieces[0] += idegree;
00255     ostringstream s;
00256     s << fixed  << setfill('0');
00257     if (ind == NONE && sign < 0)
00258       s << '-';
00259     switch (trailing) {
00260     case DEGREE:
00261       if (ind != NONE)
00262         s << setw(1 + min(int(ind), 2) + prec + (prec ? 1 : 0));
00263       s << setprecision(prec) << pieces[0];
00264       // Don't include degree designator (d) if it is the trailing component.
00265       break;
00266     default:
00267       if (ind != NONE)
00268         s << setw(1 + min(int(ind), 2));
00269       s << setprecision(0) << pieces[0] << char(tolower(dmsindicators[0]));
00270       switch (trailing) {
00271       case MINUTE:
00272         s << setw(2 + prec + (prec ? 1 : 0)) << setprecision(prec)
00273           << pieces[1] <<  char(tolower(dmsindicators[1]));
00274         break;
00275       case SECOND:
00276         s << setw(2) << pieces[1] <<  char(tolower(dmsindicators[1]))
00277           << setw(2 + prec + (prec ? 1 : 0)) << setprecision(prec)
00278           << pieces[2] <<  char(tolower(dmsindicators[2]));
00279         break;
00280       default:
00281         break;
00282       }
00283     }
00284     if (ind != NONE && ind != AZIMUTH)
00285       s << hemispheres[(ind == LATITUDE ? 0 : 2) + (sign < 0 ? 0 : 1)];
00286     return s.str();
00287   }
00288 
00289 } // namespace GeographicLib

Generated on 21 May 2010 for GeographicLib by  doxygen 1.6.1