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 ¶m) 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 }