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 #include "dms.h"
0008 
0009 #include <QLocale>
0010 
0011 #include <QRegExp>
0012 
0013 // Qt version calming
0014 #include <qtskipemptyparts.h>
0015 
0016 #ifdef COUNT_DMS_SINCOS_CALLS
0017 long unsigned dms::dms_constructor_calls         = 0;
0018 long unsigned dms::dms_with_sincos_called        = 0;
0019 long unsigned dms::trig_function_calls           = 0;
0020 long unsigned dms::redundant_trig_function_calls = 0;
0021 double dms::seconds_in_trig                      = 0;
0022 #endif
0023 
0024 void dms::setD(const int &d, const int &m, const int &s, const int &ms)
0025 {
0026     D = (double)abs(d) + ((double)m + ((double)s + (double)ms / 1000.) / 60.) / 60.;
0027     if (d < 0)
0028     {
0029         D = -1.0 * D;
0030     }
0031 #ifdef COUNT_DMS_SINCOS_CALLS
0032     m_cosDirty = m_sinDirty = true;
0033 #endif
0034 }
0035 
0036 void dms::setH(const int &h, const int &m, const int &s, const int &ms)
0037 {
0038     D = 15.0 * ((double)abs(h) + ((double)m + ((double)s + (double)ms / 1000.) / 60.) / 60.);
0039     if (h < 0)
0040     {
0041         D = -1.0 * D;
0042     }
0043 #ifdef COUNT_DMS_SINCOS_CALLS
0044     m_cosDirty = m_sinDirty = true;
0045 #endif
0046 }
0047 
0048 bool dms::setFromString(const QString &str, bool isDeg)
0049 {
0050     int d(0), m(0);
0051     double s(0.0);
0052     bool checkValue(false), badEntry(false), negative(false);
0053     QString entry = str.trimmed();
0054     entry.remove(QRegExp("[hdms'\"°]"));
0055 
0056     //Account for localized decimal-point settings
0057     //QString::toDouble() requires that the decimal symbol is "."
0058     entry.replace(QLocale().decimalPoint(), ".");
0059 
0060     //empty entry returns false
0061     if (entry.isEmpty())
0062     {
0063         dms::setD(NaN::d);
0064         return false;
0065     }
0066 
0067     //try parsing a simple integer
0068     d = entry.toInt(&checkValue);
0069     if (checkValue)
0070     {
0071         if (isDeg)
0072             dms::setD(d, 0, 0);
0073         else
0074             dms::setH(d, 0, 0);
0075         return true;
0076     }
0077 
0078     //try parsing a simple double
0079     double x = entry.toDouble(&checkValue);
0080     if (checkValue)
0081     {
0082         if (isDeg)
0083             dms::setD(x);
0084         else
0085             dms::setH(x);
0086         return true;
0087     }
0088 
0089     //try parsing multiple fields.
0090     QStringList fields;
0091 
0092     //check for colon-delimiters or space-delimiters
0093     if (entry.contains(':'))
0094         fields = entry.split(':', Qt::SkipEmptyParts);
0095     else
0096         fields = entry.split(' ', Qt::SkipEmptyParts);
0097 
0098     //anything with one field is invalid!
0099     if (fields.count() == 1)
0100     {
0101         dms::setD(NaN::d);
0102         return false;
0103     }
0104 
0105     //If two fields we will add a third one, and then parse with
0106     //the 3-field code block. If field[1] is an int, add a third field equal to "0".
0107     //If field[1] is a double, convert it to integer arcmin, and convert
0108     //the remainder to integer arcsec
0109     //If field[1] is neither int nor double, return false.
0110     if (fields.count() == 2)
0111     {
0112         m = fields[1].toInt(&checkValue);
0113         if (checkValue)
0114             fields.append(QString("0"));
0115         else
0116         {
0117             double mx = fields[1].toDouble(&checkValue);
0118             if (checkValue)
0119             {
0120                 fields[1] = QString::number(int(mx));
0121                 fields.append(QString::number(int(60.0 * (mx - int(mx)))));
0122             }
0123             else
0124             {
0125                 dms::setD(NaN::d);
0126                 return false;
0127             }
0128         }
0129     }
0130 
0131     //Now have (at least) three fields ( h/d m s );
0132     //we can ignore anything after 3rd field
0133     if (fields.count() >= 3)
0134     {
0135         //See if first two fields parse as integers, and third field as a double
0136 
0137         d = fields[0].toInt(&checkValue);
0138         if (!checkValue)
0139             badEntry = true;
0140         m = fields[1].toInt(&checkValue);
0141         if (!checkValue)
0142             badEntry = true;
0143         s = fields[2].toDouble(&checkValue);
0144         if (!checkValue)
0145             badEntry = true;
0146 
0147         //Special case: If first field is "-0", store the negative sign.
0148         //(otherwise it gets dropped)
0149         if (fields[0].at(0) == '-' && d == 0)
0150             negative = true;
0151     }
0152 
0153     if (!badEntry)
0154     {
0155         double D = (double)abs(d) + (double)abs(m) / 60. + (double)fabs(s) / 3600.;
0156 
0157         if (negative || d < 0 || m < 0 || s < 0)
0158         {
0159             D = -1.0 * D;
0160         }
0161 
0162         if (isDeg)
0163         {
0164             dms::setD(D);
0165         }
0166         else
0167         {
0168             dms::setH(D);
0169         }
0170     }
0171     else
0172     {
0173         dms::setD(NaN::d);
0174         return false;
0175     }
0176 
0177     return true;
0178 }
0179 
0180 int dms::arcmin(void) const
0181 {
0182     if (std::isnan(D))
0183         return 0;
0184 
0185     int am = int(60.0 * (fabs(D) - abs(degree())));
0186     if (D < 0.0 && D > -1.0) //angle less than zero, but greater than -1.0
0187     {
0188         am = -1 * am; //make minute negative
0189     }
0190     return am; // Warning: Will return 0 if the value is NaN
0191 }
0192 
0193 int dms::arcsec(void) const
0194 {
0195     if (std::isnan(D))
0196         return 0;
0197 
0198     int as = int(60.0 * (60.0 * (fabs(D) - abs(degree())) - abs(arcmin())));
0199     //If the angle is slightly less than 0.0, give ArcSec a neg. sgn.
0200     if (degree() == 0 && arcmin() == 0 && D < 0.0)
0201     {
0202         as = -1 * as;
0203     }
0204     return as; // Warning: Will return 0 if the value is NaN
0205 }
0206 
0207 int dms::marcsec(void) const
0208 {
0209     if (std::isnan(D))
0210         return 0;
0211 
0212     int as = int(1000.0 * (60.0 * (60.0 * (fabs(D) - abs(degree())) - abs(arcmin())) - abs(arcsec())));
0213     //If the angle is slightly less than 0.0, give ArcSec a neg. sgn.
0214     if (degree() == 0 && arcmin() == 0 && arcsec() == 0 && D < 0.0)
0215     {
0216         as = -1 * as;
0217     }
0218     return as; // Warning: Will return 0 if the value is NaN
0219 }
0220 
0221 int dms::minute(void) const
0222 {
0223     int hm = int(60.0 * (fabs(Hours()) - abs(hour())));
0224     if (Hours() < 0.0 && Hours() > -1.0) //angle less than zero, but greater than -1.0
0225     {
0226         hm = -1 * hm; //make minute negative
0227     }
0228     return hm; // Warning: Will return 0 if the value is NaN
0229 }
0230 
0231 int dms::second(void) const
0232 {
0233     int hs = int(60.0 * (60.0 * (fabs(Hours()) - abs(hour())) - abs(minute())));
0234     if (hour() == 0 && minute() == 0 && Hours() < 0.0)
0235     {
0236         hs = -1 * hs;
0237     }
0238     return hs; // Warning: Will return 0 if the value is NaN
0239 }
0240 
0241 int dms::msecond(void) const
0242 {
0243     int hs = int(1000.0 * (60.0 * (60.0 * (fabs(Hours()) - abs(hour())) - abs(minute())) - abs(second())));
0244     if (hour() == 0 && minute() == 0 && second() == 0 && Hours() < 0.0)
0245     {
0246         hs = -1 * hs;
0247     }
0248     return hs; // Warning: Will return 0 if the value is NaN
0249 }
0250 
0251 const dms dms::reduce(void) const
0252 {
0253     if (std::isnan(D))
0254         return dms(0);
0255 
0256     return dms(D - 360.0 * floor(D / 360.0));
0257 }
0258 
0259 const dms dms::deltaAngle(dms angle) const
0260 {
0261     double angleDiff = D - angle.Degrees();
0262 
0263     // Put in the range of [-360,360]
0264     while (angleDiff > 360)
0265         angleDiff -= 360;
0266     while (angleDiff < -360)
0267         angleDiff += 360;
0268     // angleDiff in the range [180,360]
0269     if (angleDiff > 180)
0270         return dms(360 - angleDiff);
0271     // angleDiff in the range [-360,-180]
0272     else if (angleDiff < -180)
0273         return dms(-(360 + angleDiff));
0274     // angleDiff in the range [-180,180]
0275     else
0276         return dms(angleDiff);
0277 }
0278 
0279 const QString dms::toDMSString(const bool forceSign, const bool machineReadable, const bool highPrecision) const
0280 {
0281     QString dummy;
0282     char pm(' ');
0283     QChar zero('0');
0284     int dd, dm, ds;
0285 
0286     if (machineReadable || !highPrecision)
0287     // minimize the mean angle representation error of DMS format
0288     // set LSD transition in the middle of +- half precision range
0289     {
0290         double half_precision = 1.0 / 7200.0;
0291     if (Degrees() < 0.0)
0292             half_precision = -half_precision;
0293     dms angle(Degrees() + half_precision);
0294         dd = abs(angle.degree());
0295         dm = abs(angle.arcmin());
0296         ds = abs(angle.arcsec());
0297     }
0298     else
0299     {
0300         dd = abs(degree());
0301         dm = abs(arcmin());
0302         ds = abs(arcsec());
0303     }
0304 
0305     if (Degrees() < 0.0)
0306         pm = '-';
0307     else if (forceSign && Degrees() > 0.0)
0308         pm = '+';
0309 
0310     if (machineReadable)
0311         return QString("%1%2:%3:%4").arg(pm)
0312                 .arg(dd, 2, 10, QChar('0'))
0313                 .arg(dm, 2, 10, QChar('0'))
0314                 .arg(ds, 2, 10, QChar('0'));
0315 
0316     if (highPrecision)
0317     {
0318         double sec = arcsec() + marcsec() / 1000.;
0319         return QString("%1%2° %3\' %L4\"").arg(pm)
0320                                          .arg(dd, 2, 10, zero)
0321                                          .arg(dm, 2, 10, zero)
0322                                          .arg(sec, 2,'f', 2, zero);
0323     }
0324 
0325     return QString("%1%2° %3\' %4\"").arg(pm)
0326                                      .arg(dd, 2, 10, zero)
0327                                      .arg(dm, 2, 10, zero)
0328                                      .arg(ds, 2, 10, zero);
0329 
0330 #if 0
0331     if (!machineReadable && dd < 10)
0332     {
0333         if (highPrecision)
0334         {
0335             double sec = arcsec() + marcsec() / 1000.;
0336             return dummy.sprintf("%c%1d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec);
0337         }
0338 
0339         return dummy.sprintf("%c%1d%c %02d\' %02d\"", pm, dd, 176, dm, ds);
0340     }
0341 
0342     if (!machineReadable && dd < 100)
0343     {
0344         if (highPrecision)
0345         {
0346             double sec = arcsec() + marcsec() / 1000.;
0347             return dummy.sprintf("%c%2d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec);
0348         }
0349 
0350         return dummy.sprintf("%c%2d%c %02d\' %02d\"", pm, dd, 176, dm, ds);
0351     }
0352     if (machineReadable && dd < 100)
0353         return dummy.sprintf("%c%02d:%02d:%02d", pm, dd, dm, ds);
0354 
0355     if (!machineReadable)
0356     {
0357         if (highPrecision)
0358         {
0359             double sec = arcsec() + marcsec() / 1000.;
0360             return dummy.sprintf("%c%3d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec);
0361         }
0362         else
0363             return dummy.sprintf("%c%3d%c %02d\' %02d\"", pm, dd, 176, dm, ds);
0364     }
0365     else
0366         return dummy.sprintf("%c%03d:%02d:%02d", pm, dd, dm, ds);
0367 #endif
0368 }
0369 
0370 const QString dms::toHMSString(const bool machineReadable, const bool highPrecision) const
0371 {
0372     QChar zero('0');
0373     dms angle;
0374     int hh, hm, hs;
0375 
0376     if (machineReadable || !highPrecision)
0377     // minimize the mean angle representation error of HMS format
0378     // set LSD transition in the middle of +- half precision range
0379     {
0380         double half_precision = 15.0 / 7200.0;
0381     angle.setD(Degrees() + half_precision);
0382         hh = angle.hour();
0383         hm = angle.minute();
0384         hs = angle.second();
0385     }
0386 
0387     if (machineReadable)
0388         return QString("%1:%2:%3").arg(hh, 2, 10, zero)
0389                                   .arg(hm, 2, 10, zero)
0390                                   .arg(hs, 2, 10, zero);
0391 
0392     if (highPrecision)
0393     {
0394         double sec = second() + msecond() / 1000.;
0395         return QString("%1h %2m %L3s").arg(hour(), 2, 10, zero)
0396                                      .arg(minute(), 2, 10, zero)
0397                                      .arg(sec, 2, 'f', 2, zero);
0398     }
0399 
0400     return QString("%1h %2m %3s").arg(hh, 2, 10, zero)
0401                                  .arg(hm, 2, 10, zero)
0402                                  .arg(hs, 2, 10, zero);
0403 
0404 #if 0
0405     QString dummy;
0406     if (!machineReadable)
0407     {
0408         if (highPrecision)
0409         {
0410             double sec = second() + msecond() / 1000.;
0411             return dummy.sprintf("%02dh %02dm %05.2f", hour(), minute(), sec);
0412         }
0413         else
0414             return dummy.sprintf("%02dh %02dm %02ds", hh, hm, hs);
0415     }
0416     else
0417         return dummy.sprintf("%02d:%02d:%02d", hh, hm, hs);
0418 #endif
0419 }
0420 
0421 dms dms::fromString(const QString &st, bool deg)
0422 {
0423     dms result;
0424     result.setFromString(st, deg);
0425     return result;
0426     //bool ok( false );
0427 
0428     //ok = result.setFromString( st, deg );
0429 
0430     //  if ( ok )
0431     //return result;
0432     //  else {
0433     //      kDebug() << i18n( "Could Not Set Angle from string: " ) << st;
0434     //      return result;
0435     //  }
0436 }
0437 
0438 void dms::reduceToRange(enum dms::AngleRanges range)
0439 {
0440     if (std::isnan(D))
0441         return;
0442 
0443     switch (range)
0444     {
0445         case MINUSPI_TO_PI:
0446             D -= 360. * floor((D + 180.) / 360.);
0447             break;
0448         case ZERO_TO_2PI:
0449             D -= 360. * floor(D / 360.);
0450     }
0451 }
0452 
0453 QDataStream &operator<<(QDataStream &out, const dms &d)
0454 {
0455     out << d.D;
0456     return out;
0457 }
0458 
0459 QDataStream &operator>>(QDataStream &in, dms &d){
0460    double D;
0461    in >> D;
0462    d = dms(D);
0463    return in;
0464 }