File indexing completed on 2024-05-05 03:42:53
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 }