00001 /** 00002 * \file DMS.hpp 00003 * \brief Header for GeographicLib::DMS class 00004 * 00005 * Copyright (c) Charles Karney (2008, 2009, 2010, 2011) <charles@karney.com> 00006 * and licensed under the LGPL. For more information, see 00007 * http://geographiclib.sourceforge.net/ 00008 **********************************************************************/ 00009 00010 #if !defined(GEOGRAPHICLIB_DMS_HPP) 00011 #define GEOGRAPHICLIB_DMS_HPP "$Id: DMS.hpp 6954 2011-02-16 14:36:56Z karney $" 00012 00013 #include "GeographicLib/Constants.hpp" 00014 #include <sstream> 00015 #include <iomanip> 00016 00017 namespace GeographicLib { 00018 00019 /** 00020 * \brief Convert between degrees and %DMS representation. 00021 * 00022 * Parse a string representing degree, minutes, and seconds and return the 00023 * angle in degrees and format an angle in degrees as degree, minutes, and 00024 * seconds. 00025 **********************************************************************/ 00026 class DMS { 00027 private: 00028 typedef Math::real real; 00029 static int lookup(const std::string& s, char c) throw() { 00030 std::string::size_type r = s.find(toupper(c)); 00031 return r == std::string::npos ? -1 : int(r); 00032 } 00033 template<typename T> static std::string str(T x) { 00034 std::ostringstream s; s << x; return s.str(); 00035 } 00036 static const std::string hemispheres; 00037 static const std::string signs; 00038 static const std::string digits; 00039 static const std::string dmsindicators; 00040 static const std::string components[3]; 00041 static Math::real NumMatch(const std::string& s); 00042 DMS(); // Disable constructor 00043 00044 public: 00045 00046 /** 00047 * Indicator for presence of hemisphere indicator (N/S/E/W) on latitudes 00048 * and longitudes. 00049 **********************************************************************/ 00050 enum flag { 00051 /** 00052 * No indicator present. 00053 * @hideinitializer 00054 **********************************************************************/ 00055 NONE = 0, 00056 /** 00057 * Latitude indicator (N/S) present. 00058 * @hideinitializer 00059 **********************************************************************/ 00060 LATITUDE = 1, 00061 /** 00062 * Longitude indicator (E/W) present. 00063 * @hideinitializer 00064 **********************************************************************/ 00065 LONGITUDE = 2, 00066 /** 00067 * Used in Encode to indicate output of an azimuth in [000, 360) with no 00068 * letter indicator. 00069 * @hideinitializer 00070 **********************************************************************/ 00071 AZIMUTH = 3, 00072 /** 00073 * Used in Encode to indicate output of a plain number. 00074 * @hideinitializer 00075 **********************************************************************/ 00076 NUMBER = 4, 00077 }; 00078 00079 /** 00080 * Indicator for trailing units on an angle. 00081 **********************************************************************/ 00082 enum component { 00083 /** 00084 * Trailing unit is degrees. 00085 * @hideinitializer 00086 **********************************************************************/ 00087 DEGREE = 0, 00088 /** 00089 * Trailing unit is arc minutes. 00090 * @hideinitializer 00091 **********************************************************************/ 00092 MINUTE = 1, 00093 /** 00094 * Trailing unit is arc seconds. 00095 * @hideinitializer 00096 **********************************************************************/ 00097 SECOND = 2, 00098 }; 00099 00100 /** 00101 * Convert a string in DMS to an angle. 00102 * 00103 * @param[in] dms string input. 00104 * @param[out] ind a DMS::flag value indicating the presence of a 00105 * hemisphere indicator. 00106 * @return angle (degrees). 00107 * 00108 * Degrees, minutes, and seconds are indicated by the letters d, ', ", 00109 * and these components may only be given in this order. Any (but not all) 00110 * components may be omitted. The last component indicator may be omitted 00111 * and is assumed to be tbe next smallest unit (thus 33d10 is interpreted 00112 * as 33d10'). The final component may be a decimal fraction but the 00113 * non-final components must be integers. The integer parts of the minutes 00114 * and seconds components must be less than 60. A single leading sign is 00115 * permitted. A hemisphere designator (N, E, W, S) may be added to tbe 00116 * beginning or end of the string. The result is multiplied by the implied 00117 * signed of the hemisphere designator (negative for S and W). In addition 00118 * \e ind is set to DMS::LATITUDE if N or S is present, to DMS::LONGITUDE 00119 * if E or W is present, and to DMS::NONE otherwise. Throws an error on a 00120 * malformed string. No check is performed on the range of the result. 00121 **********************************************************************/ 00122 static Math::real Decode(const std::string& dms, flag& ind); 00123 00124 /** 00125 * Convert DMS to an angle. 00126 * 00127 * @param[in] d degrees. 00128 * @param[in] m arc minutes. 00129 * @param[in] s arc seconds. 00130 * @return angle (degrees) 00131 * 00132 * This does not propagate the sign on \e d to the other components, so 00133 * -3d20' would need to be represented as - DMS::Decode(3.0, 20.0) or 00134 * DMS::Decode(-3.0, -20.0). 00135 **********************************************************************/ 00136 static Math::real Decode(real d, real m = 0, real s = 0) throw() 00137 { return d + (m + s/real(60))/real(60); } 00138 00139 /** 00140 * Convert a string to a real number. 00141 * 00142 * @param[in] str string input. 00143 * @return decoded number. 00144 **********************************************************************/ 00145 static Math::real Decode(const std::string& str); 00146 00147 /** 00148 * Convert a pait of strings to latitude and longitude. 00149 * 00150 * @param[in] dmsa first string. 00151 * @param[in] dmsb second string. 00152 * @param[out] lat latitude. 00153 * @param[out] lon longitude. 00154 * 00155 * By default, the \e lat (resp., \e lon) is assigned to the results of 00156 * decoding \e dmsa (resp., \e dmsb). However this is overridden if either 00157 * \e dmsa or \e dmsb contain a latitude or longitude hemisphere designator 00158 * (N, S, E, W). Throws an error if the decoded numbers are out of the 00159 * ranges [-90<sup>o</sup>, 90<sup>o</sup>] for latitude and 00160 * [-180<sup>o</sup>, 360<sup>o</sup>] for longitude and, in which case \e 00161 * lat and \e lon are unchanged. Finally the longitude is reduced to the 00162 * range [-180<sup>o</sup>, 180<sup>o</sup>). 00163 **********************************************************************/ 00164 static void DecodeLatLon(const std::string& dmsa, const std::string& dmsb, 00165 real& lat, real& lon); 00166 00167 /** 00168 * Convert a string to an angle in degrees. 00169 * 00170 * @param[in] angstr input string. 00171 * @return angle (degrees) 00172 * 00173 * No hemisphere designator is allowed and no check is done on the range of 00174 * the result. 00175 **********************************************************************/ 00176 static Math::real DecodeAngle(const std::string& angstr); 00177 00178 /** 00179 * Convert a string to an azimuth in degrees. 00180 * 00181 * @param[in] azistr input string. 00182 * @return azimuth (degrees) 00183 * 00184 * A hemisphere designator E/W can be used; the result is multiplied by -1 00185 * if W is present. Throws an error if the result is out of the range 00186 * [-180<sup>o</sup>, 360<sup>o</sup>]. Finally the azimuth is reduced to 00187 * the range [-180<sup>o</sup>, 180<sup>o</sup>). 00188 **********************************************************************/ 00189 static Math::real DecodeAzimuth(const std::string& azistr); 00190 00191 /** 00192 * Convert angle (in degrees) into a DMS string. 00193 * 00194 * @param[in] angle input angle (degrees) 00195 * @param[in] trailing DMS::component value indicating the trailing units 00196 * on the string and this is given as a decimal number if necessary. 00197 * @param[in] prec the number of digits after the decimal point for the 00198 * trailing component. 00199 * @param[in] ind DMS::flag value indicated additional formatting. 00200 * @return formatted string 00201 * 00202 * The interpretation of \e ind is as follows: 00203 * - ind == DMS::NONE, signed result no leading zeros on degrees except in 00204 * the units place, e.g., -8d03'. 00205 * - ind == DMS::LATITUDE, trailing N or S hemisphere designator, no sign, 00206 * pad degrees to 2 digits, e.g., 08d03'S. 00207 * - ind == DMS::LONGITUDE, trailing E or W hemisphere designator, no 00208 * sign, pad degrees to 3 digits, e.g., 008d03'W. 00209 * - ind == DMS::AZIMUTH, convert to the range [0, 360<sup>o</sup>), no 00210 * sign, pad degrees to 3 digits, , e.g., 351d57'. 00211 * . 00212 * The integer parts of the minutes and seconds components are always given 00213 * with 2 digits. 00214 **********************************************************************/ 00215 static std::string Encode(real angle, component trailing, unsigned prec, 00216 flag ind = NONE); 00217 00218 /** 00219 * Convert angle into a DMS string selecting the trailing component 00220 * based on the precision. 00221 * 00222 * @param[in] angle input angle (degrees) 00223 * @param[in] prec the precision relative to 1 degree. 00224 * @param[in] ind DMS::flag value indicated additional formatting. 00225 * @return formatted string 00226 * 00227 * \e prec indicates the precision relative to 1 degree, e.g., \e prec = 3 00228 * gives a result accurate to 0.1' and \e prec = 4 gives a result accurate 00229 * to 1". \e ind is interpreted as in DMS::Encode with the additional 00230 * facility at DMS::NUMBER treats \e angle a number in fixed format with 00231 * precision \e prec. 00232 **********************************************************************/ 00233 static std::string Encode(real angle, unsigned prec, flag ind = NONE) { 00234 if (ind == NUMBER) { 00235 if (!Math::isfinite(angle)) 00236 return angle < 0 ? std::string("-inf") : 00237 (angle > 0 ? std::string("inf") : std::string("nan")); 00238 std::ostringstream s; 00239 s << std::fixed << std::setprecision(prec) << angle; 00240 return s.str(); 00241 } else 00242 return Encode(angle, 00243 prec < 2 ? DEGREE : (prec < 4 ? MINUTE : SECOND), 00244 prec < 2 ? prec : (prec < 4 ? prec - 2 : prec - 4), 00245 ind); 00246 } 00247 00248 /** 00249 * Split angle into degrees and mintues 00250 * 00251 * @param[in] ang angle (degrees) 00252 * @param[out] d degrees (an integer returned as a real) 00253 * @param[out] m arc minutes. 00254 **********************************************************************/ 00255 static void Encode(real ang, real& d, real& m) throw() { 00256 d = int(ang); m = 60 * (ang - d); 00257 } 00258 00259 /** 00260 * Split angle into degrees and mintues and seconds. 00261 * 00262 * @param[in] ang angle (degrees) 00263 * @param[out] d degrees (an integer returned as a real) 00264 * @param[out] m arc minutes (an integer returned as a real) 00265 * @param[out] s arc seconds. 00266 **********************************************************************/ 00267 static void Encode(real ang, real& d, real& m, real& s) throw() { 00268 d = int(ang); ang = 60 * (ang - d); 00269 m = int(ang); s = 60 * (ang - m); 00270 } 00271 00272 }; 00273 00274 } // namespace GeographicLib 00275 00276 #endif