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) <charles@karney.com>
00006  * http://charles.karney.info/geographic
00007  * and licensed under the LGPL.
00008  **********************************************************************/
00009 
00010 #include "GeographicLib/DMS.hpp"
00011 #include <algorithm>
00012 #include <cmath>
00013 #include <stdexcept>
00014 #include <iomanip>
00015 
00016 namespace {
00017   char RCSID[] = "$Id: DMS.cpp 6572 2009-03-01 22:41:48Z ckarney $";
00018   char RCSID_H[] = DMS_HPP;
00019 }
00020 
00021 namespace GeographicLib {
00022 
00023   using namespace std;
00024 
00025   const string DMS::hemispheres = "SNWE";
00026   const string DMS::signs = "-+";
00027   const string DMS::digits = "0123456789";
00028   const string DMS::dmsindicators = "D'\"";
00029   const string DMS::components[] = {"degrees", "minutes", "seconds"};
00030 
00031   double DMS::Decode(const std::string& dms, flag& ind) {
00032     double sign = 1;
00033     unsigned
00034       beg = 0,
00035       end = unsigned(dms.size());
00036     while (beg < end && isspace(dms[beg]))
00037       ++beg;
00038     while (beg < end && isspace(dms[end - 1]))
00039       --end;
00040     ind = NONE;
00041     int k = -1;
00042     if (end > beg && (k = lookup(hemispheres, dms[beg])) >= 0) {
00043       ind = (k / 2) ? LONGITUDE : LATITUDE;
00044       sign = k % 2 ? 1 : -1;
00045       ++beg;
00046     }
00047     if (end > beg && (k = lookup(hemispheres, dms[end-1])) >= 0) {
00048       if (k >= 0) {
00049         if (ind != NONE) {
00050           if (toupper(dms[beg - 1]) == toupper(dms[end - 1]))
00051             throw out_of_range("Repeated hemisphere indicators "
00052                                + str(dms[beg - 1]) + " in "
00053                                + dms.substr(beg - 1, end - beg + 1));
00054           else
00055             throw out_of_range("Contradictory hemisphere indicators "
00056                                + str(dms[beg - 1]) + " and "
00057                                + str(dms[end - 1]) + " in "
00058                                + dms.substr(beg - 1, end - beg + 1));
00059         }
00060         ind = (k / 2) ? LONGITUDE : LATITUDE;
00061         sign = k % 2 ? 1 : -1;
00062         --end;
00063       }
00064     }
00065     if (end > beg && (k = lookup(signs, dms[beg])) >= 0) {
00066       if (k >= 0) {
00067         sign *= k ? 1 : -1;
00068         ++beg;
00069       }
00070     }
00071     if (end == beg)
00072       throw out_of_range("Empty or incomplete DMS string " + dms);
00073     double ipieces[] = {0, 0, 0};
00074     double fpieces[] = {0, 0, 0};
00075     unsigned npiece = 0;
00076     double icurrent = 0;
00077     double fcurrent = 0;
00078     unsigned ncurrent = 0, p = beg;
00079     bool pointseen = false;
00080     unsigned digcount = 0;
00081     while (p < end) {
00082       char x = dms[p++];
00083       if ((k = lookup(digits, x)) >= 0) {
00084         ++ncurrent;
00085         if (digcount > 0)
00086           ++digcount;           // Count of decimal digits
00087         else
00088           icurrent = 10 * icurrent + k;
00089       } else if (x == '.') {
00090         if (pointseen)
00091           throw out_of_range("Multiple decimal points in "
00092                              + dms.substr(beg, end - beg));
00093         pointseen = true;
00094         digcount = 1;
00095       } else if ((k = lookup(dmsindicators, x)) >= 0) {
00096         if (unsigned(k) == npiece - 1)
00097           throw out_of_range("Repeated " + components[k]
00098                              + " component in "
00099                              + dms.substr(beg, end - beg));
00100         else if (unsigned(k) < npiece)
00101           throw out_of_range(components[k] + " component follows "
00102                              + components[npiece - 1] + " component in "
00103                              + dms.substr(beg, end - beg));
00104         if (ncurrent == 0)
00105           throw out_of_range("Missing numbers in " + components[k]
00106                              + " component of "
00107                              + dms.substr(beg, end - beg));
00108         if (digcount > 1) {
00109           istringstream s(dms.substr(p-digcount-1, digcount));
00110           s >> fcurrent;
00111         }
00112         ipieces[k] = icurrent;
00113         fpieces[k] = icurrent + fcurrent;
00114         if (p < end) {
00115           npiece = k + 1;
00116           icurrent = fcurrent = 0;
00117           ncurrent = digcount = 0;
00118         }
00119       } else if (lookup(signs, x) >= 0)
00120         throw out_of_range("Internal sign in DMS string "
00121                            + dms.substr(beg, end - beg));
00122       else
00123         throw out_of_range("Illegal character " + str(x)
00124                            + " in DMS string "
00125                            + dms.substr(beg, end - beg));
00126     }
00127     if (lookup(dmsindicators, dms[p - 1]) < 0) {
00128       if (npiece >= 3)
00129         throw out_of_range("Extra text following seconds in DMS string "
00130                            + dms.substr(beg, end - beg));
00131       if (ncurrent == 0)
00132         throw out_of_range("Missing numbers in " + components[k]
00133                            + " component of "
00134                            + dms.substr(beg, end - beg));
00135       if (digcount > 1) {
00136         istringstream s(dms.substr(p - digcount, digcount));
00137         s >> fcurrent;
00138       }
00139       ipieces[npiece] = icurrent;
00140       fpieces[npiece] = icurrent + fcurrent;
00141     }
00142     if (pointseen && digcount == 0)
00143       throw out_of_range("Decimal point in non-terminal component of "
00144                          + dms.substr(beg, end - beg));
00145     // Note that we accept 59.999999... even though it rounds to 60.
00146     if (ipieces[1] >= 60)
00147       throw out_of_range("Minutes " + str(fpieces[1])
00148                          + " not in range [0, 60)");
00149     if (ipieces[2] >= 60)
00150       throw out_of_range("Seconds " + str(fpieces[2])
00151                          + " not in range [0, 60)");
00152     // Assume check on range of result is made by calling routine (which might
00153     // be able to offer a better diagnostic).
00154     return sign * (fpieces[0] + (fpieces[1] + fpieces[2] / 60) / 60);
00155   }
00156 
00157   void DMS::DecodeLatLon(const std::string& stra, const std::string& strb,
00158                          double& lat, double& lon) {
00159       double a, b;
00160       flag ia, ib;
00161       a = Decode(stra, ia);
00162       b = Decode(strb, ib);
00163       if (ia == NONE && ib == NONE) {
00164         // Default to lat, long
00165         ia = LATITUDE;
00166         ib = LONGITUDE;
00167       } else if (ia == NONE)
00168         ia = flag(LATITUDE + LONGITUDE - ib);
00169       else if (ib == NONE)
00170         ib = flag(LATITUDE + LONGITUDE - ia);
00171       if (ia == ib)
00172         throw out_of_range("Both " + stra + " and " + strb +
00173                            " interpreted as "
00174                            + (ia == LATITUDE ? "latitudes"
00175                               : "longitudes"));
00176       if (ia == LATITUDE) {
00177         lat = a; lon = b;
00178       } else {
00179         lat = b; lon = a;
00180       }
00181       if (! (lat >= -90 && lat <= 90))
00182         throw out_of_range("Latitude " + str(lat) +
00183                            "d not in [-90d, 90d]");
00184       if (! (lon >= -180 && lon <= 360))
00185         throw out_of_range("Latitude " + str(lon) +
00186                            "d not in [-180d, 360d]");
00187       if (lon >= 180)
00188         lon -= 360;
00189   }
00190 
00191   string DMS::Encode(double angle, component trailing, unsigned prec,
00192                      flag ind) {
00193     // Assume check on range of input angle has been made by calling
00194     // routine (which might be able to offer a better diagnostic).
00195     //
00196     // 15 - 2 * trailing = ceiling(log10(2^53/90/60^trailing)).
00197     // This suffices to give full double precision for numbers in [-90,90]
00198     prec = min(15 - 2 * unsigned(trailing), prec);
00199     double scale = 1;
00200     for (unsigned i = 0; i < unsigned(trailing); ++i)
00201       scale *= 60;
00202     for (unsigned i = 0; i < prec; ++i)
00203       scale *= 10;
00204     if (ind == AZIMUTH)
00205       angle -= floor(angle/360) * 360;
00206     int sign = angle < 0 ? -1 : 1;
00207     angle *= sign;
00208       
00209     // Break off integer part to preserve precision in manipulation of
00210     // fractional part.
00211     double
00212       idegree = floor(angle),
00213       fdegree = floor((angle - idegree) * scale + 0.5) / scale;
00214     if (fdegree >= 1) {
00215       idegree += 1;
00216       fdegree -= 1;
00217     }
00218     double pieces[3] = {fdegree, 0, 0};
00219     for (unsigned i = 1; i <= unsigned(trailing); ++i) {
00220       double
00221         ip = floor(pieces[i - 1]),
00222         fp = pieces[i - 1] - ip;
00223       pieces[i] = fp * 60;
00224       pieces[i - 1] = ip;
00225     }
00226     pieces[0] += idegree;
00227     ostringstream s;
00228     s << fixed  << setfill('0');
00229     if (ind == NONE && sign < 0)
00230       s << '-';
00231     switch (trailing) {
00232     case DEGREE:
00233       if (ind != NONE)
00234         s << setw(1 + min(int(ind), 2) + prec + (prec ? 1 : 0));
00235       s << setprecision(prec) << pieces[0];
00236       break;
00237     default:
00238       if (ind != NONE)
00239         s << setw(1 + min(int(ind), 2));
00240       s << setprecision(0) << pieces[0] << char(tolower(dmsindicators[0]));
00241       switch (trailing) {
00242       case MINUTE:
00243         s << setw(2 + prec + (prec ? 1 : 0)) << setprecision(prec)
00244           << pieces[1] <<  char(tolower(dmsindicators[1]));
00245         break;
00246       case SECOND:
00247         s << setw(2) << pieces[1] <<  char(tolower(dmsindicators[1]))
00248           << setw(2 + prec + (prec ? 1 : 0)) << setprecision(prec)
00249           << pieces[2] <<  char(tolower(dmsindicators[2]));
00250         break;
00251       default:
00252         break;
00253       }
00254     }
00255     if (ind != NONE && ind != AZIMUTH)
00256       s << hemispheres[(ind == LATITUDE ? 0 : 2) + (sign < 0 ? 0 : 1)];
00257     return s.str();
00258   }
00259 
00260 } // namespace GeographicLib