File indexing completed on 2024-04-21 03:42:14

0001 /*
0002     SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include "../nan.h"
0010 
0011 #include <QString>
0012 #include <QDataStream>
0013 
0014 #include <cmath>
0015 
0016 //#define COUNT_DMS_SINCOS_CALLS true
0017 //#define PROFILE_SINCOS true
0018 
0019 #ifdef PROFILE_SINCOS
0020 #include <ctime>
0021 #endif
0022 
0023 /** @class dms
0024  * @short An angle, stored as degrees, but expressible in many ways.
0025  * @author Jason Harris
0026  * @version 1.0
0027  *
0028  * dms encapsulates an angle.  The angle is stored as a double,
0029  * equal to the value of the angle in degrees.  Methods are available
0030  * for setting/getting the angle as a floating-point measured in
0031  * Degrees or Hours, or as integer triplets (degrees, arcminutes,
0032  * arcseconds or hours, minutes, seconds).  There is also a method
0033  * to set the angle according to a radian value, and to return the
0034  * angle expressed in radians.  Finally, a SinCos() method computes
0035  * the sin and cosine of the angle.
0036  */
0037 class dms
0038 {
0039   public:
0040     /** Default constructor. */
0041     dms()
0042         : D(NaN::d)
0043 #ifdef COUNT_DMS_SINCOS_CALLS
0044           ,
0045           m_sinCosCalled(false), m_sinDirty(true), m_cosDirty(true)
0046 #endif
0047     {
0048 #ifdef COUNT_DMS_SINCOS_CALLS
0049         ++dms_constructor_calls;
0050 #endif
0051     }
0052 
0053     /** Empty virtual destructor */
0054     virtual ~dms() = default;
0055 
0056     /** @short Set the floating-point value of the angle according to the four integer arguments.
0057          * @param d degree portion of angle (int).  Defaults to zero.
0058          * @param m arcminute portion of angle (int).  Defaults to zero.
0059          * @param s arcsecond portion of angle (int).  Defaults to zero.
0060          * @param ms arcsecond portion of angle (int).  Defaults to zero.
0061          */
0062     explicit dms(const int &d, const int &m = 0, const int &s = 0, const int &ms = 0)
0063 #ifdef COUNT_DMS_SINCOS_CALLS
0064         : m_sinCosCalled(false), m_sinDirty(true), m_cosDirty(true)
0065 #endif
0066     {
0067         dms::setD(d, m, s, ms);
0068 #ifdef COUNT_DMS_SINCOS_CALLS
0069         ++dms_constructor_calls;
0070 #endif
0071     }
0072 
0073     /** @short Construct an angle from a double value.
0074          *
0075          * Creates an angle whose value in Degrees is equal to the argument.
0076          * @param x angle expressed as a floating-point number (in degrees)
0077          */
0078     explicit dms(const double &x)
0079         : D(x)
0080 #ifdef COUNT_DMS_SINCOS_CALLS
0081           ,
0082           m_sinCosCalled(false), m_sinDirty(true), m_cosDirty(true)
0083 #endif
0084     {
0085 #ifdef COUNT_DMS_SINCOS_CALLS
0086         ++dms_constructor_calls;
0087 #endif
0088     }
0089 
0090     /** @short Construct an angle from a string representation.
0091          *
0092          * Attempt to create the angle according to the string argument.  If the string
0093          * cannot be parsed as an angle value, the angle is set to zero.
0094          *
0095          * @warning There is not an unambiguous notification that it failed to parse the string,
0096          * since the string could have been a valid representation of zero degrees.
0097          * If this is a concern, use the setFromString() function directly instead.
0098          *
0099          * @param s the string to parse as a dms value.
0100          * @param isDeg if true, value is in degrees; if false, value is in hours.
0101          * @sa setFromString()
0102          */
0103     explicit dms(const QString &s, bool isDeg = true)
0104 #ifdef COUNT_DMS_SINCOS_CALLS
0105         : m_sinCosCalled(false), m_sinDirty(true), m_cosDirty(true)
0106 #endif
0107     {
0108         setFromString(s, isDeg);
0109 #ifdef COUNT_DMS_SINCOS_CALLS
0110         ++dms_constructor_calls;
0111 #endif
0112     }
0113 
0114     /** @return integer degrees portion of the angle
0115          */
0116     inline int degree() const
0117     {
0118         if (std::isnan(D))
0119             return 0;
0120 
0121         return int(D);
0122     }
0123 
0124     /** @return integer arcminutes portion of the angle.
0125          * @note an arcminute is 1/60 degree.
0126          */
0127     int arcmin() const;
0128 
0129     /** @return integer arcseconds portion of the angle
0130          * @note an arcsecond is 1/60 arcmin, or 1/3600 degree.
0131          */
0132     int arcsec() const;
0133 
0134     /** @return integer milliarcseconds portion of the angle
0135          * @note a  milliarcsecond is 1/1000 arcsecond.
0136          */
0137     int marcsec() const;
0138 
0139     /** @return angle in degrees expressed as a double.
0140             */
0141     inline const double &Degrees() const { return D; }
0142 
0143     /** @return integer hours portion of the angle
0144          * @note an angle can be measured in degrees/arcminutes/arcseconds
0145          * or hours/minutes/seconds.  An hour is equal to 15 degrees.
0146          */
0147     inline int hour() const { return int(reduce().Degrees() / 15.0); }
0148 
0149     /** @return integer minutes portion of the angle
0150          * @note a minute is 1/60 hour (not the same as an arcminute)
0151          */
0152     int minute() const;
0153 
0154     /** @return integer seconds portion of the angle
0155          * @note a second is 1/3600 hour (not the same as an arcsecond)
0156          */
0157     int second() const;
0158 
0159     /** @return integer milliseconds portion of the angle
0160          * @note a millisecond is 1/1000 second (not the same as a milliarcsecond)
0161          */
0162     int msecond() const;
0163 
0164     /** @return angle in hours expressed as a double in the range 0 to 23.999...
0165          * @note an angle can be measured in degrees/arcminutes/arcseconds
0166          * or hours/minutes/seconds.  An hour is equal to 15 degrees.
0167          */
0168     inline double Hours() const { return reduce().Degrees() / 15.0; }
0169 
0170     /** @return angle in hours expressed as a double in the range -11.999 to 0 to 12.0
0171          * @note an angle can be measured in degrees/arcminutes/arcseconds
0172          * or hours/minutes/seconds.  An hour is equal to 15 degrees.
0173          */
0174     inline double HoursHa() const { return Hours() <= 12.0 ? Hours() : Hours() - 24.0; }
0175 
0176     /** Sets floating-point value of angle, in degrees.
0177          * @param x new angle (double)
0178          */
0179     inline virtual void setD(const double &x)
0180     {
0181 #ifdef COUNT_DMS_SINCOS_CALLS
0182         m_sinDirty = m_cosDirty = true;
0183 #endif
0184         D = x;
0185     }
0186 
0187     /** @short Sets floating-point value of angle, in degrees.
0188          *
0189          * This is an overloaded member function; it behaves essentially
0190          * like the above function.  The floating-point value of the angle
0191          * (D) is determined from the following formulae:
0192          *
0193          * \f$ fabs(D) = fabs(d) + \frac{(m + (s/60))}{60} \f$
0194          * \f$ sgn(D) = sgn(d) \f$
0195          *
0196          * @param d integer degrees portion of angle
0197          * @param m integer arcminutes portion of angle
0198          * @param s integer arcseconds portion of angle
0199          * @param ms integer arcseconds portion of angle
0200          */
0201     virtual void setD(const int &d, const int &m, const int &s, const int &ms = 0);
0202 
0203     /** @short Sets floating-point value of angle, in hours.
0204          *
0205          * Converts argument from hours to degrees, then
0206          * sets floating-point value of angle, in degrees.
0207          * @param x new angle, in hours (double)
0208          * @sa setD()
0209          */
0210     inline virtual void setH(const double &x)
0211     {
0212         dms::setD(x * 15.0);
0213 #ifdef COUNT_DMS_SINCOS_CALLS
0214         m_cosDirty = m_sinDirty = true;
0215 #endif
0216     }
0217 
0218     /** @short Sets floating-point value of angle, in hours.
0219          *
0220          * Converts argument values from hours to degrees, then
0221          * sets floating-point value of angle, in degrees.
0222          * This is an overloaded member function, provided for convenience.  It
0223          * behaves essentially like the above function.
0224          * @param h integer hours portion of angle
0225          * @param m integer minutes portion of angle
0226          * @param s integer seconds portion of angle
0227          * @param ms integer milliseconds portion of angle
0228          * @sa setD()
0229          */
0230     virtual void setH(const int &h, const int &m, const int &s, const int &ms = 0);
0231 
0232     /** @short Attempt to parse the string argument as a dms value, and set the dms object
0233          * accordingly.
0234          * @param s the string to be parsed as a dms value.  The string can be an int or
0235          * floating-point value, or a triplet of values (d/h, m, s) separated by spaces or colons.
0236          * @param isDeg if true, the value is in degrees.  Otherwise, it is in hours.
0237          * @return true if sting was parsed successfully.  Otherwise, set the dms value
0238          * to 0.0 and return false.
0239          */
0240     virtual bool setFromString(const QString &s, bool isDeg = true);
0241 
0242     /** @short Compute Sine and Cosine of the angle simultaneously.
0243          * On machines using glibc >= 2.1, calling SinCos() is somewhat faster
0244          * than calling sin() and cos() separately.
0245          * The values are returned through the arguments (passed by reference).
0246          *
0247          * @param s Sine of the angle
0248          * @param c Cosine of the angle
0249          * @sa sin() cos()
0250          */
0251     inline void SinCos(double &s, double &c) const;
0252 
0253     /** @short Compute the Angle's Sine.
0254          *
0255          * @return the Sine of the angle.
0256          * @sa cos()
0257          */
0258     double sin() const
0259     {
0260 #ifdef COUNT_DMS_SINCOS_CALLS
0261         if (!m_sinCosCalled)
0262         {
0263             m_sinCosCalled = true;
0264             ++dms_with_sincos_called;
0265         }
0266         if (m_sinDirty)
0267             m_sinDirty = false;
0268         else
0269             ++redundant_trig_function_calls;
0270         ++trig_function_calls;
0271 #endif
0272 #ifdef PROFILE_SINCOS
0273         std::clock_t start, stop;
0274         double s;
0275         start = std::clock();
0276         s     = ::sin(D * DegToRad);
0277         stop  = std::clock();
0278         seconds_in_trig += double(stop - start) / double(CLOCKS_PER_SEC);
0279         return s;
0280 #else
0281         return ::sin(D * DegToRad);
0282 #endif
0283     }
0284 
0285     /** @short Compute the Angle's Cosine.
0286          *
0287          * @return the Cosine of the angle.
0288          * @sa sin()
0289          */
0290     double cos() const
0291     {
0292 #ifdef COUNT_DMS_SINCOS_CALLS
0293         if (!m_sinCosCalled)
0294         {
0295             m_sinCosCalled = true;
0296             ++dms_with_sincos_called;
0297         }
0298         if (m_cosDirty)
0299             m_cosDirty = false;
0300         else
0301             ++redundant_trig_function_calls;
0302         ++trig_function_calls;
0303 #endif
0304 #ifdef PROFILE_SINCOS
0305         std::clock_t start, stop;
0306         double c;
0307         start = std::clock();
0308         c     = ::cos(D * DegToRad);
0309         stop  = std::clock();
0310         seconds_in_trig += double(stop - start) / double(CLOCKS_PER_SEC);
0311         return c;
0312 #else
0313         return ::cos(D * DegToRad);
0314 #endif
0315     }
0316 
0317     /**
0318      * @short Convenience method to return tangent of the angle
0319      */
0320     inline double tan() const { return sin()/cos(); }
0321 
0322     /** @short Express the angle in radians.
0323          * @return the angle in radians (double)
0324          */
0325     inline double radians() const { return D * DegToRad; }
0326 
0327     /** @short Set angle according to the argument, in radians.
0328          *
0329          * This function converts the argument to degrees, then sets the angle
0330          * with setD().
0331          * @param Rad an angle in radians
0332          */
0333     inline virtual void setRadians(const double &Rad)
0334     {
0335         dms::setD(Rad / DegToRad);
0336 #ifdef COUNT_DMS_SINCOS_CALLS
0337         m_cosDirty = m_sinDirty = true;
0338 #endif
0339     }
0340 
0341     /** return the equivalent angle between 0 and 360 degrees.
0342          * @warning does not change the value of the parent angle itself.
0343          */
0344     const dms reduce() const;
0345 
0346     /**
0347      * @brief deltaAngle Return the shortest difference (path) between this angle and the supplied angle. The range is normalized to [-180,+180]
0348      * @param angle Angle to subtract from current angle.
0349      * @return Normalized angle in the range [-180,+180]
0350      */
0351     const dms deltaAngle(dms angle) const;
0352 
0353     /**
0354          * @short an enum defining standard angle ranges
0355          */
0356     enum AngleRanges
0357     {
0358         ZERO_TO_2PI,
0359         MINUSPI_TO_PI
0360     };
0361 
0362     /**
0363          * @short Reduce _this_ angle to the given range
0364          */
0365     void reduceToRange(enum dms::AngleRanges range);
0366 
0367     /** @return a nicely-formatted string representation of the angle
0368          * in degrees, arcminutes, and arcseconds.
0369          * @param forceSign if @c true then adds '+' or '-' to the string
0370          * @param machineReadable uses a colon separator and produces +/-dd:mm:ss format instead
0371          * @param highPrecision adds milliseconds, if @c false the seconds will be shown as an integer
0372          */
0373     const QString toDMSString(const bool forceSign = false, const bool machineReadable = false, const bool highPrecision=false) const;
0374 
0375     /** @return a nicely-formatted string representation of the angle
0376          * in hours, minutes, and seconds.
0377          * @param machineReadable uses a colon separator and produces hh:mm:ss format instead
0378          * @param highPrecision adds milliseconds, if @c false the seconds will be shown as an integer
0379          */
0380     const QString toHMSString(const bool machineReadable = false, const bool highPrecision=false) const;
0381 
0382     /** PI is a const static member; it's public so that it can be used anywhere,
0383          * as long as dms.h is included.
0384          */
0385     static constexpr double PI = { M_PI };
0386 
0387     /** DegToRad is a const static member equal to the number of radians in
0388          * one degree (dms::PI/180.0).
0389          */
0390     static constexpr double DegToRad = { M_PI / 180.0 };
0391 
0392     /** @short Static function to create a DMS object from a QString.
0393          *
0394          * There are several ways to specify the angle:
0395          * @li Integer numbers  ( 5 or -33 )
0396          * @li Floating-point numbers  ( 5.0 or -33.0 )
0397          * @li colon-delimited integers ( 5:0:0 or -33:0:0 )
0398          * @li colon-delimited with float seconds ( 5:0:0.0 or -33:0:0.0 )
0399          * @li colon-delimited with float minutes ( 5:0.0 or -33:0.0 )
0400          * @li space-delimited ( 5 0 0; -33 0 0 ) or ( 5 0.0 or -33 0.0 )
0401          * @li space-delimited, with unit labels ( 5h 0m 0s or -33d 0m 0s )
0402          * @param s the string to be parsed as an angle value
0403          * @param deg if true, s is expressed in degrees; if false, s is expressed in hours
0404          * @return a dms object whose value is parsed from the string argument
0405          */
0406     static dms fromString(const QString &s, bool deg);
0407 
0408     inline dms operator-() { return dms(-D); }
0409 #ifdef COUNT_DMS_SINCOS_CALLS
0410     static long unsigned dms_constructor_calls; // counts number of DMS constructor calls
0411     static long unsigned dms_with_sincos_called;
0412     static long unsigned trig_function_calls;           // total number of trig function calls
0413     static long unsigned redundant_trig_function_calls; // counts number of redundant trig function calls
0414     static double seconds_in_trig;                      // accumulates number of seconds spent in trig function calls
0415 #endif
0416 
0417   protected:
0418     double D;
0419 
0420   private:
0421 #ifdef COUNT_DMS_SINCOS_CALLS
0422     mutable bool m_sinDirty, m_cosDirty, m_sinCosCalled;
0423 #endif
0424 
0425     friend dms operator+(dms, dms);
0426     friend dms operator-(dms, dms);
0427     friend QDataStream &operator<<(QDataStream &out, const dms &d);
0428     friend QDataStream &operator>>(QDataStream &in, dms &d);
0429 };
0430 
0431 /// Add two angles
0432 inline dms operator+(dms a, dms b)
0433 {
0434     return dms(a.D + b.D);
0435 }
0436 
0437 /// Subtract angles
0438 inline dms operator-(dms a, dms b)
0439 {
0440     return dms(a.D - b.D);
0441 }
0442 
0443 // Inline sincos
0444 inline void dms::SinCos(double &s, double &c) const
0445 {
0446 #ifdef PROFILE_SINCOS
0447     std::clock_t start, stop;
0448     start = std::clock();
0449 #endif
0450 
0451 #ifdef HAVE_SINCOS
0452     sincos(radians(), &s, &c);
0453 #else
0454     s = ::sin(radians());
0455     c = ::cos(radians());
0456 #endif
0457 
0458 #ifdef PROFILE_SINCOS
0459     stop = std::clock();
0460     seconds_in_trig += double(stop - start) / double(CLOCKS_PER_SEC);
0461 #endif
0462 
0463 #ifdef COUNT_DMS_SINCOS_CALLS
0464     if (!m_sinCosCalled)
0465     {
0466         m_sinCosCalled = true;
0467         ++dms_with_sincos_called;
0468     }
0469     if (m_sinDirty)
0470         m_sinDirty = false;
0471     else
0472         ++redundant_trig_function_calls;
0473 
0474     if (m_cosDirty)
0475         m_cosDirty = false;
0476     else
0477         ++redundant_trig_function_calls;
0478 
0479     trig_function_calls += 2;
0480 #endif
0481 }
0482 
0483 /** Overloaded equality operator */
0484 inline bool operator==(const dms &a1, const dms &a2)
0485 {
0486     return a1.Degrees() == a2.Degrees();
0487 }
0488 
0489 /**
0490  * User-defined dms literals for convenience
0491  */
0492 
0493 /**
0494  * Create a constant angle in degrees
0495  */
0496 inline dms operator "" _deg(long double x) { return dms(double(x)); }
0497 
0498 /**
0499  * Create a constant angle in hours
0500  */
0501 inline dms operator "" _h(long double x) { return dms(double(x * 15.0)); }
0502 
0503 /**
0504  * Create a constant angle in radians
0505  */
0506 inline dms operator "" _rad(long double x) { return dms(double(x / dms::DegToRad)); }
0507 
0508 /**
0509  * Create a constant angle from a DMS string
0510  */
0511 inline dms operator "" _dms(const char *dmsString) { return dms::fromString(QString(dmsString), true); }
0512 
0513 /**
0514  * Create a constant angle from a HMS string
0515  */
0516 inline dms operator "" _hms(const char *hmsString) { return dms::fromString(QString(hmsString), false); }