GeographicLib  1.21
DMS.hpp
Go to the documentation of this file.
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), &quot; (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 &quot; 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&quot; 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&quot;S, -20d30'40.5, -20d30.675,
00144      *     N-20d30'40.5&quot;, -20:30:40.5
00145      *   - 4d0'9, 4d9&quot;, 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&quot;4', 4::5, 4:5:, :4:5, 4d4.5'4&quot;, -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      *   - &quot; quotation mark
00171      *   - U+2033 double prime
00172      *   - '&nbsp;' 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 &quot;).
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, ', &quot; 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 &quot;).
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 &quot;) 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, ', &quot; 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&quot;.  \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