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

0001 /*
0002     SPDX-FileCopyrightText: 2005 Jason Harris <kstars@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "cometscomponent.h"
0008 
0009 #ifndef KSTARS_LITE
0010 #include "kstars.h"
0011 #endif
0012 #include "ksfilereader.h"
0013 #include "kspaths.h"
0014 #include "kstarsdata.h"
0015 #include "ksutils.h"
0016 #include "ksnotification.h"
0017 #include "kstars_debug.h"
0018 #ifndef KSTARS_LITE
0019 #include "skymap.h"
0020 #else
0021 #include "kstarslite.h"
0022 #endif
0023 #include "Options.h"
0024 #include "skylabeler.h"
0025 #include "skypainter.h"
0026 #include "solarsystemcomposite.h"
0027 #include "auxiliary/filedownloader.h"
0028 #include "auxiliary/kspaths.h"
0029 #include "projections/projector.h"
0030 #include "skyobjects/kscomet.h"
0031 
0032 #include <QFile>
0033 #include <QHttpMultiPart>
0034 #include <QPen>
0035 #include <QStandardPaths>
0036 
0037 #include <cmath>
0038 
0039 CometsComponent::CometsComponent(SolarSystemComposite *parent)
0040     : SolarSystemListComponent(parent)
0041 {
0042     loadData();
0043 }
0044 
0045 bool CometsComponent::selected()
0046 {
0047     return Options::showComets();
0048 }
0049 
0050 /*
0051  * @short Initialize the comets list.
0052  * Reads in the comets data from the comets.dat file.
0053  *
0054  * Populate the list of Comets from the data file.
0055  * The data file is a CSV file with the following columns :
0056  * @li 1 full name [string]
0057  * @li 2 modified julian day of orbital elements [int]
0058  * @li 3 perihelion distance in AU [double]
0059  * @li 4 eccentricity of orbit [double]
0060  * @li 5 inclination angle of orbit in degrees [double]
0061  * @li 6 argument of perihelion in degrees [double]
0062  * @li 7 longitude of the ascending node in degrees [double]
0063  * @li 8 time of perihelion passage (YYYYMMDD.DDD) [double]
0064  * @li 9 orbit solution ID [string]
0065  * @li 10 Near-Earth Object (NEO) flag [bool]
0066  * @li 11 comet total magnitude parameter [float]
0067  * @li 12 comet nuclear magnitude parameter [float]
0068  * @li 13 object diameter (from equivalent sphere) [float]
0069  * @li 14 object bi/tri-axial ellipsoid dimensions [string]
0070  * @li 15 geometric albedo [float]
0071  * @li 16 rotation period [float]
0072  * @li 17 orbital period [float]
0073  * @li 18 earth minimum orbit intersection distance [double]
0074  * @li 19 orbit classification [string]
0075  * @li 20 comet total magnitude slope parameter
0076  * @li 21 comet nuclear magnitude slope parameter
0077  * @note See KSComet constructor for more details.
0078  */
0079 void CometsComponent::loadData()
0080 {
0081     QString name, orbit_class;
0082 
0083     emitProgressText(i18n("Loading comets"));
0084     qCInfo(KSTARS) << "Loading comets";
0085 
0086     qDeleteAll(m_ObjectList);
0087     m_ObjectList.clear();
0088 
0089     objectNames(SkyObject::COMET).clear();
0090     objectLists(SkyObject::COMET).clear();
0091 
0092     QString file_name = KSPaths::locate(QStandardPaths::AppLocalDataLocation, QString("cometels.json.gz"));
0093 
0094     try
0095     {
0096         KSUtils::MPCParser com_parser(file_name);
0097         com_parser.for_each(
0098             [&](const auto & get)
0099         {
0100             KSComet *com = nullptr;
0101             name = get("Designation_and_name").toString();
0102 
0103             int perihelion_year, perihelion_month, perihelion_day, perihelion_hour, perihelion_minute, perihelion_second;
0104 
0105             // Perihelion Distance in AU
0106             double perihelion_distance = get("Perihelion_dist").toDouble();
0107             // Orbital Eccentricity
0108             double eccentricity = get("e").toDouble();
0109             // Argument of perihelion, J2000.0 (degrees)
0110             double perihelion_argument = get("Peri").toDouble();
0111             // Longitude of the ascending node, J2000.0 (degrees)
0112             double ascending_node = get("Node").toDouble();
0113             // Inclination in degrees, J2000.0 (degrees)
0114             double inclination = get("i").toDouble();
0115 
0116             // Perihelion Date
0117             perihelion_year = get("Year_of_perihelion").toInt();
0118             perihelion_month = get("Month_of_perihelion").toInt();
0119             // Stored as double in MPC
0120             double peri_day = get("Day_of_perihelion").toDouble();
0121             perihelion_day = static_cast<int>(peri_day);
0122             double peri_hour = (peri_day - perihelion_day) * 24;
0123             perihelion_hour = static_cast<int>(peri_hour);
0124             perihelion_minute = static_cast<int>((peri_hour - perihelion_hour) * 60);
0125             perihelion_second = ( (( peri_hour - perihelion_hour) * 60) - perihelion_minute) * 60;
0126 
0127             long double Tp = KStarsDateTime(QDate(perihelion_year, perihelion_month, perihelion_day),
0128                                             QTime(perihelion_hour, perihelion_minute, perihelion_second)).djd();
0129 
0130             // Orbit type
0131             orbit_class = get("Orbit_type").toString();
0132             double absolute_magnitude = get("H").toDouble();
0133             double slope_parameter = get("G").toDouble();
0134 
0135             com = new KSComet(name,
0136                               QString(),
0137                               perihelion_distance,
0138                               eccentricity,
0139                               dms(inclination),
0140                               dms(perihelion_argument),
0141                               dms(ascending_node),
0142                               Tp,
0143                               absolute_magnitude,
0144                               101.0,
0145                               slope_parameter,
0146                               101.0);
0147 
0148             com->setOrbitClass(orbit_class);
0149             com->setAngularSize(0.005);
0150             appendListObject(com);
0151 
0152             // Add *short* name to the list of object names
0153             objectNames(SkyObject::COMET).append(com->name());
0154             objectLists(SkyObject::COMET).append(QPair<QString, const SkyObject *>(com->name(), com));
0155         });
0156     }
0157     catch (const std::runtime_error&)
0158     {
0159         qCInfo(KSTARS) << "Loading comets failed.";
0160         qCInfo(KSTARS) << " -> was trying to read " + file_name;
0161         return;
0162     }
0163 }
0164 
0165 // Used for JPL Data
0166 // DO NOT REMOVE, we can revert to JPL at any time.
0167 //void CometsComponent::loadData()
0168 //{
0169 //    QString name, orbit_id, orbit_class, dimensions;
0170 
0171 //    emitProgressText(i18n("Loading comets"));
0172 //    qCInfo(KSTARS) << "Loading comets";
0173 
0174 //    qDeleteAll(m_ObjectList);
0175 //    m_ObjectList.clear();
0176 
0177 //    objectNames(SkyObject::COMET).clear();
0178 //    objectLists(SkyObject::COMET).clear();
0179 
0180 //    QString file_name =
0181 //        KSPaths::locate(QStandardPaths::AppLocalDataLocation, QString("comets.dat"));
0182 
0183 //    try
0184 //    {
0185 //        KSUtils::JPLParser com_parser(file_name);
0186 //        com_parser.for_each(
0187 //            [&](const auto &get)
0188 //            {
0189 //                KSComet *com = nullptr;
0190 //                name         = get("full_name").toString();
0191 //                name         = name.trimmed();
0192 //                bool neo;
0193 //                double q, e, dble_i, dble_w, dble_N, Tp, earth_moid;
0194 //                float M1, M2, K1, K2, diameter, albedo, rot_period, period;
0195 //                q        = get("q").toString().toDouble();
0196 //                e        = get("e").toString().toDouble();
0197 //                dble_i   = get("i").toString().toDouble();
0198 //                dble_w   = get("w").toString().toDouble();
0199 //                dble_N   = get("om").toString().toDouble();
0200 //                Tp       = get("tp").toString().toDouble();
0201 //                orbit_id = get("orbit_id").toString();
0202 //                neo      = get("neo").toString() == "Y";
0203 
0204 //                if (get("M1").toString().toFloat() == 0.0)
0205 //                    M1 = 101.0;
0206 //                else
0207 //                    M1 = get("M1").toString().toFloat();
0208 
0209 //                if (get("M2").toString().toFloat() == 0.0)
0210 //                    M2 = 101.0;
0211 //                else
0212 //                    M2 = get("M2").toString().toFloat();
0213 
0214 //                diameter    = get("diameter").toString().toFloat();
0215 //                dimensions  = get("extent").toString();
0216 //                albedo      = get("albedo").toString().toFloat();
0217 //                rot_period  = get("rot_per").toString().toFloat();
0218 //                period      = get("per.y").toDouble();
0219 //                earth_moid  = get("moid").toString().toDouble();
0220 //                orbit_class = get("class").toString();
0221 //                K1          = get("H").toString().toFloat();
0222 //                K2          = get("G").toString().toFloat();
0223 
0224 //                com = new KSComet(name, QString(), q, e, dms(dble_i), dms(dble_w),
0225 //                                  dms(dble_N), Tp, M1, M2, K1, K2);
0226 //                com->setOrbitID(orbit_id);
0227 //                com->setNEO(neo);
0228 //                com->setDiameter(diameter);
0229 //                com->setDimensions(dimensions);
0230 //                com->setAlbedo(albedo);
0231 //                com->setRotationPeriod(rot_period);
0232 //                com->setPeriod(period);
0233 //                com->setEarthMOID(earth_moid);
0234 //                com->setOrbitClass(orbit_class);
0235 //                com->setAngularSize(0.005);
0236 //                appendListObject(com);
0237 
0238 //                // Add *short* name to the list of object names
0239 //                objectNames(SkyObject::COMET).append(com->name());
0240 //                objectLists(SkyObject::COMET)
0241 //                    .append(QPair<QString, const SkyObject *>(com->name(), com));
0242 //            });
0243 //    }
0244 //    catch (const std::runtime_error &e)
0245 //    {
0246 //        qCInfo(KSTARS) << "Loading comets failed.";
0247 //        qCInfo(KSTARS) << " -> was trying to read " + file_name;
0248 //        return;
0249 //    }
0250 //}
0251 
0252 void CometsComponent::draw(SkyPainter *skyp)
0253 {
0254     Q_UNUSED(skyp)
0255 #ifndef KSTARS_LITE
0256     if (!selected() || Options::zoomFactor() < 1 * MINZOOM)
0257         return;
0258 
0259     bool hideLabels       = !Options::showCometNames() || (SkyMap::Instance()->isSlewing() && Options::hideLabels());
0260     double rsunLabelLimit = Options::maxRadCometName();
0261 
0262     //FIXME: Should these be config'able?
0263     skyp->setPen(QPen(QColor("transparent")));
0264     skyp->setBrush(QBrush(QColor("white")));
0265 
0266     for (auto so : m_ObjectList)
0267     {
0268         KSComet *com = dynamic_cast<KSComet *>(so);
0269         double mag   = com->mag();
0270         if (std::isnan(mag) == 0)
0271         {
0272             bool drawn = skyp->drawComet(com);
0273             if (drawn && !(hideLabels || com->rsun() >= rsunLabelLimit))
0274                 SkyLabeler::AddLabel(com, SkyLabeler::COMET_LABEL);
0275         }
0276     }
0277 #endif
0278 }
0279 
0280 // DO NOT REMOVE
0281 //void CometsComponent::updateDataFile(bool isAutoUpdate)
0282 //{
0283 //    delete (downloadJob);
0284 //    downloadJob = new FileDownloader();
0285 
0286 //    if (isAutoUpdate == false)
0287 //        downloadJob->setProgressDialogEnabled(true, i18n("Comets Update"),
0288 //                                              i18n("Downloading comets updates..."));
0289 //    downloadJob->registerDataVerification([&](const QByteArray &data)
0290 //                                          { return data.startsWith("{\"signature\""); });
0291 
0292 //    connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady()));
0293 
0294 //    // For auto-update, we ignore errors
0295 //    if (isAutoUpdate == false)
0296 //        connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));
0297 
0298 //    QUrl url             = QUrl("https://ssd-api.jpl.nasa.gov/sbdb_query.api");
0299 //    QByteArray post_data = KSUtils::getJPLQueryString(
0300 //        "c",
0301 //        "full_name,epoch.mjd,q,e,i,w,om,tp,orbit_id,neo,"
0302 //        "M1,M2,diameter,extent,albedo,rot_per,per.y,moid,H,G,class",
0303 //        QVector<KSUtils::JPLFilter>{});
0304 //    // FIXME: find out what { "Af", "!=", "D" } used to mean
0305 
0306 //    downloadJob->post(url, post_data);
0307 //}
0308 
0309 void CometsComponent::updateDataFile(bool isAutoUpdate)
0310 {
0311     delete (downloadJob);
0312     downloadJob = new FileDownloader();
0313 
0314     if (isAutoUpdate == false)
0315         downloadJob->setProgressDialogEnabled(true, i18n("Comets Update"),
0316                                               i18n("Downloading comets updates..."));
0317 
0318     connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady()));
0319 
0320     // For auto-update, we ignore errors
0321     if (isAutoUpdate == false)
0322         connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));
0323 
0324     QUrl url = QUrl("https://www.minorplanetcenter.net/Extended_Files/cometels.json.gz");
0325     downloadJob->get(url);
0326 }
0327 
0328 void CometsComponent::downloadReady()
0329 {
0330     // Comment the first line
0331     QByteArray data = downloadJob->downloadedData();
0332 
0333     // Write data to cometels.json.gz
0334     QFile file(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
0335                .filePath("cometels.json.gz"));
0336     if (file.open(QIODevice::WriteOnly))
0337     {
0338         file.write(data);
0339         file.close();
0340     }
0341     else
0342         qCWarning(KSTARS) << "Failed writing comet data to" << file.fileName();
0343 
0344     QString focusedComet;
0345 
0346 #ifdef KSTARS_LITE
0347     SkyObject *foc = KStarsLite::Instance()->map()->focusObject();
0348     if (foc && foc->type() == SkyObject::COMET)
0349     {
0350         focusedComet = foc->name();
0351         KStarsLite::Instance()->map()->setFocusObject(nullptr);
0352     }
0353 #else
0354     SkyObject *foc = KStars::Instance()->map()->focusObject();
0355     if (foc && foc->type() == SkyObject::COMET)
0356     {
0357         focusedComet = foc->name();
0358         KStars::Instance()->map()->setFocusObject(nullptr);
0359     }
0360 #endif
0361 
0362     // Reload comets
0363     loadData();
0364 
0365 #ifdef KSTARS_LITE
0366     KStarsLite::Instance()->data()->setFullTimeUpdate();
0367     if (!focusedComet.isEmpty())
0368         KStarsLite::Instance()->map()->setFocusObject(
0369             KStarsLite::Instance()->data()->objectNamed(focusedComet));
0370 #else
0371     if (!focusedComet.isEmpty())
0372         KStars::Instance()->map()->setFocusObject(
0373             KStars::Instance()->data()->objectNamed(focusedComet));
0374     KStars::Instance()->data()->setFullTimeUpdate();
0375 #endif
0376 
0377     downloadJob->deleteLater();
0378 }
0379 
0380 // DO NOT REMOVE
0381 //void CometsComponent::downloadReady()
0382 //{
0383 //    // Comment the first line
0384 //    QByteArray data = downloadJob->downloadedData();
0385 
0386 //    // Write data to comets.dat
0387 //    QFile file(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
0388 //                   .filePath("comets.dat"));
0389 //    if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
0390 //    {
0391 //        file.write(data);
0392 //        file.close();
0393 //    }
0394 //    else
0395 //        qCWarning(KSTARS) << "Failed writing comet data to" << file.fileName();
0396 
0397 //    QString focusedComet;
0398 
0399 //#ifdef KSTARS_LITE
0400 //    SkyObject *foc = KStarsLite::Instance()->map()->focusObject();
0401 //    if (foc && foc->type() == SkyObject::COMET)
0402 //    {
0403 //        focusedComet = foc->name();
0404 //        KStarsLite::Instance()->map()->setFocusObject(nullptr);
0405 //    }
0406 //#else
0407 //    SkyObject *foc = KStars::Instance()->map()->focusObject();
0408 //    if (foc && foc->type() == SkyObject::COMET)
0409 //    {
0410 //        focusedComet = foc->name();
0411 //        KStars::Instance()->map()->setFocusObject(nullptr);
0412 //    }
0413 //#endif
0414 
0415 //    // Reload comets
0416 //    loadData();
0417 
0418 //#ifdef KSTARS_LITE
0419 //    KStarsLite::Instance()->data()->setFullTimeUpdate();
0420 //    if (!focusedComet.isEmpty())
0421 //        KStarsLite::Instance()->map()->setFocusObject(
0422 //            KStarsLite::Instance()->data()->objectNamed(focusedComet));
0423 //#else
0424 //    if (!focusedComet.isEmpty())
0425 //        KStars::Instance()->map()->setFocusObject(
0426 //            KStars::Instance()->data()->objectNamed(focusedComet));
0427 //    KStars::Instance()->data()->setFullTimeUpdate();
0428 //#endif
0429 
0430 //    downloadJob->deleteLater();
0431 //}
0432 
0433 void CometsComponent::downloadError(const QString &errorString)
0434 {
0435     KSNotification::error(i18n("Error downloading asteroids data: %1", errorString));
0436     qCCritical(KSTARS) << errorString;
0437     downloadJob->deleteLater();
0438 }