File indexing completed on 2024-04-14 14:08:56

0001 /*
0002     SPDX-FileCopyrightText: 2016 Akarsh Simha <akarsh.simha@kdemail.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ksdssdownloader.h"
0008 
0009 #include "Options.h"
0010 #include "auxiliary/filedownloader.h"
0011 #include "catalogobject.h"
0012 
0013 #include <QImageWriter>
0014 #include <QMimeDatabase>
0015 
0016 
0017 KSDssDownloader::KSDssDownloader(QObject *parent) : QObject(parent)
0018 {
0019     connect(this, &KSDssDownloader::downloadCanceled, this, [&]() { deleteLater(); });
0020     m_VersionPreference << "poss2ukstu_blue"
0021                         << "poss2ukstu_red"
0022                         << "poss1_blue"
0023                         << "poss1_red"
0024                         << "quickv"
0025                         << "poss2ukstu_ir";
0026     m_TempFile.open();
0027 }
0028 
0029 KSDssDownloader::KSDssDownloader(const SkyPoint *const p, const QString &destFileName,
0030                                  const std::function<void(bool)> &slotDownloadReady, QObject *parent)
0031     : QObject(parent)
0032 {
0033     // Initialize version preferences. FIXME: This must be made a
0034     // user-changeable option just in case someone likes red
0035 
0036     connect(this, &KSDssDownloader::downloadCanceled, this, [&]() { deleteLater(); });
0037 
0038     m_VersionPreference << "poss2ukstu_blue"
0039                         << "poss2ukstu_red"
0040                         << "poss1_blue"
0041                         << "poss1_red"
0042                         << "quickv"
0043                         << "poss2ukstu_ir";
0044     m_TempFile.open();
0045     connect(this, &KSDssDownloader::downloadComplete, slotDownloadReady);
0046     startDownload(p, destFileName);
0047 }
0048 
0049 QString KSDssDownloader::getDSSURL(const SkyPoint *const p, const QString &version, struct KSDssImage::Metadata *md)
0050 {
0051     double height, width;
0052 
0053     double dss_default_size = Options::defaultDSSImageSize();
0054     double dss_padding      = Options::dSSPadding();
0055 
0056     Q_ASSERT(p);
0057     Q_ASSERT(dss_default_size > 0.0 && dss_padding >= 0.0);
0058 
0059     const auto * dso = dynamic_cast<const CatalogObject *>(p);
0060 
0061     // Decide what to do about the height and width
0062     if (dso)
0063     {
0064         // For deep-sky objects, use their height and width information
0065         double a, b, pa;
0066         a = dso->a();
0067         b = dso->a() *
0068             dso->e(); // Use a * e instead of b, since e() returns 1 whenever one of the dimensions is zero. This is important for circular objects
0069         Q_ASSERT(a >= b);
0070         pa = dso->pa() * dms::DegToRad;
0071 
0072         // We now want to convert a, b, and pa into an image
0073         // height and width -- i.e. a dRA and dDec.
0074         // DSS uses dDec for height and dRA for width. (i.e. "top" is north in the DSS images, AFAICT)
0075         // From some trigonometry, assuming we have a rectangular object (worst case), we need:
0076         width  = a * sin(pa) + b * fabs(cos(pa));
0077         height = a * fabs(cos(pa)) + b * sin(pa);
0078         // 'a' and 'b' are in arcminutes, so height and width are in arcminutes
0079 
0080         // Pad the RA and Dec, so that we show more of the sky than just the object.
0081         height += dss_padding;
0082         width += dss_padding;
0083     }
0084     else
0085     {
0086         // For a generic sky object, we don't know what to do. So
0087         // we just assume the default size.
0088         height = width = dss_default_size;
0089     }
0090     // There's no point in tiny DSS images that are smaller than dss_default_size
0091     if (height < dss_default_size)
0092         height = dss_default_size;
0093     if (width < dss_default_size)
0094         width = dss_default_size;
0095 
0096     return getDSSURL(p, width, height, version, md);
0097 }
0098 
0099 QString KSDssDownloader::getDSSURL(const SkyPoint *const p, float width, float height, const QString &version,
0100                                    struct KSDssImage::Metadata *md)
0101 {
0102     Q_ASSERT(p);
0103     if (!p)
0104         return QString();
0105     if (width <= 0)
0106         return QString();
0107     if (height <= 0)
0108         height = width;
0109     QString URL = getDSSURL(p->ra0(), p->dec0(), width, height, "gif", version, md);
0110     if (md)
0111     {
0112         const SkyObject *obj = nullptr;
0113         obj                  = dynamic_cast<const SkyObject *>(p);
0114         if (obj && obj->hasName())
0115             md->object = obj->name();
0116     }
0117     return URL;
0118 }
0119 
0120 QString KSDssDownloader::getDSSURL(const dms &ra, const dms &dec, float width, float height, const QString &type_,
0121                                    const QString &version_, struct KSDssImage::Metadata *md)
0122 {
0123     const double dss_default_size = Options::defaultDSSImageSize();
0124 
0125     QString version   = version_.toLower();
0126     QString type      = type_.toLower();
0127     QString URLprefix = QString("https://archive.stsci.edu/cgi-bin/dss_search?v=%1&").arg(version);
0128     QString URLsuffix = QString("&e=J2000&f=%1&c=none&fov=NONE").arg(type);
0129 
0130     char decsgn = (dec.Degrees() < 0.0) ? '-' : '+';
0131     int dd      = abs(dec.degree());
0132     int dm      = abs(dec.arcmin());
0133     int ds      = abs(dec.arcsec());
0134 
0135     // Infinite and NaN sizes are replaced by the default size and tiny DSS images are resized to default size
0136     if (!qIsFinite(height) || height <= 0.0)
0137         height = dss_default_size;
0138     if (!qIsFinite(width) || width <= 0.0)
0139         width = dss_default_size;
0140 
0141     // DSS accepts images that are no larger than 75 arcminutes
0142     if (height > 75.0)
0143         height = 75.0;
0144     if (width > 75.0)
0145         width = 75.0;
0146 
0147     QString RAString, DecString, SizeString;
0148     DecString  = QString::asprintf("&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds);
0149     RAString   = QString::asprintf("r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second());
0150     SizeString  = QString::asprintf("&h=%02.1f&w=%02.1f", height, width);
0151 
0152     if (md)
0153     {
0154         md->src     = KSDssImage::Metadata::DSS;
0155         md->width   = width;
0156         md->height  = height;
0157         md->ra0     = ra;
0158         md->dec0    = dec;
0159         md->version = version;
0160         md->format  = (type.contains("fit") ? KSDssImage::Metadata::FITS : KSDssImage::Metadata::GIF);
0161         md->height  = height;
0162         md->width   = width;
0163 
0164         if (version.contains("poss2"))
0165             md->gen = 2;
0166         else if (version.contains("poss1"))
0167             md->gen = 1;
0168         else if (version.contains("quickv"))
0169             md->gen = 4;
0170         else
0171             md->gen = -1;
0172 
0173         if (version.contains("red"))
0174             md->band = 'R';
0175         else if (version.contains("blue"))
0176             md->band = 'B';
0177         else if (version.contains("ir"))
0178             md->band = 'I';
0179         else if (version.contains("quickv"))
0180             md->band = 'V';
0181         else
0182             md->band = '?';
0183 
0184         md->object = QString();
0185     }
0186 
0187     return (URLprefix + RAString + DecString + SizeString + URLsuffix);
0188 }
0189 
0190 void KSDssDownloader::initiateSingleDownloadAttempt(QUrl srcUrl)
0191 {
0192     qDebug() << Q_FUNC_INFO << "Temp file is at " << m_TempFile.fileName();
0193     QUrl fileUrl = QUrl::fromLocalFile(m_TempFile.fileName());
0194     qDebug() << Q_FUNC_INFO << "Attempt #" << m_attempt << "downloading DSS Image. URL: " << srcUrl << " to " << fileUrl;
0195     //m_DownloadJob = KIO::copy( srcUrl, fileUrl, KIO::Overwrite ) ; // FIXME: Can be done with pure Qt
0196     //connect ( m_DownloadJob, SIGNAL (result(KJob*)), SLOT (downloadAttemptFinished()) );
0197 
0198     downloadJob = new FileDownloader();
0199 
0200     downloadJob->setProgressDialogEnabled(true, i18n("DSS Download"),
0201                                           i18n("Please wait while DSS image is being downloaded..."));
0202     connect(downloadJob, SIGNAL(canceled()), this, SIGNAL(downloadCanceled()));
0203     connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadAttemptFinished()));
0204     connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));
0205 
0206     downloadJob->get(srcUrl);
0207 }
0208 
0209 void KSDssDownloader::startDownload(const SkyPoint *const p, const QString &destFileName)
0210 {
0211     QUrl srcUrl;
0212     m_FileName = destFileName;
0213     m_attempt  = 0;
0214     srcUrl.setUrl(getDSSURL(p, m_VersionPreference[m_attempt], &m_AttemptData));
0215     initiateSingleDownloadAttempt(srcUrl);
0216 }
0217 
0218 void KSDssDownloader::startSingleDownload(const QUrl srcUrl, const QString &destFileName, KSDssImage::Metadata &md)
0219 {
0220     m_FileName   = destFileName;
0221     QUrl fileUrl = QUrl::fromLocalFile(m_TempFile.fileName());
0222     qDebug() << Q_FUNC_INFO << "Downloading DSS Image from URL: " << srcUrl << " to " << fileUrl;
0223     //m_DownloadJob = KIO::copy( srcUrl, fileUrl, KIO::Overwrite ) ; // FIXME: Can be done with pure Qt
0224     //connect ( m_DownloadJob, SIGNAL (result(KJob*)), SLOT (singleDownloadFinished()) );
0225 
0226     downloadJob = new FileDownloader();
0227 
0228     downloadJob->setProgressDialogEnabled(true, i18n("DSS Download"),
0229                                           i18n("Please wait while DSS image is being downloaded..."));
0230     connect(downloadJob, SIGNAL(canceled()), this, SIGNAL(downloadCanceled()));
0231     connect(downloadJob, SIGNAL(downloaded()), this, SLOT(singleDownloadFinished()));
0232     connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));
0233 
0234     m_AttemptData = md;
0235 
0236     downloadJob->get(srcUrl);
0237 }
0238 
0239 void KSDssDownloader::downloadError(const QString &errorString)
0240 {
0241     qDebug() << Q_FUNC_INFO << "Error " << errorString << " downloading DSS images!";
0242     emit downloadComplete(false);
0243     downloadJob->deleteLater();
0244 }
0245 
0246 void KSDssDownloader::singleDownloadFinished()
0247 {
0248     m_TempFile.open();
0249     m_TempFile.write(downloadJob->downloadedData());
0250     m_TempFile.close();
0251     downloadJob->deleteLater();
0252 
0253     // Check if we have a proper DSS image or the DSS server failed
0254     QMimeDatabase mdb;
0255     QMimeType mt = mdb.mimeTypeForFile(m_TempFile.fileName(), QMimeDatabase::MatchContent);
0256     if (mt.name().contains("image", Qt::CaseInsensitive))
0257     {
0258         qDebug() << Q_FUNC_INFO << "DSS download was successful";
0259         emit downloadComplete(writeImageWithMetadata(m_TempFile.fileName(), m_FileName, m_AttemptData));
0260         return;
0261     }
0262     else
0263         emit downloadComplete(false);
0264 }
0265 
0266 void KSDssDownloader::downloadAttemptFinished()
0267 {
0268     if (m_AttemptData.src == KSDssImage::Metadata::SDSS)
0269     {
0270         // FIXME: do SDSS-y things
0271         emit downloadComplete(false);
0272         deleteLater();
0273         downloadJob->deleteLater();
0274         return;
0275     }
0276     else
0277     {
0278         m_TempFile.open();
0279         m_TempFile.write(downloadJob->downloadedData());
0280         m_TempFile.close();
0281         downloadJob->deleteLater();
0282 
0283         // Check if we have a proper DSS image or the DSS server failed
0284         QMimeDatabase mdb;
0285         QMimeType mt = mdb.mimeTypeForFile(m_TempFile.fileName(), QMimeDatabase::MatchContent);
0286         if (mt.name().contains("image", Qt::CaseInsensitive))
0287         {
0288             qDebug() << Q_FUNC_INFO << "DSS download was successful";
0289             emit downloadComplete(writeImageFile());
0290             deleteLater();
0291             return;
0292         }
0293 
0294         // We must have failed, try the next attempt
0295         QUrl srcUrl;
0296         m_attempt++;
0297         if (m_attempt == m_VersionPreference.count())
0298         {
0299             // Nothing downloaded... very strange. Fail.
0300             qDebug() << Q_FUNC_INFO << "Error downloading DSS images: All alternatives failed!";
0301             emit downloadComplete(false);
0302             deleteLater();
0303             return;
0304         }
0305         srcUrl.setUrl(getDSSURL(m_AttemptData.ra0, m_AttemptData.dec0, m_AttemptData.width, m_AttemptData.height,
0306                                 ((m_AttemptData.format == KSDssImage::Metadata::FITS) ? "fits" : "gif"),
0307                                 m_VersionPreference[m_attempt], &m_AttemptData));
0308         initiateSingleDownloadAttempt(srcUrl);
0309     }
0310 }
0311 
0312 bool KSDssDownloader::writeImageFile()
0313 {
0314     return writeImageWithMetadata(m_TempFile.fileName(), m_FileName, m_AttemptData);
0315 }
0316 
0317 bool KSDssDownloader::writeImageWithMetadata(const QString &srcFile, const QString &destFile,
0318                                              const KSDssImage::Metadata &md)
0319 {
0320     // Write the temporary file into an image file with metadata
0321     QImage img(srcFile);
0322     QImageWriter writer(destFile, "png");
0323 
0324     writer.setText("Calibrated",
0325                    "true");    // This means that the image has RA/Dec size and orientation that is calibrated
0326     writer.setText("PA", "0"); // Position Angle is zero degrees for DSS images
0327     writer.setText("Source", QString::number(KSDssImage::Metadata::DSS));
0328     writer.setText("Format", QString::number(KSDssImage::Metadata::PNG));
0329     writer.setText("Version", md.version);
0330     writer.setText("Object", md.object);
0331     writer.setText("RA", md.ra0.toHMSString(true));
0332     writer.setText("Dec", md.dec0.toDMSString(true, true));
0333     writer.setText("Width", QString::number(md.width));
0334     writer.setText("Height", QString::number(md.height));
0335     writer.setText("Band", QString() + md.band);
0336     writer.setText("Generation", QString::number(md.gen));
0337     writer.setText("Author", "KStars KSDssDownloader");
0338     return writer.write(img);
0339 }