00001
00002
00003
00004
00005
00006
00007
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;
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
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
00147
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
00157
00158
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
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
00222
00223
00224
00225
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
00238
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
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 }