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 }