File indexing completed on 2024-03-24 15:17:04

0001 /*
0002     SPDX-FileCopyrightText: 2015-2017 Pavel Mraz
0003 
0004     SPDX-FileCopyrightText: 2017 Jasem Mutlaq
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "hipsmanager.h"
0010 
0011 #include "auxiliary/kspaths.h"
0012 #include "auxiliary/ksuserdb.h"
0013 #include "kstars.h"
0014 #include "kstarsdata.h"
0015 #include "kstars_debug.h"
0016 #include "Options.h"
0017 #include "skymap.h"
0018 
0019 #include <KConfigDialog>
0020 
0021 #include <QTime>
0022 #include <QHash>
0023 #include <QNetworkDiskCache>
0024 #include <QPainter>
0025 
0026 static QNetworkDiskCache *g_discCache = nullptr;
0027 static UrlFileDownload *g_download = nullptr;
0028 
0029 static int qHash(const pixCacheKey_t &key, uint seed)
0030 {
0031     return qHash(QString("%1_%2_%3").arg(key.level).arg(key.pix).arg(key.uid), seed);
0032 }
0033 
0034 
0035 inline bool operator==(const pixCacheKey_t &k1, const pixCacheKey_t &k2)
0036 {
0037     return (k1.uid == k2.uid) && (k1.level == k2.level) && (k1.pix == k2.pix);
0038 }
0039 
0040 HIPSManager * HIPSManager::_HIPSManager = nullptr;
0041 
0042 HIPSManager *HIPSManager::Instance()
0043 {
0044     if (_HIPSManager == nullptr)
0045     {
0046         _HIPSManager = new HIPSManager();
0047 
0048         // We should read offline sources on startup
0049         QDir hipsDirectory(Options::hIPSOfflinePath());
0050         auto orders = hipsDirectory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
0051         HIPSManager::Instance()->setOfflineLevels(orders);
0052 
0053         if (Options::hIPSUseOfflineSource())
0054             _HIPSManager->setCurrentSource(Options::hIPSSource());
0055     }
0056 
0057     return _HIPSManager;
0058 }
0059 
0060 HIPSManager::HIPSManager() : QObject(KStars::Instance())
0061 {
0062     if (g_discCache == nullptr)
0063     {
0064         g_discCache = new QNetworkDiskCache();
0065     }
0066 
0067     if (g_download == nullptr)
0068     {
0069         g_download = new UrlFileDownload(this, g_discCache);
0070 
0071         connect(g_download, SIGNAL(sigDownloadDone(QNetworkReply::NetworkError, QByteArray &, pixCacheKey_t &)),
0072                 this, SLOT(slotDone(QNetworkReply::NetworkError, QByteArray &, pixCacheKey_t &)));
0073     }
0074 
0075     g_discCache->setCacheDirectory(QDir(KSPaths::writableLocation(QStandardPaths::CacheLocation)).filePath("hips"));
0076     qint64 net = Options::hIPSNetCache();
0077     qint64 value = net * 1024 * 1024;
0078     g_discCache->setMaximumCacheSize(Options::hIPSNetCache() * 1024 * 1024);
0079     value = Options::hIPSMemoryCache() * 1024 * 1024;
0080     m_cache.setMaxCost(Options::hIPSMemoryCache() * 1024 * 1024);
0081 
0082 
0083 
0084 }
0085 
0086 void HIPSManager::showSettings()
0087 {
0088     KConfigDialog *dialog = KConfigDialog::exists("hipssettings");
0089     if (dialog == nullptr)
0090     {
0091         dialog = new KConfigDialog(KStars::Instance(), "hipssettings", Options::self());
0092         connect(dialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(slotApply()));
0093         connect(dialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(slotApply()));
0094 
0095         displaySettings.reset(new OpsHIPSDisplay());
0096         KPageWidgetItem *page = dialog->addPage(displaySettings.get(), i18n("Display"));
0097         page->setIcon(QIcon::fromTheme("computer"));
0098 
0099         cacheSettings.reset(new OpsHIPSCache());
0100         page = dialog->addPage(cacheSettings.get(), i18n("Cache"));
0101         page->setIcon(QIcon::fromTheme("preferences-web-browser-cache"));
0102 
0103         sourceSettings.reset(new OpsHIPS());
0104         page = dialog->addPage(sourceSettings.get(), i18n("Sources"));
0105         page->setIcon(QIcon::fromTheme("view-preview"));
0106 
0107         dialog->resize(800, 600);
0108     }
0109 
0110     dialog->show();
0111 }
0112 
0113 void HIPSManager::slotApply()
0114 {
0115     if (Options::hIPSUseOfflineSource())
0116     {
0117         QDir hipsDirectory(Options::hIPSOfflinePath());
0118         auto orders = hipsDirectory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
0119         HIPSManager::Instance()->setOfflineLevels(orders);
0120         _HIPSManager->setCurrentSource(Options::hIPSSource());
0121     }
0122 
0123     readSources();
0124     KStars::Instance()->repopulateHIPS();
0125     SkyMap::Instance()->forceUpdate();
0126 }
0127 
0128 qint64 HIPSManager::getDiscCacheSize() const
0129 {
0130     return g_discCache->cacheSize();
0131 }
0132 
0133 void HIPSManager::readSources()
0134 {
0135     KStarsData::Instance()->userdb()->GetAllHIPSSources(m_hipsSources);
0136 
0137     QString currentSourceTitle = Options::hIPSSource();
0138 
0139     setCurrentSource(currentSourceTitle);
0140 }
0141 
0142 /*void HIPSManager::setParam(const hipsParams_t &param)
0143 {
0144   m_param = param;
0145   m_uid = qHash(param.url);
0146 }*/
0147 
0148 QImage *HIPSManager::getPix(bool allsky, int level, int pix, bool &freeImage)
0149 {
0150     if (Options::hIPSUseOfflineSource() == false && m_currentSource.isEmpty())
0151     {
0152         qCWarning(KSTARS) << "HIPS source not available!";
0153         return nullptr;
0154     }
0155 
0156     int origPix = pix;
0157     freeImage = false;
0158 
0159     if (allsky)
0160     {
0161         level = 0;
0162         pix = 0;
0163     }
0164 
0165     pixCacheKey_t key;
0166 
0167     key.level = level;
0168     key.pix = pix;
0169     key.uid = m_uid;
0170 
0171     pixCacheItem_t *item = getCacheItem(key);
0172 
0173     if (m_downloadMap.contains(key))
0174     {
0175         // downloading
0176 
0177         // try render (level - 1) while downloading
0178         key.level = level - 1;
0179         key.pix = pix / 4;
0180         pixCacheItem_t *item = getCacheItem(key);
0181 
0182         if (item != nullptr)
0183         {
0184             QImage *cacheImage = item->image;
0185             int size = m_currentTileWidth >> 1;
0186             int offset = cacheImage->width() / size;
0187             QImage *image = cacheImage;
0188 
0189             int index[4] = {0, 2, 1, 3};
0190 
0191             int ox = index[pix % 4] % offset;
0192             int oy = index[pix % 4] / offset;
0193 
0194             QImage *newImage = new QImage(image->copy(ox * size, oy * size, size, size));
0195             freeImage = true;
0196             return newImage;
0197         }
0198         return nullptr;
0199     }
0200 
0201     if (item != nullptr)
0202     {
0203         QImage *cacheImage = item->image;
0204 
0205         Q_ASSERT(!item->image->isNull());
0206 
0207         if (allsky && cacheImage != nullptr)
0208         {
0209             // all sky
0210             int size = 64;
0211             int offset = cacheImage->width() / size;
0212             QImage *image = cacheImage;
0213 
0214             int ox = origPix % offset;
0215             int oy = origPix / offset;
0216 
0217             QImage *newImage = new QImage(image->copy(ox * size, oy * size, size, size));
0218             freeImage = true;
0219             return newImage;
0220         }
0221 
0222         return cacheImage;
0223     }
0224 
0225     QString path;
0226 
0227     if (!allsky)
0228     {
0229         int dir = (pix / 10000) * 10000;
0230 
0231         path = "/Norder" + QString::number(level) + "/Dir" + QString::number(dir) + "/Npix" + QString::number(pix) +
0232                '.' + m_currentFormat;
0233     }
0234     else
0235     {
0236         path = "/Norder3/Allsky." + m_currentFormat;
0237     }
0238 
0239     QUrl downloadURL(m_currentURL);
0240     downloadURL.setPath(downloadURL.path() + path);
0241     g_download->begin(downloadURL, key);
0242     m_downloadMap.insert(key);
0243 
0244     return nullptr;
0245 }
0246 
0247 
0248 #if 0
0249 bool HIPSManager::parseProperties(hipsParams_t *param, const QString &filename, const QString &url)
0250 {
0251     QFile f(filename);
0252 
0253     if (!f.open(QFile::ReadOnly | QFile::Text))
0254     {
0255         qDebug() << Q_FUNC_INFO << "nf" << f.fileName();
0256         return false;
0257     }
0258 
0259     QMap <QString, QString> map;
0260     QTextStream in(&f);
0261     while (!in.atEnd())
0262     {
0263         QString line = in.readLine();
0264 
0265         int index = line.indexOf("=");
0266         if (index > 0)
0267         {
0268             map[line.left(index).simplified()] = line.mid(index + 1).simplified();
0269         }
0270     }
0271 
0272     param->url = url;
0273     qDebug() << Q_FUNC_INFO << url;
0274 
0275     int count = 0;
0276     QString tmp;
0277 
0278     if (map.contains("obs_collection"))
0279     {
0280         param->name = map["obs_collection"];
0281         count++;
0282     }
0283 
0284     if (map.contains("hips_tile_width"))
0285     {
0286         param->tileWidth = map["hips_tile_width"].toInt();
0287         count++;
0288     }
0289 
0290     if (map.contains("hips_order"))
0291     {
0292         param->max_level = map["hips_order"].toInt();
0293         count++;
0294     }
0295 
0296     if (map.contains("hips_tile_format"))
0297     {
0298         tmp = map["hips_tile_format"];
0299 
0300         QStringList list = tmp.split(" ");
0301 
0302         if (list.contains("jpeg"))
0303         {
0304             param->imageExtension = "jpg";
0305             count++;
0306         }
0307         else if (list.contains("png"))
0308         {
0309             param->imageExtension = "png";
0310             count++;
0311         }
0312     }
0313 
0314     if (map.contains("hips_frame") || map.contains("ohips_frame"))
0315     {
0316         if (map.contains("hips_frame"))
0317             tmp = map["hips_frame"];
0318         else
0319             tmp = map["ohips_frame"];
0320 
0321         if (tmp == "equatorial")
0322         {
0323             param->frame = HIPS_FRAME_EQT;
0324             count++;
0325         }
0326         else if (tmp == "galactic")
0327         {
0328             param->frame = HIPS_FRAME_GAL;
0329             count++;
0330         }
0331     }
0332 
0333     return count == 5; // all items have been loaded
0334 }
0335 #endif
0336 
0337 void HIPSManager::cancelAll()
0338 {
0339     g_download->abortAll();
0340 }
0341 
0342 void HIPSManager::clearDiscCache()
0343 {
0344     g_discCache->clear();
0345 }
0346 
0347 void HIPSManager::slotDone(QNetworkReply::NetworkError error, QByteArray &data, pixCacheKey_t &key)
0348 {
0349     if (error == QNetworkReply::NoError)
0350     {
0351         m_downloadMap.remove(key);
0352 
0353         auto *item = new pixCacheItem_t;
0354 
0355         item->image = new QImage();
0356         if (item->image->loadFromData(data))
0357         {
0358             addToMemoryCache(key, item);
0359 
0360             //SkyMap::Instance()->forceUpdate();
0361         }
0362         else
0363         {
0364             delete item;
0365             qCWarning(KSTARS) << "no image. Data size: " << data.length();
0366         }
0367     }
0368     else
0369     {
0370         if (error == QNetworkReply::OperationCanceledError)
0371         {
0372             m_downloadMap.remove(key);
0373         }
0374         else
0375         {
0376             auto *timer = new RemoveTimer();
0377             timer->setKey(key);
0378             connect(timer, SIGNAL(remove(pixCacheKey_t &)), this, SLOT(removeTimer(pixCacheKey_t &)));
0379         }
0380     }
0381 }
0382 
0383 void HIPSManager::removeTimer(pixCacheKey_t &key)
0384 {
0385     m_downloadMap.remove(key);
0386     sender()->deleteLater();
0387     emit sigRepaint();
0388 }
0389 
0390 PixCache *HIPSManager::getCache()
0391 {
0392     return &m_cache;
0393 }
0394 
0395 void HIPSManager::addToMemoryCache(pixCacheKey_t &key, pixCacheItem_t *item)
0396 {
0397     Q_ASSERT(item);
0398     Q_ASSERT(item->image);
0399 
0400     int cost = item->image->sizeInBytes();
0401 
0402     m_cache.add(key, item, cost);
0403 }
0404 
0405 pixCacheItem_t *HIPSManager::getCacheItem(pixCacheKey_t &key)
0406 {
0407     return m_cache.get(key);
0408 }
0409 
0410 bool HIPSManager::setCurrentSource(const QString &title)
0411 {
0412     if (title == "None")
0413     {
0414         Options::setShowHIPS(false);
0415         Options::setHIPSSource(title);
0416         m_currentSource.clear();
0417         m_currentFormat.clear();
0418         m_currentFrame = HIPS_OTHER_FRAME;
0419         m_currentURL.clear();
0420         m_currentOrder = 0;
0421         m_currentTileWidth = 0;
0422         m_uid = 0;
0423         return true;
0424     }
0425     // Offline DSS
0426     else if (Options::hIPSUseOfflineSource())
0427     {
0428         m_currentFormat = "jpg";
0429         m_currentTileWidth = 512;
0430         m_currentFrame = HIPS_EQUATORIAL_FRAME;
0431         m_currentURL = QUrl(Options::hIPSOfflinePath());
0432         m_currentURL.setScheme("file");
0433         m_currentOrder = m_OfflineLevelsMap.lastKey();
0434         m_uid = qHash(m_currentURL);
0435         Options::setShowHIPS(true);
0436         // N.B. Only DSS Colored catalog is supported for offline source
0437         Options::setHIPSSource("DSS Colored");
0438         return true;
0439     }
0440 
0441     for (QMap<QString, QString> &source : m_hipsSources)
0442     {
0443         if (source.value("obs_title") == title)
0444         {
0445             m_currentSource = source;
0446             m_currentFormat = source.value("hips_tile_format");
0447             if (m_currentFormat.contains("jpeg"))
0448                 m_currentFormat = "jpg";
0449             else if (m_currentFormat.contains("png"))
0450                 m_currentFormat = "png";
0451             else
0452             {
0453                 qCWarning(KSTARS) << "FITS HIPS images are not currently supported.";
0454                 return false;
0455             }
0456 
0457             m_currentOrder = source.value("hips_order").toInt();
0458             m_currentTileWidth = source.value("hips_tile_width").toInt();
0459 
0460             if (source.value("hips_frame") == "equatorial")
0461                 m_currentFrame = HIPS_EQUATORIAL_FRAME;
0462             else if (source.value("hips_frame") == "galactic")
0463                 m_currentFrame = HIPS_GALACTIC_FRAME;
0464             else
0465                 m_currentFrame = HIPS_OTHER_FRAME;
0466 
0467             m_currentURL = QUrl(source.value("hips_service_url"));
0468             m_uid = qHash(m_currentURL);
0469 
0470             Options::setHIPSSource(title);
0471             Options::setShowHIPS(true);
0472 
0473             return true;
0474         }
0475     }
0476 
0477     return false;
0478 }
0479 
0480 // Extract which levels are available for offline use.
0481 void HIPSManager::setOfflineLevels(const QStringList &value)
0482 {
0483     for (auto oneLevel : value)
0484     {
0485         if (oneLevel.startsWith("Norder"))
0486         {
0487             oneLevel.remove("Norder");
0488             auto level =  oneLevel.toUInt();
0489             m_OfflineLevelsMap[level] = level;
0490         }
0491     }
0492 
0493     // In case we don't have offline maps, fill all levels with 1
0494     if (m_OfflineLevelsMap.isEmpty())
0495     {
0496         for (int i = 0; i <= 20; i++)
0497             m_OfflineLevelsMap[i] = 1;
0498         return;
0499     }
0500 
0501     // Now let's map all the missing levels, if any
0502     for (int i = 0; i <= 20; i++)
0503     {
0504         // Find closest level
0505         if (m_OfflineLevelsMap.contains(i) == false)
0506         {
0507             const auto keys = m_OfflineLevelsMap.keys();
0508             const auto values = m_OfflineLevelsMap.values();
0509             auto it = std::upper_bound(keys.constBegin(), keys.constEnd(), i);
0510             if (it != keys.end())
0511                 m_OfflineLevelsMap[i] = *it;
0512             else
0513                 m_OfflineLevelsMap[i] = values.last();
0514 
0515         }
0516     }
0517 }
0518 
0519 int HIPSManager::getUsableLevel(int level) const
0520 {
0521     return Options::hIPSUseOfflineSource() ? m_OfflineLevelsMap[level] : level;
0522 }
0523 
0524 int HIPSManager::getUsableOfflineLevel(int level) const
0525 {
0526     return m_OfflineLevelsMap[level];
0527 }
0528 
0529 void RemoveTimer::setKey(const pixCacheKey_t &key)
0530 {
0531     m_key = key;
0532 }