File indexing completed on 2024-07-14 07:17:25

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     Based on Samikshan Bairagya GSoC work.
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "supernovaecomponent.h"
0010 
0011 #ifndef KSTARS_LITE
0012 #include "kstars.h"
0013 #include "skymap.h"
0014 #else
0015 #include "kstarslite.h"
0016 #endif
0017 #include "dms.h"
0018 #include "kstars_debug.h"
0019 #include "ksnotification.h"
0020 #include "kstarsdata.h"
0021 #include "Options.h"
0022 #include "skylabeler.h"
0023 #include "skymesh.h"
0024 #include "skypainter.h"
0025 #include "auxiliary/filedownloader.h"
0026 #include "projections/projector.h"
0027 #include "auxiliary/kspaths.h"
0028 
0029 #include <QtConcurrent>
0030 #include <QJsonDocument>
0031 #include <QJsonValue>
0032 
0033 #include <zlib.h>
0034 #include <fstream>
0035 #include <stdio.h>
0036 
0037 #include <csv.h>
0038 
0039 const QString SupernovaeComponent::tnsDataFilename("tns_public_objects.csv");
0040 const QString SupernovaeComponent::tnsDataFilenameZip("tns-daily.csv.gz");
0041 const QString SupernovaeComponent::tnsDataUrl(
0042     "https://indilib.org/jdownloads/kstars/tns-daily.csv.gz");
0043 
0044 SupernovaeComponent::SupernovaeComponent(SkyComposite *parent) : ListComponent(parent)
0045 {
0046     //QtConcurrent::run(this, &SupernovaeComponent::loadData);
0047     //loadData(); MagnitudeLimitShowSupernovae
0048     connect(Options::self(), &Options::SupernovaDownloadUrlChanged, this,
0049             &SupernovaeComponent::loadData);
0050     connect(Options::self(), &Options::MagnitudeLimitShowSupernovaeChanged, this,
0051             &SupernovaeComponent::loadData);
0052 }
0053 
0054 void SupernovaeComponent::update(KSNumbers *num)
0055 {
0056     if (!selected() || !m_DataLoaded)
0057         return;
0058 
0059     KStarsData *data = KStarsData::Instance();
0060     for (auto so : m_ObjectList)
0061     {
0062         if (num)
0063             so->updateCoords(num);
0064         so->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0065     }
0066 }
0067 
0068 bool SupernovaeComponent::selected()
0069 {
0070     return Options::showSupernovae();
0071 }
0072 
0073 void SupernovaeComponent::loadData()
0074 {
0075     qDeleteAll(m_ObjectList);
0076     m_ObjectList.clear();
0077 
0078     objectNames(SkyObject::SUPERNOVA).clear();
0079     objectLists(SkyObject::SUPERNOVA).clear();
0080 
0081     auto sFileName = KSPaths::locate(QStandardPaths::AppLocalDataLocation, QString(tnsDataFilename));
0082 
0083     try
0084     {
0085         io::CSVReader<26, io::trim_chars<' '>, io::double_quote_escape<',', '\"'>,
0086         io::ignore_overflow>
0087         in(sFileName.toLocal8Bit());
0088         // skip header
0089         const char *line = in.next_line();
0090         if (line == nullptr)
0091         {
0092             qCritical() << "file is empty\n";
0093             return;
0094         }
0095 
0096         std::string id, name, ra_s, dec_s, type;
0097         double redshift;
0098         std::string host_name;
0099         double host_redshift;
0100         std::string reporting_group, disc_datasource, classifying_group, assoc_group,
0101             disc_internal_name, disc_instrument, classifying_instrument;
0102         int tns_at, is_public;
0103         std::string end_prop_period;
0104         double discovery_mag;
0105         std::string discovery_filter, discovery_date_s, sender, remarks,
0106             discovery_bibcode, classification_bibcode, ext_catalog;
0107 
0108         while (in.read_row(
0109                     id, name, ra_s, dec_s, type, redshift, host_name, host_redshift,
0110                     reporting_group, disc_datasource, classifying_group, assoc_group,
0111                     disc_internal_name, disc_instrument, classifying_instrument, tns_at,
0112                     is_public, end_prop_period, discovery_mag, discovery_filter, discovery_date_s,
0113                     sender, remarks, discovery_bibcode, classification_bibcode, ext_catalog))
0114         {
0115             auto discovery_date =
0116                 QDateTime::fromString(discovery_date_s.c_str(), Qt::ISODate);
0117             QString qname = QString(name.c_str());
0118             dms ra(QString(ra_s.c_str()), false);
0119             dms dec(QString(dec_s.c_str()), true);
0120 
0121             Supernova *sup = new Supernova(
0122                 qname, ra, dec, QString(type.c_str()), QString(host_name.c_str()),
0123                 QString(discovery_date_s.c_str()), redshift, discovery_mag, discovery_date);
0124 
0125             objectNames(SkyObject::SUPERNOVA).append(QString(name.c_str()));
0126 
0127             appendListObject(sup);
0128             objectLists(SkyObject::SUPERNOVA)
0129             .append(QPair<QString, const SkyObject *>(qname, sup));
0130         }
0131 
0132         m_DataLoading = false;
0133         m_DataLoaded  = true;
0134     }
0135     catch (io::error::can_not_open_file &ex)
0136     {
0137         qCCritical(KSTARS) << "could not open file " << sFileName.toLocal8Bit() << "\n";
0138         return;
0139     }
0140     catch (std::exception &ex)
0141     {
0142         qCCritical(KSTARS) << "unknown exception happened:" << ex.what() << "\n";
0143     }
0144 }
0145 
0146 SkyObject *SupernovaeComponent::objectNearest(SkyPoint *p, double &maxrad)
0147 {
0148     if (!selected() || !m_DataLoaded)
0149         return nullptr;
0150 
0151     SkyObject *oBest = nullptr;
0152     double rBest     = maxrad;
0153 
0154     for (auto &so : m_ObjectList)
0155     {
0156         double r = so->angularDistanceTo(p).Degrees();
0157         //qDebug()<<r;
0158         if (r < rBest)
0159         {
0160             oBest = so;
0161             rBest = r;
0162         }
0163     }
0164     maxrad = rBest;
0165     return oBest;
0166 }
0167 
0168 float SupernovaeComponent::zoomMagnitudeLimit()
0169 {
0170     //adjust maglimit for ZoomLevel
0171     double lgmin = log10(MINZOOM);
0172     double lgz   = log10(Options::zoomFactor());
0173 
0174     return 14.0 + 2.222 * (lgz - lgmin) +
0175            2.222 * log10(static_cast<double>(Options::starDensity()));
0176 }
0177 
0178 void SupernovaeComponent::draw(SkyPainter *skyp)
0179 {
0180     //qDebug()<<"selected()="<<selected();
0181     if (!selected())
0182         return;
0183     else if (!m_DataLoaded)
0184     {
0185         if (!m_DataLoading)
0186         {
0187             m_DataLoading = true;
0188             QtConcurrent::run(this, &SupernovaeComponent::loadData);
0189         }
0190         return;
0191     }
0192 
0193     double maglim = zoomMagnitudeLimit();
0194     double refage = Options::supernovaDetectionAge();
0195     bool hostOnly = Options::supernovaeHostOnly();
0196     bool classifiedOnly = Options::supernovaeClassifiedOnly();
0197 
0198     for (auto so : m_ObjectList)
0199     {
0200         Supernova *sup = dynamic_cast<Supernova *>(so);
0201         float mag      = sup->mag();
0202         float age      = sup->getAgeDays();
0203         QString type     = sup->getType();
0204 
0205         if (mag > float(Options::magnitudeLimitShowSupernovae()))
0206             continue;
0207 
0208         if (age > refage)
0209             continue;
0210 
0211         // only SN with host galaxy?
0212         if (hostOnly && sup->getHostGalaxy() == "")
0213             continue;
0214 
0215         // Do not draw if mag>maglim
0216         if (mag > maglim && Options::limitSupernovaeByZoom())
0217             continue;
0218 
0219         // classified SN only?
0220         if (classifiedOnly && type == "")
0221             continue;
0222 
0223         skyp->drawSupernova(sup);
0224     }
0225 }
0226 
0227 #if 0
0228 void SupernovaeComponent::notifyNewSupernovae()
0229 {
0230 #ifndef KSTARS_LITE
0231     //qDebug()<<"New Supernovae discovered";
0232     QList<SkyObject *> latestList;
0233     foreach (SkyObject * so, latest)
0234     {
0235         Supernova * sup = (Supernova *)so;
0236 
0237         if (sup->getMagnitude() > float(Options::magnitudeLimitAlertSupernovae()))
0238         {
0239             //qDebug()<<"Not Bright enough to be notified";
0240             continue;
0241         }
0242 
0243         //qDebug()<<"Bright enough to be notified";
0244         latestList.append(so);
0245     }
0246     if (!latestList.empty())
0247     {
0248         NotifyUpdatesUI * ui = new NotifyUpdatesUI(0);
0249         ui->addItems(latestList);
0250         ui->show();
0251     }
0252     //     if (!latest.empty())
0253     //         KMessageBox::informationList(0, i18n("New Supernovae discovered!"), latestList, i18n("New Supernovae discovered!"));
0254 #endif
0255 }
0256 #endif
0257 
0258 void SupernovaeComponent::slotTriggerDataFileUpdate()
0259 {
0260     delete (downloadJob);
0261     downloadJob    = new FileDownloader();
0262     auto shownames = Options::showSupernovaNames();
0263     auto age       = Options::supernovaDetectionAge();
0264     QString url    = Options::supernovaDownloadUrl();
0265     QString output = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
0266                      .filePath(tnsDataFilenameZip);
0267 
0268     if (!url.startsWith("file://"))
0269     {
0270         qInfo() << "fetching data from web: " << url << "\n";
0271         downloadJob->setProgressDialogEnabled(true, i18n("Supernovae Update"),
0272                                               i18n("Downloading Supernovae updates..."));
0273 
0274         QObject::connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady()));
0275         QObject::connect(downloadJob, SIGNAL(error(QString)), this,
0276                          SLOT(downloadError(QString)));
0277 
0278         downloadJob->setDownloadedFileURL(QUrl::fromLocalFile(output));
0279         downloadJob->get(QUrl(url));
0280     }
0281     else
0282     {
0283         // if we have a local file as url
0284         // copy data manually to target location
0285         QString fname = url.right(url.size() - 7);
0286         qInfo() << "fetching data from local file at: " << fname << "\n";
0287         QFile::remove(fname);
0288         auto res = QFile::copy(fname, output);
0289         qInfo() << "copy returned: " << res << "\n";
0290         // uncompress csv
0291         unzipData();
0292         // Reload Supernova
0293         loadData();
0294     }
0295 }
0296 
0297 void SupernovaeComponent::unzipData()
0298 {
0299     // TODO: error handling
0300     std::string ifpath =
0301         QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
0302         .filePath(tnsDataFilenameZip)
0303         .toStdString();
0304     std::string ofpath =
0305         QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
0306         .filePath(tnsDataFilename)
0307         .toStdString();
0308     auto fhz  = gzopen(ifpath.c_str(), "rb");
0309     auto fout = fopen(ofpath.c_str(), "wb");
0310 
0311     if (fhz == NULL)
0312     {
0313         printf("Error: Failed to gzopen %s\n", ifpath.c_str());
0314         exit(0);
0315     }
0316     unsigned char unzipBuffer[8192];
0317     unsigned int unzippedBytes;
0318     while (true)
0319     {
0320         unzippedBytes = gzread(fhz, unzipBuffer, 8192);
0321         if (unzippedBytes > 0)
0322         {
0323             fwrite(unzipBuffer, 1, unzippedBytes, fout);
0324         }
0325         else
0326         {
0327             break;
0328         }
0329     }
0330     gzclose(fhz);
0331     fclose(fout);
0332 }
0333 
0334 void SupernovaeComponent::downloadReady()
0335 {
0336     // uncompress csv
0337     unzipData();
0338     // Reload Supernova
0339     loadData();
0340 #ifdef KSTARS_LITE
0341     KStarsLite::Instance()->data()->setFullTimeUpdate();
0342 #else
0343     KStars::Instance()->data()->setFullTimeUpdate();
0344 #endif
0345     downloadJob->deleteLater();
0346 }
0347 
0348 void SupernovaeComponent::downloadError(const QString &errorString)
0349 {
0350     KSNotification::error(i18n("Error downloading supernova data: %1", errorString));
0351     downloadJob->deleteLater();
0352 }