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

0001 /*
0002     SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kscomet.h"
0008 
0009 #include "ksnumbers.h"
0010 #include "kstarsdata.h"
0011 
0012 #include <QDir>
0013 
0014 namespace
0015 {
0016 int letterToNum(QChar c)
0017 {
0018     char l = c.toLatin1();
0019     if (l < 'A' || l > 'Z' || l == 'I')
0020         return 0;
0021     if (l > 'I')
0022         return l - 'A';
0023     return l - 'A' + 1;
0024 }
0025 // Convert letter designation like "AZ" to number.
0026 int letterDesigToN(QString s)
0027 {
0028     int n = 0;
0029     foreach (const QChar &c, s)
0030     {
0031         int nl = letterToNum(c);
0032         if (nl == 0)
0033             return 0;
0034         n = n * 25 + nl;
0035     }
0036     return n;
0037 }
0038 
0039 QMap<QChar, qint64> cometType;
0040 }
0041 
0042 KSComet::KSComet(const QString &_s, const QString &imfile, double _q, double _e, dms _i, dms _w,
0043                  dms _Node, double Tp, float _M1, float _M2, float _K1, float _K2)
0044     : KSPlanetBase(_s, imfile), q(_q), e(_e), M1(_M1), M2(_M2), K1(_K1), K2(_K2), i(_i), w(_w), N(_Node)
0045 {
0046     setType(SkyObject::COMET);
0047 
0048     //Find the Julian Day of Perihelion from Tp
0049     //Tp is a double which encodes a date like: YYYYMMDD.DDDDD (e.g., 19730521.33333
0050     //    int year    = int(Tp / 10000.0);
0051     //    int month   = int((int(Tp) % 10000) / 100.0);
0052     //    int day     = int(int(Tp) % 100);
0053     //    double Hour = 24.0 * (Tp - int(Tp));
0054     //    int h       = int(Hour);
0055     //    int m       = int(60.0 * (Hour - h));
0056     //    int s       = int(60.0 * (60.0 * (Hour - h) - m));
0057 
0058     //JDp   = KStarsDateTime(QDate(year, month, day), QTime(h, m, s)).djd();
0059 
0060     // JM 2021.10.21: In the new JPL format, it's already in JD
0061     JDp = Tp;
0062 
0063     //compute the semi-major axis, a:
0064     if (e == 1)
0065         a = 0;
0066     else
0067         a = q / (1.0 - e);
0068 
0069 
0070     //Compute the orbital Period from Kepler's 3rd law:
0071     P = 365.2568984 * pow(a, 1.5); //period in days
0072 
0073     //If the name contains a "/", make this name2 and make name a truncated version without the leading "P/" or "C/"
0074     // 2017-10-03 Jasem: We should keep the full name
0075     /*if (name().contains(QDir::separator()))
0076     {
0077         setLongName(name());
0078         setName(name().replace("P/", " ").trimmed());
0079         setName(name().remove("C/"));
0080     }*/
0081 
0082     // Try to calculate UID for comets. It's derived from comet designation.
0083     // To parse name string regular expressions are used. Not really readable.
0084     // And its make strong assumptions about format of comets' names.
0085     // Probably come better algorithm should be used.
0086 
0087     // Periodic comet. Designation like: 12P or 12P-C
0088     QRegExp rePer("^(\\d+)[PD](-([A-Z]+))?");
0089     if (rePer.indexIn(_s) != -1)
0090     {
0091         // Comet number
0092         qint64 num = rePer.cap(1).toInt();
0093         // Fragment number
0094         qint64 fragmentN = letterDesigToN(rePer.cap(2));
0095         // Set UID
0096         uidPart = num << 16 | fragmentN;
0097         return;
0098     }
0099 
0100     // Non periodic comet or comets with single approach
0101     // Designations like C/(2006 A7)
0102     QRegExp rePro("^([PCXDA])/.*\\((\\d{4}) ([A-Z])(\\d+)(-([A-Z]+))?\\)");
0103     if (rePro.indexIn(_s) != -1)
0104     {
0105         // Fill comet type
0106         if (cometType.empty())
0107         {
0108             cometType.insert('P', 0);
0109             cometType.insert('C', 1);
0110             cometType.insert('X', 2);
0111             cometType.insert('D', 3);
0112             cometType.insert('A', 4);
0113         }
0114         qint64 type       = cometType[rePro.cap(1).at(0)]; // Type of comet
0115         qint64 year       = rePro.cap(2).toInt();       // Year of discovery
0116         qint64 halfMonth  = letterToNum(rePro.cap(3).at(0));
0117         qint64 nHalfMonth = rePro.cap(4).toInt();
0118         qint64 fragment   = letterDesigToN(rePro.cap(6));
0119 
0120         uidPart = 1LL << 43 | type << 40 | // Bits 40-42 (3)
0121                   halfMonth << 33 |        // Bits 33-39 (7) Hope this is enough
0122                   nHalfMonth << 28 |       // Bits 28-32 (5)
0123                   year << 16 |             // Bits 16-27 (12)
0124                   fragment;                // Bits 0-15  (16)
0125         return;
0126     }
0127     // qDebug() << Q_FUNC_INFO << "Didn't get it: " << _s;
0128 }
0129 
0130 KSComet *KSComet::clone() const
0131 {
0132     Q_ASSERT(typeid(this) == typeid(static_cast<const KSComet *>(this))); // Ensure we are not slicing a derived class
0133     return new KSComet(*this);
0134 }
0135 
0136 void KSComet::findPhysicalParameters()
0137 {
0138     // Compute and store the estimated Physical size of the comet's coma, tail and nucleus
0139     // References:
0140     // * https://www.projectpluto.com/update7b.htm#comet_tail_formula [Project Pluto / GUIDE]
0141     // * http://articles.adsabs.harvard.edu//full/1978BAICz..29..103K/0000113.000.html [Kresak, 1978a, "Passages of comets and asteroids near the earth"]
0142     NuclearSize   = pow(10, 2.1 - 0.2 * M1);
0143     double mHelio = M1 + K1 * log10(rsun());
0144     double L0, D0, L, D;
0145     L0       = pow(10, -0.0075 * mHelio * mHelio - 0.19 * mHelio + 2.10);
0146     D0       = pow(10, -0.0033 * mHelio * mHelio - 0.07 * mHelio + 3.25);
0147     L        = L0 * (1 - pow(10, -4 * rsun())) * (1 - pow(10, -2 * rsun()));
0148     D        = D0 * (1 - pow(10, -2 * rsun())) * (1 - pow(10, -rsun()));
0149     TailSize = L * 1e6;
0150     ComaSize = D * 1e3;
0151     setPhysicalSize(ComaSize);
0152 }
0153 
0154 bool KSComet::findGeocentricPosition(const KSNumbers *num, const KSPlanetBase *Earth)
0155 {
0156     // All elements are in the heliocentric ecliptic J2000 reference frame.
0157 
0158     double v(0.0), r(0.0);
0159 
0160     // Different between lastJD and Tp (Time of periapsis (Julian Day Number))
0161     long double deltaJDP = lastPrecessJD - JDp;
0162 
0163     if (e > 0.98)
0164     {
0165         //Use near-parabolic approximation
0166         double k = 0.01720209895; //Gauss gravitational constant
0167         double a = 0.75 * (deltaJDP) * k * sqrt((1 + e) / (q * q * q));
0168         double b = sqrt(1.0 + a * a);
0169         double W = pow((b + a), 1.0 / 3.0) - pow((b - a), 1.0 / 3.0);
0170         double c = 1.0 + 1.0 / (W * W);
0171         double f = (1.0 - e) / (1.0 + e);
0172         double g = f / (c * c);
0173 
0174         double a1 = (2.0 / 3.0) + (2.0 * W * W / 5.0);
0175         double a2 = (7.0 / 5.0) + (33.0 * W * W / 35.0) + (37.0 * W * W * W * W / 175.0);
0176         double a3 = W * W * ((432.0 / 175.0) + (956.0 * W * W / 1125.0) + (84.0 * W * W * W * W / 1575.0));
0177         double w  = W * (1.0 + g * c * (a1 + a2 * g + a3 * g * g));
0178 
0179         v = 2.0 * atan(w) / dms::DegToRad;
0180         r = q * (1.0 + w * w) / (1.0 + w * w * f);
0181     }
0182     else
0183     {
0184         //Use normal ellipse method
0185         //Determine Mean anomaly for desired date. deltaJDP is the difference between current JD minus JD of comet epoch.
0186         // In JPL data, the Modified Julian Day is given to designate the epoch of comet data, which we convert to JD.
0187         // Check http://astro.if.ufrgs.br/trigesf/position.html#17 for more details
0188 
0189         dms m = dms(double(360.0 * (deltaJDP) / P)).reduce();
0190         double sinm, cosm;
0191         m.SinCos(sinm, cosm);
0192 
0193         //compute eccentric anomaly:
0194         double E = m.Degrees() + e * 180.0 / dms::PI * sinm * (1.0 + e * cosm);
0195 
0196         if (e > 0.005) //need more accurate approximation, iterate...
0197         {
0198             double E0;
0199             int iter(0);
0200             do
0201             {
0202                 E0 = E;
0203                 iter++;
0204                 E = E0 - (E0 - e * 180.0 / dms::PI * sin(E0 * dms::DegToRad) - m.Degrees()) /
0205                     (1 - e * cos(E0 * dms::DegToRad));
0206             }
0207             while (fabs(E - E0) > 0.001 && iter < 1000);
0208         }
0209 
0210         // Assert that the solution of the Kepler equation E = M + e sin E is accurate to about 0.1 arcsecond
0211         //Q_ASSERT( fabs( E - ( m.Degrees() + ( e * 180.0 / dms::PI ) * sin( E * dms::DegToRad ) ) ) < 0.10/3600.0 );
0212 
0213         double sinE, cosE;
0214         dms E1(E);
0215         E1.SinCos(sinE, cosE);
0216 
0217         double xv = a * (cosE - e);
0218         double yv = a * sqrt(1.0 - e * e) * sinE;
0219 
0220         //v is the true anomaly in degrees
0221         v = atan2(yv, xv) / dms::DegToRad;
0222         // Comet-Sun Heliocentric Distance in AU
0223         r = sqrt(xv * xv + yv * yv);
0224     }
0225 
0226     //Precess the longitude of the Ascending Node to the desired epoch
0227     // i, w, and N are supplied in J2000 Epoch from JPL
0228     // http://astro.if.ufrgs.br/trigesf/position.html#16
0229     //dms n = dms(double(N.Degrees() + 3.82394E-5 * (lastPrecessJD - J2000))).reduce();
0230 
0231     //vw is the sum of the true anomaly and the argument of perihelion
0232     dms vw(v + w.Degrees());
0233 
0234     double sinN, cosN, sinvw, cosvw, sini, cosi;
0235 
0236     // Get sin's and cos's for:
0237     // Longitude of ascending node
0238     N.SinCos(sinN, cosN);
0239     // sum of true anomaly and argument of perihelion
0240     vw.SinCos(sinvw, cosvw);
0241     // Inclination
0242     i.SinCos(sini, cosi);
0243 
0244     //xh, yh, zh are the heliocentric cartesian coords with the ecliptic plane congruent with zh=0.
0245     double xh = r * (cosN * cosvw - sinN * sinvw * cosi);
0246     double yh = r * (sinN * cosvw + cosN * sinvw * cosi);
0247     double zh = r * (sinvw * sini);
0248 
0249     //the spherical ecliptic coordinates:
0250     double ELongRad = atan2(yh, xh);
0251     double ELatRad  = atan2(zh, r);
0252 
0253     helEcPos.longitude.setRadians(ELongRad);
0254     helEcPos.longitude.reduceToRange(dms::ZERO_TO_2PI);
0255     helEcPos.latitude.setRadians(ELatRad);
0256     setRsun(r);
0257 
0258     //xe, ye, ze are the Earth's heliocentric cartesian coords
0259     double cosBe, sinBe, cosLe, sinLe;
0260     Earth->ecLong().SinCos(sinLe, cosLe);
0261     Earth->ecLat().SinCos(sinBe, cosBe);
0262 
0263     double xe = Earth->rsun() * cosBe * cosLe;
0264     double ye = Earth->rsun() * cosBe * sinLe;
0265     double ze = Earth->rsun() * sinBe;
0266 
0267     //convert to geocentric ecliptic coordinates by subtracting Earth's coords:
0268     xh -= xe;
0269     yh -= ye;
0270     zh -= ze;
0271 
0272     //Finally, the spherical ecliptic coordinates:
0273     ELongRad  = atan2(yh, xh);
0274     double rr = sqrt(xh * xh + yh * yh);
0275     ELatRad   = atan2(zh, rr);
0276 
0277     ep.longitude.setRadians(ELongRad);
0278     ep.longitude.reduceToRange(dms::ZERO_TO_2PI);
0279     ep.latitude.setRadians(ELatRad);
0280     setRearth(Earth);
0281 
0282     // Now convert to geocentric equatorial coords
0283     EclipticToEquatorial(num->obliquity());
0284 
0285     // JM 2017-09-10: The calculations above produce J2000 RESULTS
0286     // So we have to precess as well
0287     setRA0(ra());
0288     setDec0(dec());
0289     apparentCoord(J2000, lastPrecessJD);
0290     findPhysicalParameters();
0291 
0292     return true;
0293 }
0294 
0295 // m = M1 + 2.5 * K1 * log10(rsun) + 5 * log10(rearth)
0296 void KSComet::findMagnitude(const KSNumbers *)
0297 {
0298     setMag(M1 + 2.5 * K1 * log10(rsun()) +  5 * log10(rearth()));
0299 }
0300 
0301 void KSComet::setEarthMOID(double earth_moid)
0302 {
0303     EarthMOID = earth_moid;
0304 }
0305 
0306 void KSComet::setAlbedo(float albedo)
0307 {
0308     Albedo = albedo;
0309 }
0310 
0311 void KSComet::setDiameter(float diam)
0312 {
0313     Diameter = diam;
0314 }
0315 
0316 void KSComet::setDimensions(QString dim)
0317 {
0318     Dimensions = dim;
0319 }
0320 
0321 void KSComet::setNEO(bool neo)
0322 {
0323     NEO = neo;
0324 }
0325 
0326 void KSComet::setOrbitClass(QString orbit_class)
0327 {
0328     OrbitClass = orbit_class;
0329 }
0330 
0331 void KSComet::setOrbitID(QString orbit_id)
0332 {
0333     OrbitID = orbit_id;
0334 }
0335 
0336 void KSComet::setPeriod(float per)
0337 {
0338     Period = per;
0339 }
0340 
0341 void KSComet::setRotationPeriod(float rot_per)
0342 {
0343     RotationPeriod = rot_per;
0344 }
0345 
0346 //Unused virtual function from KSPlanetBase
0347 bool KSComet::loadData()
0348 {
0349     return false;
0350 }
0351 
0352 SkyObject::UID KSComet::getUID() const
0353 {
0354     return solarsysUID(UID_SOL_COMET) | uidPart;
0355 }