File indexing completed on 2024-05-19 05:37:53

0001 /*
0002     SPDX-FileCopyrightText: 2009 Petri Damsten <damu@iki.fi>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "solarsystem.h"
0008 #include <QList>
0009 #include <math.h>
0010 
0011 /*
0012  *   Mathematics, ideas, public domain code used for these classes from:
0013  *   https://www.stjarnhimlen.se/comp/tutorial.html
0014  *   https://www.stjarnhimlen.se/comp/riset.html
0015  *   https://www.esrl.noaa.gov/gmd/grad/solcalc/azel.html
0016  *   https://www.esrl.noaa.gov/gmd/grad/solcalc/sunrise.html
0017  *   http://web.archive.org/web/20080309162302/http://bodmas.org/astronomy/riset.html
0018  *   moontool.c by John Walker
0019  *   Wikipedia
0020  */
0021 
0022 Sun::Sun()
0023     : SolarSystemObject()
0024 {
0025 }
0026 
0027 void Sun::calcForDateTime(const QDateTime &local, int offset)
0028 {
0029     SolarSystemObject::calcForDateTime(local, offset);
0030 
0031     N = 0.0;
0032     i = 0.0;
0033     w = rev(282.9404 + 4.70935E-5 * m_day);
0034     a = 1.0;
0035     e = rev(0.016709 - 1.151E-9 * m_day);
0036     M = rev(356.0470 + 0.9856002585 * m_day);
0037 
0038     calc();
0039 }
0040 
0041 void Sun::rotate(double *y, double *z)
0042 {
0043     *y *= cosd(m_obliquity);
0044     *z *= sind(m_obliquity);
0045 }
0046 
0047 Moon::Moon(Sun *sun)
0048     : m_sun(sun)
0049 {
0050 }
0051 
0052 void Moon::calcForDateTime(const QDateTime &local, int offset)
0053 {
0054     if (m_sun->dateTime() != local) {
0055         m_sun->calcForDateTime(local, offset);
0056     }
0057 
0058     SolarSystemObject::calcForDateTime(local, offset);
0059 
0060     N = rev(125.1228 - 0.0529538083 * m_day);
0061     i = 5.1454;
0062     w = rev(318.0634 + 0.1643573223 * m_day);
0063     a = 60.2666;
0064     e = 0.054900;
0065     M = rev(115.3654 + 13.0649929509 * m_day);
0066 
0067     calc();
0068 }
0069 
0070 bool Moon::calcPerturbations(double *lo, double *la, double *r)
0071 {
0072     double Ms = m_sun->meanAnomaly();
0073     double D = L - m_sun->meanLongitude();
0074     double F = L - N;
0075     // clang-format off
0076     *lo +=  -1.274 * sind(M - 2 * D)
0077             +0.658 * sind(2 * D)
0078             -0.186 * sind(Ms)
0079             -0.059 * sind(2 * M - 2 * D)
0080             -0.057 * sind(M - 2 * D + Ms)
0081             +0.053 * sind(M + 2 * D)
0082             +0.046 * sind(2 * D - Ms)
0083             +0.041 * sind(M - Ms)
0084             -0.035 * sind(D)
0085             -0.031 * sind(M + Ms)
0086             -0.015 * sind(2 * F - 2 * D)
0087             +0.011 * sind(M - 4 * D);
0088     *la +=  -0.173 * sind(F - 2 * D)
0089             -0.055 * sind(M - F - 2 * D)
0090             -0.046 * sind(M + F - 2 * D)
0091             +0.033 * sind(F + 2 * D)
0092             +0.017 * sind(2 * M + F);
0093     *r +=   -0.58 * cosd(M - 2 * D)
0094             -0.46 * cosd(2 * D);
0095     // clang-format on
0096     return true;
0097 }
0098 
0099 void Moon::topocentricCorrection(double *RA, double *dec)
0100 {
0101     double HA = rev(siderealTime() - *RA);
0102     double gclat = m_latitude - 0.1924 * sind(2 * m_latitude);
0103     double rho = 0.99833 + 0.00167 * cosd(2 * m_latitude);
0104     double mpar = asind(1 / rad);
0105     double g = atand(tand(gclat) / cosd(HA));
0106 
0107     *RA -= mpar * rho * cosd(gclat) * sind(HA) / cosd(*dec);
0108     *dec -= mpar * rho * sind(gclat) * sind(g - *dec) / sind(g);
0109 }
0110 
0111 double Moon::phase()
0112 {
0113     return rev(m_eclipticLongitude - m_sun->lambda());
0114 }
0115 
0116 void Moon::rotate(double *y, double *z)
0117 {
0118     double t = *y;
0119     *y = t * cosd(m_obliquity) - *z * sind(m_obliquity);
0120     *z = t * sind(m_obliquity) + *z * cosd(m_obliquity);
0121 }
0122 
0123 void SolarSystemObject::calc()
0124 {
0125     double x, y, z;
0126     double la, r;
0127 
0128     L = rev(N + w + M);
0129     double E0 = 720.0;
0130     double E = M + (180.0 / M_PI) * e * sind(M) * (1.0 + e * cosd(M));
0131     for (int j = 0; fabs(E0 - E) > 0.005 && j < 10; ++j) {
0132         E0 = E;
0133         E = E0 - (E0 - (180.0 / M_PI) * e * sind(E0) - M) / (1 - e * cosd(E0));
0134     }
0135     x = a * (cosd(E) - e);
0136     y = a * sind(E) * sqrt(1.0 - e * e);
0137     r = sqrt(x * x + y * y);
0138     double v = rev(atan2d(y, x));
0139     m_lambda = rev(v + w);
0140     x = r * (cosd(N) * cosd(m_lambda) - sind(N) * sind(m_lambda) * cosd(i));
0141     y = r * (sind(N) * cosd(m_lambda) + cosd(N) * sind(m_lambda) * cosd(i));
0142     z = r * sind(m_lambda);
0143     if (!qFuzzyCompare(i, 0.0)) {
0144         z *= sind(i);
0145     }
0146     toSpherical(x, y, z, &m_eclipticLongitude, &la, &r);
0147     if (calcPerturbations(&m_eclipticLongitude, &la, &r)) {
0148         toRectangular(m_eclipticLongitude, la, r, &x, &y, &z);
0149     }
0150     rotate(&y, &z);
0151     toSpherical(x, y, z, &RA, &dec, &rad);
0152     topocentricCorrection(&RA, &dec);
0153 
0154     HA = rev(siderealTime() - RA);
0155     x = cosd(HA) * cosd(dec) * sind(m_latitude) - sind(dec) * cosd(m_latitude);
0156     y = sind(HA) * cosd(dec);
0157     z = cosd(HA) * cosd(dec) * cosd(m_latitude) + sind(dec) * sind(m_latitude);
0158     m_azimuth = atan2d(y, x) + 180.0;
0159     m_altitude = asind(z);
0160 }
0161 
0162 double SolarSystemObject::siderealTime()
0163 {
0164     double UT = m_utc.time().hour() + m_utc.time().minute() / 60.0 + m_utc.time().second() / 3600.0;
0165     double GMST0 = rev(282.9404 + 4.70935E-5 * m_day + 356.0470 + 0.9856002585 * m_day + 180.0);
0166     return GMST0 + UT * 15.0 + m_longitude;
0167 }
0168 
0169 void SolarSystemObject::calcForDateTime(const QDateTime &local, int offset)
0170 {
0171     m_local = local;
0172     m_utc = local.addSecs(-offset);
0173     m_day = 367 * m_utc.date().year() - (7 * (m_utc.date().year() + ((m_utc.date().month() + 9) / 12))) / 4 + (275 * m_utc.date().month()) / 9
0174         + m_utc.date().day() - 730530;
0175     m_day += m_utc.time().hour() / 24.0 + m_utc.time().minute() / (24.0 * 60.0) + m_utc.time().second() / (24.0 * 60.0 * 60.0);
0176     m_obliquity = 23.4393 - 3.563E-7 * m_day;
0177 }
0178 
0179 SolarSystemObject::SolarSystemObject()
0180     : m_latitude(0.0)
0181     , m_longitude(0.0)
0182 {
0183 }
0184 
0185 SolarSystemObject::~SolarSystemObject()
0186 {
0187 }
0188 
0189 void SolarSystemObject::setPosition(double latitude, double longitude)
0190 {
0191     m_latitude = latitude;
0192     m_longitude = longitude;
0193 }
0194 
0195 double SolarSystemObject::rev(double x)
0196 {
0197     return x - floor(x / 360.0) * 360.0;
0198 }
0199 
0200 double SolarSystemObject::asind(double x)
0201 {
0202     return asin(x) * 180.0 / M_PI;
0203 }
0204 
0205 double SolarSystemObject::sind(double x)
0206 {
0207     return sin(x * M_PI / 180.0);
0208 }
0209 
0210 double SolarSystemObject::cosd(double x)
0211 {
0212     return cos(x * M_PI / 180.0);
0213 }
0214 
0215 double SolarSystemObject::tand(double x)
0216 {
0217     return tan(x * M_PI / 180.0);
0218 }
0219 
0220 double SolarSystemObject::atan2d(double y, double x)
0221 {
0222     return atan2(y, x) * 180.0 / M_PI;
0223 }
0224 
0225 double SolarSystemObject::atand(double x)
0226 {
0227     return atan(x) * 180.0 / M_PI;
0228 }
0229 
0230 void SolarSystemObject::toRectangular(double lo, double la, double r, double *x, double *y, double *z)
0231 {
0232     *x = r * cosd(lo) * cosd(la);
0233     *y = r * sind(lo) * cosd(la);
0234     *z = r * sind(la);
0235 }
0236 
0237 void SolarSystemObject::toSpherical(double x, double y, double z, double *lo, double *la, double *r)
0238 {
0239     *r = sqrt(x * x + y * y + z * z);
0240     *la = asind(z / *r);
0241     *lo = rev(atan2d(y, x));
0242 }
0243 
0244 QPair<double, double> SolarSystemObject::zeroPoints(QPointF p1, QPointF p2, QPointF p3)
0245 {
0246     double a = ((p2.y() - p1.y()) * (p1.x() - p3.x()) + (p3.y() - p1.y()) * (p2.x() - p1.x()))
0247         / ((p1.x() - p3.x()) * (p2.x() * p2.x() - p1.x() * p1.x()) + (p2.x() - p1.x()) * (p3.x() * p3.x() - p1.x() * p1.x()));
0248     double b = ((p2.y() - p1.y()) - a * (p2.x() * p2.x() - p1.x() * p1.x())) / (p2.x() - p1.x());
0249     double c = p1.y() - a * p1.x() * p1.x() - b * p1.x();
0250     double discriminant = b * b - 4.0 * a * c;
0251     double z1 = -1.0, z2 = -1.0;
0252 
0253     if (discriminant >= 0.0) {
0254         z1 = (-b + sqrt(discriminant)) / (2 * a);
0255         z2 = (-b - sqrt(discriminant)) / (2 * a);
0256     }
0257     return QPair<double, double>(z1, z2);
0258 }
0259 
0260 QList<QPair<QDateTime, QDateTime>> SolarSystemObject::timesForAngles(const QList<double> &angles, const QDateTime &dt, int offset)
0261 {
0262     QList<double> altitudes;
0263     QDate d = dt.date();
0264     QDateTime local(d, QTime(0, 0));
0265     for (int j = 0; j <= 25; ++j) {
0266         calcForDateTime(local, offset);
0267         altitudes.append(altitude());
0268         local = local.addSecs(60 * 60);
0269     }
0270     QList<QPair<QDateTime, QDateTime>> result;
0271     QTime rise, set;
0272     foreach (double angle, angles) {
0273         for (int j = 3; j <= 25; j += 2) {
0274             QPointF p1((j - 2) * 60 * 60, altitudes[j - 2] - angle);
0275             QPointF p2((j - 1) * 60 * 60, altitudes[j - 1] - angle);
0276             QPointF p3(j * 60 * 60, altitudes[j] - angle);
0277             QPair<double, double> z = zeroPoints(p1, p2, p3);
0278             if (z.first > p1.x() && z.first < p3.x()) {
0279                 if (p1.y() < 0.0) {
0280                     rise = QTime(0, 0).addSecs(z.first);
0281                 } else {
0282                     set = QTime(0, 0).addSecs(z.first);
0283                 }
0284             }
0285             if (z.second > p1.x() && z.second < p3.x()) {
0286                 if (p3.y() < 0.0) {
0287                     set = QTime(0, 0).addSecs(z.second);
0288                 } else {
0289                     rise = QTime(0, 0).addSecs(z.second);
0290                 }
0291             }
0292         }
0293         result.append(QPair<QDateTime, QDateTime>(QDateTime(d, rise), QDateTime(d, set)));
0294     }
0295     return result;
0296 }
0297 
0298 double SolarSystemObject::calcElevation()
0299 {
0300     double refractionCorrection;
0301 
0302     if (m_altitude > 85.0) {
0303         refractionCorrection = 0.0;
0304     } else {
0305         double te = tand(m_altitude);
0306         if (m_altitude > 5.0) {
0307             refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te);
0308         } else if (m_altitude > -0.575) {
0309             refractionCorrection = 1735.0 + m_altitude * (-518.2 + m_altitude * (103.4 + m_altitude * (-12.79 + m_altitude * 0.711)));
0310         } else {
0311             refractionCorrection = -20.774 / te;
0312         }
0313         refractionCorrection = refractionCorrection / 3600.0;
0314     }
0315     return m_altitude + refractionCorrection;
0316 }