GeographicLib
1.21
|
00001 /** 00002 * \file DMS.hpp 00003 * \brief Header for GeographicLib::DMS class 00004 * 00005 * Copyright (c) Charles Karney (2008-2011) <charles@karney.com> and licensed 00006 * under the MIT/X11 License. For more information, see 00007 * http://geographiclib.sourceforge.net/ 00008 **********************************************************************/ 00009 00010 #if !defined(GEOGRAPHICLIB_DMS_HPP) 00011 #define GEOGRAPHICLIB_DMS_HPP "$Id: 67770a78c105495a31a9d3755c811e938729c85a $" 00012 00013 #include <sstream> 00014 #include <iomanip> 00015 #include <GeographicLib/Constants.hpp> 00016 #include <GeographicLib/Utility.hpp> 00017 00018 #if defined(_MSC_VER) 00019 // Squelch warnings about dll vs string 00020 #pragma warning (push) 00021 #pragma warning (disable: 4251) 00022 #endif 00023 00024 namespace GeographicLib { 00025 00026 /** 00027 * \brief Convert between degrees and %DMS representation. 00028 * 00029 * Parse a string representing degree, minutes, and seconds and return the 00030 * angle in degrees and format an angle in degrees as degree, minutes, and 00031 * seconds. In addition, handle NANs and infinities on input and output. 00032 * 00033 * Example of use: 00034 * \include example-DMS.cpp 00035 **********************************************************************/ 00036 class GEOGRAPHIC_EXPORT DMS { 00037 private: 00038 typedef Math::real real; 00039 // Replace all occurrences of pat by c 00040 static void replace(std::string& s, const std::string& pat, char c) { 00041 std::string::size_type p = 0; 00042 while (true) { 00043 p = s.find(pat, p); 00044 if (p == std::string::npos) 00045 break; 00046 s.replace(p, pat.length(), 1, c); 00047 } 00048 } 00049 static const std::string hemispheres_; 00050 static const std::string signs_; 00051 static const std::string digits_; 00052 static const std::string dmsindicators_; 00053 static const std::string components_[3]; 00054 static Math::real NumMatch(const std::string& s); 00055 DMS(); // Disable constructor 00056 00057 public: 00058 00059 /** 00060 * Indicator for presence of hemisphere indicator (N/S/E/W) on latitudes 00061 * and longitudes. 00062 **********************************************************************/ 00063 enum flag { 00064 /** 00065 * No indicator present. 00066 * @hideinitializer 00067 **********************************************************************/ 00068 NONE = 0, 00069 /** 00070 * Latitude indicator (N/S) present. 00071 * @hideinitializer 00072 **********************************************************************/ 00073 LATITUDE = 1, 00074 /** 00075 * Longitude indicator (E/W) present. 00076 * @hideinitializer 00077 **********************************************************************/ 00078 LONGITUDE = 2, 00079 /** 00080 * Used in Encode to indicate output of an azimuth in [000, 360) with no 00081 * letter indicator. 00082 * @hideinitializer 00083 **********************************************************************/ 00084 AZIMUTH = 3, 00085 /** 00086 * Used in Encode to indicate output of a plain number. 00087 * @hideinitializer 00088 **********************************************************************/ 00089 NUMBER = 4, 00090 }; 00091 00092 /** 00093 * Indicator for trailing units on an angle. 00094 **********************************************************************/ 00095 enum component { 00096 /** 00097 * Trailing unit is degrees. 00098 * @hideinitializer 00099 **********************************************************************/ 00100 DEGREE = 0, 00101 /** 00102 * Trailing unit is arc minutes. 00103 * @hideinitializer 00104 **********************************************************************/ 00105 MINUTE = 1, 00106 /** 00107 * Trailing unit is arc seconds. 00108 * @hideinitializer 00109 **********************************************************************/ 00110 SECOND = 2, 00111 }; 00112 00113 /** 00114 * Convert a string in DMS to an angle. 00115 * 00116 * @param[in] dms string input. 00117 * @param[out] ind a DMS::flag value signaling the presence of a 00118 * hemisphere indicator. 00119 * @return angle (degrees). 00120 * 00121 * Degrees, minutes, and seconds are indicated by the characters d, ' 00122 * (single quote), " (double quote), and these components may only be 00123 * given in this order. Any (but not all) components may be omitted and 00124 * other symbols (e.g., the <sup>o</sup> symbol for degrees and the unicode 00125 * prime and double prime symbols for minutes and seconds) may be 00126 * substituted. The last component indicator may be omitted and is assumed 00127 * to be the next smallest unit (thus 33d10 is interpreted as 33d10'). The 00128 * final component may be a decimal fraction but the non-final components 00129 * must be integers. Instead of using d, ', and " to indicate 00130 * degrees, minutes, and seconds, : (colon) may be used to <i>separate</i> 00131 * these components (numbers must appear before and after each colon); thus 00132 * 50d30'10.3" may be written as 50:30:10.3, 5.5' may be written 00133 * 0:5.5, and so on. The integer parts of the minutes and seconds 00134 * components must be less than 60. A single leading sign is permitted. A 00135 * hemisphere designator (N, E, W, S) may be added to the beginning or end 00136 * of the string. The result is multiplied by the implied sign of the 00137 * hemisphere designator (negative for S and W). In addition \e ind is set 00138 * to DMS::LATITUDE if N or S is present, to DMS::LONGITUDE if E or W is 00139 * present, and to DMS::NONE otherwise. Throws an error on a malformed 00140 * string. No check is performed on the range of the result. Examples of 00141 * legal and illegal strings are 00142 * - <i>LEGAL</i> (all the entries on each line are equivalent) 00143 * - -20.51125, 20d30'40.5"S, -20d30'40.5, -20d30.675, 00144 * N-20d30'40.5", -20:30:40.5 00145 * - 4d0'9, 4d9", 4d9'', 4:0:9, 004:00:09, 4.0025, 4.0025d, 4d0.15, 00146 * 04:.15 00147 * - <i>ILLEGAL</i> (the exception thrown explains the problem) 00148 * - 4d5"4', 4::5, 4:5:, :4:5, 4d4.5'4", -N20.5, 1.8e2d, 4:60, 00149 * 4d-5' 00150 * 00151 * <b>NOTE:</b> At present, all the string handling in the C++ 00152 * implementation %GeographicLib is with 8-bit characters. The support for 00153 * unicode symbols for degrees, minutes, and seconds is therefore via the 00154 * <a href="http://en.wikipedia.org/wiki/UTF-8">UTF-8</a> encoding. (The 00155 * Javascript implementation of this class uses unicode natively, of 00156 * course.) 00157 * 00158 * Here is the list of Unicode symbols supported for degrees, minutes, 00159 * seconds: 00160 * - degrees: 00161 * - d, D lower and upper case letters 00162 * - U+00b0 degree symbol 00163 * - U+00ba masculine ordinal indicator 00164 * - U+2070 superscript zero 00165 * - minutes: 00166 * - ' apostrophe 00167 * - U+2032 prime 00168 * - U+00b4 acute accent 00169 * - seconds: 00170 * - " quotation mark 00171 * - U+2033 double prime 00172 * - ' ' any two consecutive symbols for minutes 00173 * . 00174 * The codes with a leading zero byte, e.g., U+00b0, are accepted in their 00175 * UTF-8 coded form 0xc2 0xb0 and as a single byte 0xb0. 00176 **********************************************************************/ 00177 static Math::real Decode(const std::string& dms, flag& ind); 00178 00179 /** 00180 * Convert DMS to an angle. 00181 * 00182 * @param[in] d degrees. 00183 * @param[in] m arc minutes. 00184 * @param[in] s arc seconds. 00185 * @return angle (degrees) 00186 * 00187 * This does not propagate the sign on \e d to the other components, so 00188 * -3d20' would need to be represented as - DMS::Decode(3.0, 20.0) or 00189 * DMS::Decode(-3.0, -20.0). 00190 **********************************************************************/ 00191 static Math::real Decode(real d, real m = 0, real s = 0) throw() 00192 { return d + (m + s/real(60))/real(60); } 00193 00194 /// \cond SKIP 00195 /** 00196 * <b>DEPRECATED</b> (use Utility::num, instead). 00197 * Convert a string to a real number. 00198 * 00199 * @param[in] str string input. 00200 * @return decoded number. 00201 **********************************************************************/ 00202 static Math::real Decode(const std::string& str) 00203 { return Utility::num<real>(str); } 00204 00205 /** 00206 * <b>DEPRECATED</b> (use Utility::fract, instead). 00207 * Convert a string to a real number treating the case where the string is 00208 * a simple fraction. 00209 * 00210 * @param[in] str string input. 00211 * @return decoded number. 00212 **********************************************************************/ 00213 static Math::real DecodeFraction(const std::string& str) 00214 { return Utility::fract<real>(str); } 00215 /// \endcond 00216 00217 /** 00218 * Convert a pair of strings to latitude and longitude. 00219 * 00220 * @param[in] dmsa first string. 00221 * @param[in] dmsb second string. 00222 * @param[out] lat latitude. 00223 * @param[out] lon longitude. 00224 * @param[in] swaplatlong if true assume longitude is given before latitude 00225 * in the absence of hemisphere designators (default false). 00226 * 00227 * By default, the \e lat (resp., \e lon) is assigned to the results of 00228 * decoding \e dmsa (resp., \e dmsb). However this is overridden if either 00229 * \e dmsa or \e dmsb contain a latitude or longitude hemisphere designator 00230 * (N, S, E, W). Throws an error if the decoded numbers are out of the 00231 * ranges [-90<sup>o</sup>, 90<sup>o</sup>] for latitude and 00232 * [-180<sup>o</sup>, 360<sup>o</sup>] for longitude and, in which case \e 00233 * lat and \e lon are unchanged. Finally the longitude is reduced to the 00234 * range [-180<sup>o</sup>, 180<sup>o</sup>). 00235 **********************************************************************/ 00236 static void DecodeLatLon(const std::string& dmsa, const std::string& dmsb, 00237 real& lat, real& lon, bool swaplatlong = false); 00238 00239 /** 00240 * Convert a string to an angle in degrees. 00241 * 00242 * @param[in] angstr input string. 00243 * @return angle (degrees) 00244 * 00245 * No hemisphere designator is allowed and no check is done on the range of 00246 * the result. 00247 **********************************************************************/ 00248 static Math::real DecodeAngle(const std::string& angstr); 00249 00250 /** 00251 * Convert a string to an azimuth in degrees. 00252 * 00253 * @param[in] azistr input string. 00254 * @return azimuth (degrees) 00255 * 00256 * A hemisphere designator E/W can be used; the result is multiplied by -1 00257 * if W is present. Throws an error if the result is out of the range 00258 * [-180<sup>o</sup>, 360<sup>o</sup>]. Finally the azimuth is reduced to 00259 * the range [-180<sup>o</sup>, 180<sup>o</sup>). 00260 **********************************************************************/ 00261 static Math::real DecodeAzimuth(const std::string& azistr); 00262 00263 /** 00264 * Convert angle (in degrees) into a DMS string (using d, ', and "). 00265 * 00266 * @param[in] angle input angle (degrees) 00267 * @param[in] trailing DMS::component value indicating the trailing units 00268 * on the string and this is given as a decimal number if necessary. 00269 * @param[in] prec the number of digits after the decimal point for the 00270 * trailing component. 00271 * @param[in] ind DMS::flag value indicated additional formatting. 00272 * @param[in] dmssep if non-null, use as the DMS separator character 00273 * (instead of d, ', " delimiters). 00274 * @return formatted string 00275 * 00276 * The interpretation of \e ind is as follows: 00277 * - ind == DMS::NONE, signed result no leading zeros on degrees except in 00278 * the units place, e.g., -8d03'. 00279 * - ind == DMS::LATITUDE, trailing N or S hemisphere designator, no sign, 00280 * pad degrees to 2 digits, e.g., 08d03'S. 00281 * - ind == DMS::LONGITUDE, trailing E or W hemisphere designator, no 00282 * sign, pad degrees to 3 digits, e.g., 008d03'W. 00283 * - ind == DMS::AZIMUTH, convert to the range [0, 360<sup>o</sup>), no 00284 * sign, pad degrees to 3 digits, , e.g., 351d57'. 00285 * . 00286 * The integer parts of the minutes and seconds components are always given 00287 * with 2 digits. 00288 **********************************************************************/ 00289 static std::string Encode(real angle, component trailing, unsigned prec, 00290 flag ind, char dmssep); 00291 00292 /** 00293 * Convert angle (in degrees) into a DMS string (using d, ', and "). 00294 * 00295 * @param[in] angle input angle (degrees) 00296 * @param[in] trailing DMS::component value indicating the trailing units 00297 * on the string and this is given as a decimal number if necessary. 00298 * @param[in] prec the number of digits after the decimal point for the 00299 * trailing component. 00300 * @param[in] ind DMS::flag value indicated additional formatting. 00301 * @return formatted string 00302 * 00303 * <b>COMPATIBILITY NOTE:</b> This function calls 00304 * Encode(real, component, unsigned, flag, char) with a 5th 00305 * argument of char(0). At some point, 00306 * Encode(real, component, unsigned, flag) and will be withdrawn 00307 * and the interface to 00308 * Encode(real, component, unsigned, flag, char) changed so that 00309 * its 4th and 5th arguments have default values. This will 00310 * preserve source-level compatibility. 00311 **********************************************************************/ 00312 static std::string Encode(real angle, component trailing, unsigned prec, 00313 flag ind = NONE); 00314 00315 /** 00316 * Convert angle into a DMS string (using d, ', and ") selecting the 00317 * trailing component based on the precision. 00318 * 00319 * @param[in] angle input angle (degrees) 00320 * @param[in] prec the precision relative to 1 degree. 00321 * @param[in] ind DMS::flag value indicated additional formatting. 00322 * @param[in] dmssep if non-null, use as the DMS separator character 00323 * (instead of d, ', " delimiters). 00324 * @return formatted string 00325 * 00326 * \e prec indicates the precision relative to 1 degree, e.g., \e prec = 3 00327 * gives a result accurate to 0.1' and \e prec = 4 gives a result accurate 00328 * to 1". \e ind is interpreted as in DMS::Encode with the additional 00329 * facility that DMS::NUMBER represents \e angle as a number in fixed 00330 * format with precision \e prec. 00331 **********************************************************************/ 00332 static std::string Encode(real angle, unsigned prec, flag ind = NONE, 00333 char dmssep = char(0)) { 00334 return ind == NUMBER ? Utility::str<real>(angle, int(prec)) : 00335 Encode(angle, 00336 prec < 2 ? DEGREE : (prec < 4 ? MINUTE : SECOND), 00337 prec < 2 ? prec : (prec < 4 ? prec - 2 : prec - 4), 00338 ind, dmssep); 00339 } 00340 00341 /** 00342 * Split angle into degrees and minutes 00343 * 00344 * @param[in] ang angle (degrees) 00345 * @param[out] d degrees (an integer returned as a real) 00346 * @param[out] m arc minutes. 00347 **********************************************************************/ 00348 static void Encode(real ang, real& d, real& m) throw() { 00349 d = int(ang); m = 60 * (ang - d); 00350 } 00351 00352 /** 00353 * Split angle into degrees and minutes and seconds. 00354 * 00355 * @param[in] ang angle (degrees) 00356 * @param[out] d degrees (an integer returned as a real) 00357 * @param[out] m arc minutes (an integer returned as a real) 00358 * @param[out] s arc seconds. 00359 **********************************************************************/ 00360 static void Encode(real ang, real& d, real& m, real& s) throw() { 00361 d = int(ang); ang = 60 * (ang - d); 00362 m = int(ang); s = 60 * (ang - m); 00363 } 00364 00365 }; 00366 00367 } // namespace GeographicLib 00368 00369 #if defined(_MSC_VER) 00370 #pragma warning (pop) 00371 #endif 00372 00373 #endif // GEOGRAPHICLIB_DMS_HPP