File indexing completed on 2024-04-21 03:44:53

0001 /*
0002     SPDX-FileCopyrightText: 2007 Jason Harris <kstars@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "modcalcvizequinox.h"
0008 
0009 #include "dms.h"
0010 #include "kstarsdata.h"
0011 #include "ksnotification.h"
0012 #include "skyobjects/kssun.h"
0013 #include "widgets/dmsbox.h"
0014 
0015 #include <KLineEdit>
0016 #include <KPlotAxis>
0017 #include <KPlotObject>
0018 #include <KPlotPoint>
0019 
0020 #include <cmath>
0021 #include <QtMath>
0022 
0023 modCalcEquinox::modCalcEquinox(QWidget *parentSplit) : QFrame(parentSplit), dSpring(), dSummer(), dAutumn(), dWinter()
0024 {
0025     setupUi(this);
0026 
0027     connect(Year, SIGNAL(valueChanged(int)), this, SLOT(slotCompute()));
0028     connect(InputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles()));
0029     connect(OutputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles()));
0030     connect(RunButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch()));
0031     connect(ViewButtonBatch, SIGNAL(clicked()), this, SLOT(slotViewBatch()));
0032 
0033     Plot->axis(KPlotWidget::LeftAxis)->setLabel(i18n("Sun's Declination"));
0034     Plot->setTopPadding(40);
0035     //Don't draw Top & Bottom axes; custom axes drawn as plot objects
0036     Plot->axis(KPlotWidget::BottomAxis)->setVisible(false);
0037     Plot->axis(KPlotWidget::TopAxis)->setVisible(false);
0038 
0039     //This will call slotCompute():
0040     Year->setValue(KStarsData::Instance()->lt().date().year());
0041 
0042     RunButtonBatch->setEnabled(false);
0043     ViewButtonBatch->setEnabled(false);
0044 
0045     show();
0046 }
0047 
0048 double modCalcEquinox::dmonth(int i)
0049 {
0050     Q_ASSERT(i >= 0 && i < 12 && "Month must be in 0 .. 11 range");
0051     return DMonth[i];
0052 }
0053 
0054 void modCalcEquinox::slotCheckFiles()
0055 {
0056     RunButtonBatch->setEnabled(!InputFileBatch->lineEdit()->text().isEmpty() &&
0057                                !OutputFileBatch->lineEdit()->text().isEmpty());
0058 }
0059 
0060 void modCalcEquinox::slotRunBatch()
0061 {
0062     QString inputFileName = InputFileBatch->url().toLocalFile();
0063 
0064     if (QFile::exists(inputFileName))
0065     {
0066         QFile f(inputFileName);
0067         if (!f.open(QIODevice::ReadOnly))
0068         {
0069             KSNotification::sorry(i18n("Could not open file %1.", f.fileName()), i18n("Could Not Open File"));
0070             inputFileName.clear();
0071             return;
0072         }
0073 
0074         QTextStream istream(&f);
0075         processLines(istream);
0076 
0077         ViewButtonBatch->setEnabled(true);
0078 
0079         f.close();
0080     }
0081     else
0082     {
0083         KSNotification::sorry(i18n("Invalid file: %1", inputFileName), i18n("Invalid file"));
0084         inputFileName.clear();
0085         return;
0086     }
0087 }
0088 
0089 void modCalcEquinox::processLines(QTextStream &istream)
0090 {
0091     QFile fOut(OutputFileBatch->url().toLocalFile());
0092     fOut.open(QIODevice::WriteOnly);
0093     QTextStream ostream(&fOut);
0094     int originalYear = Year->value();
0095 
0096     //Write header to output file
0097     ostream << i18n("# Timing of Equinoxes and Solstices\n") << i18n("# computed by KStars\n#\n")
0098             << i18n("# Vernal Equinox\t\tSummer Solstice\t\t\tAutumnal Equinox\t\tWinter Solstice\n#\n");
0099 
0100     while (!istream.atEnd())
0101     {
0102         QString line = istream.readLine();
0103         bool ok      = false;
0104         int year     = line.toInt(&ok);
0105 
0106         //for now I will simply change the value of the Year widget to trigger
0107         //computation of the Equinoxes and Solstices.
0108         if (ok)
0109         {
0110             //triggers slotCompute(), which sets values of dSpring et al.:
0111             Year->setValue(year);
0112 
0113             //Write to output file
0114             ostream << QLocale().toString(dSpring.date(), QLocale::LongFormat) << "\t"
0115                     << QLocale().toString(dSummer.date(), QLocale::LongFormat) << "\t"
0116                     << QLocale().toString(dAutumn.date(), QLocale::LongFormat) << "\t"
0117                     << QLocale().toString(dWinter.date(), QLocale::LongFormat) << '\n';
0118         }
0119     }
0120 
0121     if (Year->value() != originalYear)
0122         Year->setValue(originalYear);
0123 }
0124 
0125 void modCalcEquinox::slotViewBatch()
0126 {
0127     QFile fOut(OutputFileBatch->url().toLocalFile());
0128     fOut.open(QIODevice::ReadOnly);
0129     QTextStream istream(&fOut);
0130     QStringList text;
0131 
0132     while (!istream.atEnd())
0133         text.append(istream.readLine());
0134 
0135     fOut.close();
0136 
0137     KMessageBox::informationList(nullptr, i18n("Results of Sidereal time calculation"), text,
0138                                  OutputFileBatch->url().toLocalFile());
0139 }
0140 
0141 void modCalcEquinox::slotCompute()
0142 {
0143     KStarsData *data = KStarsData::Instance();
0144     KSSun Sun;
0145     int year0 = Year->value();
0146 
0147     KStarsDateTime dt(QDate(year0, 1, 1), QTime(0, 0, 0));
0148     long double jd0 = dt.djd(); //save JD on Jan 1st
0149     for (int imonth = 0; imonth < 12; imonth++)
0150     {
0151         KStarsDateTime kdt(QDate(year0, imonth + 1, 1), QTime(0, 0, 0));
0152         DMonth[imonth] = kdt.djd() - jd0;
0153     }
0154 
0155     Plot->removeAllPlotObjects();
0156 
0157     //Add the celestial equator, just a single line bisecting the plot horizontally
0158     KPlotObject *ce = new KPlotObject(data->colorScheme()->colorNamed("EqColor"), KPlotObject::Lines, 2.0);
0159     ce->addPoint(0.0, 0.0);
0160     ce->addPoint(366.0, 0.0);
0161     Plot->addPlotObject(ce);
0162 
0163     // Tropic of cancer
0164     KPlotObject *tcan = new KPlotObject(data->colorScheme()->colorNamed("EqColor"), KPlotObject::Lines, 2.0);
0165     tcan->addPoint(0.0, 23.5);
0166     tcan->addPoint(366.0, 23.5);
0167     Plot->addPlotObject(tcan);
0168 
0169     // Tropic of capricorn
0170     KPlotObject *tcap = new KPlotObject(data->colorScheme()->colorNamed("EqColor"), KPlotObject::Lines, 2.0);
0171     tcap->addPoint(0.0, -23.5);
0172     tcap->addPoint(366.0, -23.5);
0173     Plot->addPlotObject(tcap);
0174 
0175     //Add Ecliptic.  This is more complicated than simply incrementing the
0176     //ecliptic longitude, because we want the x-axis to be time, not RA.
0177     //For each day in the year, compute the Sun's position.
0178     KPlotObject *ecl = new KPlotObject(data->colorScheme()->colorNamed("EclColor"), KPlotObject::Lines, 2);
0179     ecl->setLinePen(QPen(ecl->pen().color(), 4));
0180 
0181     Plot->setLimits(1.0, double(dt.date().daysInYear()), -30.0, 30.0);
0182 
0183     //Add top and bottom axis lines, and custom tickmarks at each month
0184     addDateAxes();
0185 
0186     for (int i = 1; i <= dt.date().daysInYear(); i++)
0187     {
0188         KSNumbers num(dt.djd());
0189         Sun.findPosition(&num);
0190         ecl->addPoint(double(i), Sun.dec().Degrees());
0191 
0192         dt = dt.addDays(1);
0193     }
0194     Plot->addPlotObject(ecl);
0195 
0196     // Calculate dates for all solstices and equinoxes
0197     findSolsticeAndEquinox(Year->value());
0198 
0199     //Display the Date/Time of each event in the text fields
0200     VEquinox->setText(QLocale().toString(dSpring, QLocale::LongFormat));
0201     SSolstice->setText(QLocale().toString(dSummer, QLocale::LongFormat));
0202     AEquinox->setText(QLocale().toString(dAutumn, QLocale::LongFormat));
0203     WSolstice->setText(QLocale().toString(dWinter, QLocale::LongFormat));
0204 
0205     //Add vertical dotted lines at times of the equinoxes and solstices
0206     KPlotObject *poSpring = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0207     poSpring->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine));
0208     poSpring->addPoint(dSpring.djd() - jd0, Plot->dataRect().top());
0209     poSpring->addPoint(dSpring.djd() - jd0, Plot->dataRect().bottom());
0210     Plot->addPlotObject(poSpring);
0211     KPlotObject *poSummer = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0212     poSummer->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine));
0213     poSummer->addPoint(dSummer.djd() - jd0, Plot->dataRect().top());
0214     poSummer->addPoint(dSummer.djd() - jd0, Plot->dataRect().bottom());
0215     Plot->addPlotObject(poSummer);
0216     KPlotObject *poAutumn = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0217     poAutumn->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine));
0218     poAutumn->addPoint(dAutumn.djd() - jd0, Plot->dataRect().top());
0219     poAutumn->addPoint(dAutumn.djd() - jd0, Plot->dataRect().bottom());
0220     Plot->addPlotObject(poAutumn);
0221     KPlotObject *poWinter = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0222     poWinter->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine));
0223     poWinter->addPoint(dWinter.djd() - jd0, Plot->dataRect().top());
0224     poWinter->addPoint(dWinter.djd() - jd0, Plot->dataRect().bottom());
0225     Plot->addPlotObject(poWinter);
0226 }
0227 
0228 //Add custom top/bottom axes with tickmarks for each month
0229 void modCalcEquinox::addDateAxes()
0230 {
0231     KPlotObject *poTopAxis = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0232     poTopAxis->addPoint(0.0, Plot->dataRect().bottom()); //y-axis is reversed!
0233     poTopAxis->addPoint(366.0, Plot->dataRect().bottom());
0234     Plot->addPlotObject(poTopAxis);
0235 
0236     KPlotObject *poBottomAxis = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0237     poBottomAxis->addPoint(0.0, Plot->dataRect().top() + 0.02);
0238     poBottomAxis->addPoint(366.0, Plot->dataRect().top() + 0.02);
0239     Plot->addPlotObject(poBottomAxis);
0240 
0241     //Tick mark for each month
0242     for (int imonth = 0; imonth < 12; imonth++)
0243     {
0244         KPlotObject *poMonth = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0245         poMonth->addPoint(dmonth(imonth), Plot->dataRect().top());
0246         poMonth->addPoint(dmonth(imonth), Plot->dataRect().top() + 1.4);
0247         Plot->addPlotObject(poMonth);
0248         poMonth = new KPlotObject(Qt::white, KPlotObject::Lines, 1);
0249         poMonth->addPoint(dmonth(imonth), Plot->dataRect().bottom());
0250         poMonth->addPoint(dmonth(imonth), Plot->dataRect().bottom() - 1.4);
0251         Plot->addPlotObject(poMonth);
0252     }
0253 }
0254 
0255 // Calculate and store dates for all solstices and equinoxes
0256 void modCalcEquinox::findSolsticeAndEquinox(uint32_t year)
0257 {
0258     // All the magic numbers are taken from Meeus - 1991 chapter 27
0259     qreal m, jdme[4];
0260     if(year > 1000)
0261     {
0262         m = (year-2000.0) / 1000.0;
0263         jdme[0] = (-0.00057 * qPow(m, 4)) + (-0.00411 * qPow(m, 3)) + (0.05169 * qPow(m, 2)) + (365242.37404 * m) + 2451623.80984;
0264         jdme[1] = (-0.0003 * qPow(m, 4)) + (0.00888 * qPow(m, 3)) + (0.00325 * qPow(m, 2)) + (365241.62603 * m) + 2451716.56767;
0265         jdme[2] = (0.00078 * qPow(m, 4)) + (0.00337 * qPow(m, 3)) + (-0.11575 * qPow(m, 2)) + (365242.01767 * m) + 2451810.21715;
0266         jdme[3] = (0.00032 * qPow(m, 4)) + (-0.00823 * qPow(m, 3)) + (-0.06223 * qPow(m, 2)) + (365242.74049 * m) + 2451900.05952;
0267     }
0268     else
0269     {
0270         m = year / 1000.0;
0271         jdme[0] = (-0.00071 * qPow(m, 4)) + (0.00111 * qPow(m, 3)) + (0.06134 * qPow(m, 2)) + (365242.13740 * m) + 1721139.29189;
0272         jdme[1] = (0.00025 * qPow(m, 4)) + (0.00907 * qPow(m, 3)) + (-0.05323 * qPow(m, 2)) + (365241.72562 * m) + 1721233.25401;
0273         jdme[2] = (0.00074 * qPow(m, 4)) + (-0.00297 * qPow(m, 3)) + (-0.11677 * qPow(m, 2)) + (365242.49558 * m) + 1721325.70455;
0274         jdme[3] = (-0.00006 * qPow(m, 4)) + (-0.00933 * qPow(m, 3)) + (-0.00769 * qPow(m, 2)) + (365242.88257 * m) + 1721414.39987;
0275     }
0276 
0277     qreal t[4];
0278     for(int i = 0; i < 4; i++)
0279     {
0280         t[i] = (jdme[i] - 2451545)/36525;
0281     }
0282 
0283     qreal a[4][24] = {{485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8}, {485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8}, {485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8}, {485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8}};
0284 
0285     qreal b[4][24] = {{324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45}, {324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45}, {324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45}, {324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45}};
0286 
0287     qreal c[] = {1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443, 65928.934, 3034.906, 9037.513, 33718.147, 150.678, 2281.226, 29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029, 31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074};
0288 
0289     qreal d[4][24];
0290 
0291     for (int i = 0; i < 4; i++)
0292     {
0293         for (int j = 0; j < 24; j++)
0294         {
0295             d[i][j] = c[j] * t[i];
0296         }
0297     }
0298 
0299     for (int i = 0; i < 4; i++)
0300     {
0301         for (int j = 0; j < 24; j++)
0302         {
0303             d[i][j] = qCos(qDegreesToRadians(b[i][j] + d[i][j]));
0304         }
0305     }
0306 
0307     for (int i = 0; i < 4; i++)
0308     {
0309         for (int j = 0; j < 24; j++)
0310         {
0311             d[i][j] = d[i][j] * a[i][j];
0312         }
0313     }
0314 
0315     qreal e[4], f[4], g[4], jd[4];
0316 
0317     for (int i = 0; i < 4; i++)
0318     {
0319         e[i] = 0;
0320         for (int j = 0; j < 24; j++)
0321         {
0322             e[i] += d[i][j];
0323         }
0324     }
0325 
0326     for (int i = 0; i < 4; i++)
0327     {
0328         f[i] = (35999.373*t[i]-2.47);
0329     }
0330 
0331     for (int i = 0; i < 4; i++)
0332     {
0333         g[i] = 1 + (0.0334 * qCos(qDegreesToRadians(f[i]))) + (0.0007 * qCos(qDegreesToRadians(2*f[i])));
0334     }
0335 
0336     for (int i = 0; i < 4; i++)
0337     {
0338         jd[i] = jdme[i] + (0.00001*e[i]/g[i]);
0339     }
0340 
0341     // Get correction
0342     qreal correction = FindCorrection(year);
0343 
0344     for (int i = 0; i < 4; i++)
0345     {
0346         KStarsDateTime dt(jd[i]);
0347 
0348         // Apply correction
0349         dt = dt.addSecs(correction);
0350 
0351         switch(i)
0352         {
0353             case 0 :
0354                 dSpring = dt;
0355                 break;
0356             case 1 :
0357                 dSummer = dt;
0358                 break;
0359             case 2 :
0360                 dAutumn = dt;
0361                 break;
0362             case 3 :
0363                 dWinter = dt;
0364                 break;
0365         }
0366     }
0367 }
0368 
0369 qreal modCalcEquinox::FindCorrection(uint32_t year)
0370 {
0371     uint32_t tblFirst = 1620, tblLast = 2002;
0372 
0373     // Corrections taken from Meeus -1991 chapter 10
0374     qreal tbl[] = {/*1620*/ 121,112,103, 95, 88,  82, 77, 72, 68, 63,  60, 56, 53, 51, 48,  46, 44, 42, 40, 38,
0375             /*1660*/  35, 33, 31, 29, 26,  24, 22, 20, 18, 16,  14, 12, 11, 10,  9,   8,  7,  7,  7,  7,
0376             /*1700*/   7,  7,  8,  8,  9,   9,  9,  9,  9, 10,  10, 10, 10, 10, 10,  10, 10, 11, 11, 11,
0377             /*1740*/  11, 11, 12, 12, 12,  12, 13, 13, 13, 14,  14, 14, 14, 15, 15,  15, 15, 15, 16, 16,
0378             /*1780*/  16, 16, 16, 16, 16,  16, 15, 15, 14, 13,
0379             /*1800*/ 13.1, 12.5, 12.2, 12.0, 12.0,  12.0, 12.0, 12.0, 12.0, 11.9,  11.6, 11.0, 10.2,  9.2,  8.2,
0380             /*1830*/  7.1,  6.2,  5.6,  5.4,  5.3,   5.4,  5.6,  5.9,  6.2,  6.5,   6.8,  7.1,  7.3,  7.5,  7.6,
0381             /*1860*/  7.7,  7.3,  6.2,  5.2,  2.7,   1.4, -1.2, -2.8, -3.8, -4.8,  -5.5, -5.3, -5.6, -5.7, -5.9,
0382             /*1890*/ -6.0, -6.3, -6.5, -6.2, -4.7,  -2.8, -0.1,  2.6,  5.3,  7.7,  10.4, 13.3, 16.0, 18.2, 20.2,
0383             /*1920*/ 21.1, 22.4, 23.5, 23.8, 24.3,  24.0, 23.9, 23.9, 23.7, 24.0,  24.3, 25.3, 26.2, 27.3, 28.2,
0384             /*1950*/ 29.1, 30.0, 30.7, 31.4, 32.2,  33.1, 34.0, 35.0, 36.5, 38.3,  40.2, 42.2, 44.5, 46.5, 48.5,
0385             /*1980*/ 50.5, 52.5, 53.8, 54.9, 55.8,  56.9, 58.3, 60.0, 61.6, 63.0,  63.8, 64.3};
0386 
0387     qreal deltaT = 0;
0388     qreal t = (year - 2000.0)/100.0;
0389 
0390     if(year >= tblFirst && year <= tblLast)
0391     {
0392         if(year % 2)
0393         {
0394             deltaT = (tbl[(year-tblFirst-1)/2] + tbl[(year-tblFirst+1)/2]) / 2;
0395         }
0396         else
0397         {
0398             deltaT = tbl[(year-tblFirst)/2];
0399         }
0400     }
0401     else if(year < 948)
0402     {
0403         deltaT = 2177 + 497*t + 44.1*qPow(t, 2);
0404     }
0405     else if(year >= 948)
0406     {
0407         deltaT =  102 + 102*t + 25.3*qPow(t, 2);
0408         if (year>=2000 && year <=2100)
0409         {
0410             // Special correction to avoid discontinuity in 2000
0411             deltaT += 0.37 * ( year - 2100.0 );
0412         }
0413     }
0414     return -deltaT;
0415 }