File indexing completed on 2024-04-21 14:43:58
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); }