Kstars

hipsmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2017 Pavel Mraz
3
4 SPDX-FileCopyrightText: 2017 Jasem Mutlaq
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "hipsmanager.h"
10
11#include "auxiliary/kspaths.h"
12#include "auxiliary/ksuserdb.h"
13#include "kstars.h"
14#include "kstarsdata.h"
15#include "kstars_debug.h"
16#include "Options.h"
17#include "skymap.h"
18
19#include <KConfigDialog>
20
21#include <QTime>
22#include <QHash>
23#include <QNetworkDiskCache>
24#include <QPainter>
25
26static QNetworkDiskCache *g_discCache = nullptr;
27static UrlFileDownload *g_download = nullptr;
28
29static int qHash(const pixCacheKey_t &key, uint seed)
30{
31 return qHash(QString("%1_%2_%3").arg(key.level).arg(key.pix).arg(key.uid), seed);
32}
33
34
35inline bool operator==(const pixCacheKey_t &k1, const pixCacheKey_t &k2)
36{
37 return (k1.uid == k2.uid) && (k1.level == k2.level) && (k1.pix == k2.pix);
38}
39
40HIPSManager * HIPSManager::_HIPSManager = nullptr;
41
42HIPSManager *HIPSManager::Instance()
43{
44 if (_HIPSManager == nullptr)
45 {
46 _HIPSManager = new HIPSManager();
47
48 // We should read offline sources on startup
49 QDir hipsDirectory(Options::hIPSOfflinePath());
50 auto orders = hipsDirectory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
51 HIPSManager::Instance()->setOfflineLevels(orders);
52
53 if (Options::hIPSUseOfflineSource())
54 _HIPSManager->setCurrentSource(Options::hIPSSource());
55 }
56
57 return _HIPSManager;
58}
59
60HIPSManager::HIPSManager() : QObject(KStars::Instance())
61{
62 if (g_discCache == nullptr)
63 {
64 g_discCache = new QNetworkDiskCache();
65 }
66
67 if (g_download == nullptr)
68 {
69 g_download = new UrlFileDownload(this, g_discCache);
70
71 connect(g_download, SIGNAL(sigDownloadDone(QNetworkReply::NetworkError, QByteArray &, pixCacheKey_t &)),
72 this, SLOT(slotDone(QNetworkReply::NetworkError, QByteArray &, pixCacheKey_t &)));
73 }
74
75 g_discCache->setCacheDirectory(QDir(KSPaths::writableLocation(QStandardPaths::CacheLocation)).filePath("hips"));
76 qint64 net = Options::hIPSNetCache();
77 qint64 value = net * 1024 * 1024;
78 g_discCache->setMaximumCacheSize(Options::hIPSNetCache() * 1024 * 1024);
79 value = Options::hIPSMemoryCache() * 1024 * 1024;
80 m_cache.setMaxCost(Options::hIPSMemoryCache() * 1024 * 1024);
81
82
83
84}
85
86void HIPSManager::showSettings()
87{
88 KConfigDialog *dialog = KConfigDialog::exists("hipssettings");
89 if (dialog == nullptr)
90 {
91 dialog = new KConfigDialog(KStars::Instance(), "hipssettings", Options::self());
92 connect(dialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(slotApply()));
93 connect(dialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(slotApply()));
94
95 displaySettings.reset(new OpsHIPSDisplay());
96 KPageWidgetItem *page = dialog->addPage(displaySettings.get(), i18n("Display"));
97 page->setIcon(QIcon::fromTheme("computer"));
98
99 cacheSettings.reset(new OpsHIPSCache());
100 page = dialog->addPage(cacheSettings.get(), i18n("Cache"));
101 page->setIcon(QIcon::fromTheme("preferences-web-browser-cache"));
102
103 sourceSettings.reset(new OpsHIPS());
104 page = dialog->addPage(sourceSettings.get(), i18n("Sources"));
105 page->setIcon(QIcon::fromTheme("view-preview"));
106
107 dialog->resize(800, 600);
108 }
109
110 dialog->show();
111}
112
113void HIPSManager::slotApply()
114{
115 if (Options::hIPSUseOfflineSource())
116 {
117 QDir hipsDirectory(Options::hIPSOfflinePath());
118 auto orders = hipsDirectory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
119 HIPSManager::Instance()->setOfflineLevels(orders);
120 _HIPSManager->setCurrentSource(Options::hIPSSource());
121 }
122
123 readSources();
125 SkyMap::Instance()->forceUpdate();
126}
127
128qint64 HIPSManager::getDiscCacheSize() const
129{
130 return g_discCache->cacheSize();
131}
132
133void HIPSManager::readSources()
134{
135 KStarsData::Instance()->userdb()->GetAllHIPSSources(m_hipsSources);
136
137 QString currentSourceTitle = Options::hIPSSource();
138
139 setCurrentSource(currentSourceTitle);
140}
141
142/*void HIPSManager::setParam(const hipsParams_t &param)
143{
144 m_param = param;
145 m_uid = qHash(param.url);
146}*/
147
148QImage *HIPSManager::getPix(bool allsky, int level, int pix, bool &freeImage)
149{
150 if (Options::hIPSUseOfflineSource() == false && m_currentSource.isEmpty())
151 {
152 qCWarning(KSTARS) << "HIPS source not available!";
153 return nullptr;
154 }
155
156 int origPix = pix;
157 freeImage = false;
158
159 if (allsky)
160 {
161 level = 0;
162 pix = 0;
163 }
164
165 pixCacheKey_t key;
166
167 key.level = level;
168 key.pix = pix;
169 key.uid = m_uid;
170
171 pixCacheItem_t *item = getCacheItem(key);
172
173 if (m_downloadMap.contains(key))
174 {
175 // downloading
176
177 // try render (level - 1) while downloading
178 key.level = level - 1;
179 key.pix = pix / 4;
180 pixCacheItem_t *item = getCacheItem(key);
181
182 if (item != nullptr)
183 {
184 QImage *cacheImage = item->image;
185 int size = m_currentTileWidth >> 1;
186 int offset = cacheImage->width() / size;
187 QImage *image = cacheImage;
188
189 int index[4] = {0, 2, 1, 3};
190
191 int ox = index[pix % 4] % offset;
192 int oy = index[pix % 4] / offset;
193
194 QImage *newImage = new QImage(image->copy(ox * size, oy * size, size, size));
195 freeImage = true;
196 return newImage;
197 }
198 return nullptr;
199 }
200
201 if (item != nullptr)
202 {
203 QImage *cacheImage = item->image;
204
205 Q_ASSERT(!item->image->isNull());
206
207 if (allsky && cacheImage != nullptr)
208 {
209 // all sky
210 int size = 64;
211 int offset = cacheImage->width() / size;
212 QImage *image = cacheImage;
213
214 int ox = origPix % offset;
215 int oy = origPix / offset;
216
217 QImage *newImage = new QImage(image->copy(ox * size, oy * size, size, size));
218 freeImage = true;
219 return newImage;
220 }
221
222 return cacheImage;
223 }
224
226
227 if (!allsky)
228 {
229 int dir = (pix / 10000) * 10000;
230
231 path = "/Norder" + QString::number(level) + "/Dir" + QString::number(dir) + "/Npix" + QString::number(pix) +
232 '.' + m_currentFormat;
233 }
234 else
235 {
236 path = "/Norder3/Allsky." + m_currentFormat;
237 }
238
239 QUrl downloadURL(m_currentURL);
240 downloadURL.setPath(downloadURL.path() + path);
241 g_download->begin(downloadURL, key);
242 m_downloadMap.insert(key);
243
244 return nullptr;
245}
246
247
248#if 0
249bool HIPSManager::parseProperties(hipsParams_t *param, const QString &filename, const QString &url)
250{
251 QFile f(filename);
252
253 if (!f.open(QFile::ReadOnly | QFile::Text))
254 {
255 qDebug() << Q_FUNC_INFO << "nf" << f.fileName();
256 return false;
257 }
258
259 QMap <QString, QString> map;
260 QTextStream in(&f);
261 while (!in.atEnd())
262 {
263 QString line = in.readLine();
264
265 int index = line.indexOf("=");
266 if (index > 0)
267 {
268 map[line.left(index).simplified()] = line.mid(index + 1).simplified();
269 }
270 }
271
272 param->url = url;
273 qDebug() << Q_FUNC_INFO << url;
274
275 int count = 0;
276 QString tmp;
277
278 if (map.contains("obs_collection"))
279 {
280 param->name = map["obs_collection"];
281 count++;
282 }
283
284 if (map.contains("hips_tile_width"))
285 {
286 param->tileWidth = map["hips_tile_width"].toInt();
287 count++;
288 }
289
290 if (map.contains("hips_order"))
291 {
292 param->max_level = map["hips_order"].toInt();
293 count++;
294 }
295
296 if (map.contains("hips_tile_format"))
297 {
298 tmp = map["hips_tile_format"];
299
300 QStringList list = tmp.split(" ");
301
302 if (list.contains("jpeg"))
303 {
304 param->imageExtension = "jpg";
305 count++;
306 }
307 else if (list.contains("png"))
308 {
309 param->imageExtension = "png";
310 count++;
311 }
312 }
313
314 if (map.contains("hips_frame") || map.contains("ohips_frame"))
315 {
316 if (map.contains("hips_frame"))
317 tmp = map["hips_frame"];
318 else
319 tmp = map["ohips_frame"];
320
321 if (tmp == "equatorial")
322 {
323 param->frame = HIPS_FRAME_EQT;
324 count++;
325 }
326 else if (tmp == "galactic")
327 {
328 param->frame = HIPS_FRAME_GAL;
329 count++;
330 }
331 }
332
333 return count == 5; // all items have been loaded
334}
335#endif
336
337void HIPSManager::cancelAll()
338{
339 g_download->abortAll();
340}
341
342void HIPSManager::clearDiscCache()
343{
344 g_discCache->clear();
345}
346
347void HIPSManager::slotDone(QNetworkReply::NetworkError error, QByteArray &data, pixCacheKey_t &key)
348{
349 if (error == QNetworkReply::NoError)
350 {
351 m_downloadMap.remove(key);
352
353 auto *item = new pixCacheItem_t;
354
355 item->image = new QImage();
356 if (item->image->loadFromData(data))
357 {
358 addToMemoryCache(key, item);
359
360 //SkyMap::Instance()->forceUpdate();
361 }
362 else
363 {
364 delete item;
365 qCWarning(KSTARS) << "no image. Data size: " << data.length();
366 }
367 }
368 else
369 {
371 {
372 m_downloadMap.remove(key);
373 }
374 else
375 {
376 auto *timer = new RemoveTimer();
377 timer->setKey(key);
378 connect(timer, SIGNAL(remove(pixCacheKey_t &)), this, SLOT(removeTimer(pixCacheKey_t &)));
379 }
380 }
381}
382
383void HIPSManager::removeTimer(pixCacheKey_t &key)
384{
385 m_downloadMap.remove(key);
386 sender()->deleteLater();
387 emit sigRepaint();
388}
389
390PixCache *HIPSManager::getCache()
391{
392 return &m_cache;
393}
394
395void HIPSManager::addToMemoryCache(pixCacheKey_t &key, pixCacheItem_t *item)
396{
397 Q_ASSERT(item);
398 Q_ASSERT(item->image);
399
400 int cost = item->image->sizeInBytes();
401
402 m_cache.add(key, item, cost);
403}
404
405pixCacheItem_t *HIPSManager::getCacheItem(pixCacheKey_t &key)
406{
407 return m_cache.get(key);
408}
409
410bool HIPSManager::setCurrentSource(const QString &title)
411{
412 if (title == "None")
413 {
414 Options::setShowHIPS(false);
415 Options::setHIPSSource(title);
416 m_currentSource.clear();
417 m_currentFormat.clear();
418 m_currentFrame = HIPS_OTHER_FRAME;
419 m_currentURL.clear();
420 m_currentOrder = 0;
421 m_currentTileWidth = 0;
422 m_uid = 0;
423 return true;
424 }
425 // Offline DSS
426 else if (Options::hIPSUseOfflineSource())
427 {
428 m_currentFormat = "jpg";
429 m_currentTileWidth = 512;
430 m_currentFrame = HIPS_EQUATORIAL_FRAME;
431 m_currentURL = QUrl(Options::hIPSOfflinePath());
432 m_currentURL.setScheme("file");
433 m_currentOrder = m_OfflineLevelsMap.lastKey();
434 m_uid = qHash(m_currentURL);
435 Options::setShowHIPS(true);
436 // N.B. Only DSS Colored catalog is supported for offline source
437 Options::setHIPSSource("DSS Colored");
438 return true;
439 }
440
441 for (QMap<QString, QString> &source : m_hipsSources)
442 {
443 if (source.value("obs_title") == title)
444 {
445 m_currentSource = source;
446 m_currentFormat = source.value("hips_tile_format");
447 if (m_currentFormat.contains("jpeg"))
448 m_currentFormat = "jpg";
449 else if (m_currentFormat.contains("png"))
450 m_currentFormat = "png";
451 else
452 {
453 qCWarning(KSTARS) << "FITS HIPS images are not currently supported.";
454 return false;
455 }
456
457 m_currentOrder = source.value("hips_order").toInt();
458 m_currentTileWidth = source.value("hips_tile_width").toInt();
459
460 if (source.value("hips_frame") == "equatorial")
461 m_currentFrame = HIPS_EQUATORIAL_FRAME;
462 else if (source.value("hips_frame") == "galactic")
463 m_currentFrame = HIPS_GALACTIC_FRAME;
464 else
465 m_currentFrame = HIPS_OTHER_FRAME;
466
467 m_currentURL = QUrl(source.value("hips_service_url"));
468 m_uid = qHash(m_currentURL);
469
470 Options::setHIPSSource(title);
471 Options::setShowHIPS(true);
472
473 return true;
474 }
475 }
476
477 return false;
478}
479
480// Extract which levels are available for offline use.
481void HIPSManager::setOfflineLevels(const QStringList &value)
482{
483 for (auto oneLevel : value)
484 {
485 if (oneLevel.startsWith("Norder"))
486 {
487 oneLevel.remove("Norder");
488 auto level = oneLevel.toUInt();
489 m_OfflineLevelsMap[level] = level;
490 }
491 }
492
493 // In case we don't have offline maps, fill all levels with 1
494 if (m_OfflineLevelsMap.isEmpty())
495 {
496 for (int i = 0; i <= 20; i++)
497 m_OfflineLevelsMap[i] = 1;
498 return;
499 }
500
501 // Now let's map all the missing levels, if any
502 for (int i = 0; i <= 20; i++)
503 {
504 // Find closest level
505 if (m_OfflineLevelsMap.contains(i) == false)
506 {
507 const auto keys = m_OfflineLevelsMap.keys();
508 const auto values = m_OfflineLevelsMap.values();
509 auto it = std::upper_bound(keys.constBegin(), keys.constEnd(), i);
510 if (it != keys.end())
511 m_OfflineLevelsMap[i] = *it;
512 else
513 m_OfflineLevelsMap[i] = values.last();
514
515 }
516 }
517}
518
519int HIPSManager::getUsableLevel(int level) const
520{
521 return Options::hIPSUseOfflineSource() ? m_OfflineLevelsMap[level] : level;
522}
523
524int HIPSManager::getUsableOfflineLevel(int level) const
525{
526 return m_OfflineLevelsMap[level];
527}
528
529void RemoveTimer::setKey(const pixCacheKey_t &key)
530{
531 m_key = key;
532}
KPageWidgetItem * addPage(QWidget *page, const QString &itemName, const QString &pixmapName=QString(), const QString &header=QString(), bool manage=true)
static KConfigDialog * exists(const QString &name)
QPushButton * button(QDialogButtonBox::StandardButton which) const
void setIcon(const QIcon &icon)
KSUserDB * userdb()
Definition kstarsdata.h:215
This is the main window for KStars.
Definition kstars.h:89
static KStars * Instance()
Definition kstars.h:121
void repopulateHIPS()
Load HIPS information and repopulate menu.
HIPS Settings including download of external sources and enabling/disabling them accordingly.
Definition opships.h:40
void forceUpdate(bool now=false)
Recalculates the positions of objects in the sky, and then repaints the sky map.
Definition skymap.cpp:1173
QString i18n(const char *text, const TYPE &arg...)
KCALENDARCORE_EXPORT size_t qHash(const KCalendarCore::Period &key, size_t seed=0)
KIOCORE_EXPORT bool operator==(const UDSEntry &entry, const UDSEntry &other)
QString path(const QString &relativePath)
QStringView level(QStringView ifopt)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem remove()
qsizetype length() const const
QIcon fromTheme(const QString &name)
QImage copy(const QRect &rectangle) const const
bool isNull() const const
bool loadFromData(QByteArrayView data, const char *format)
qsizetype sizeInBytes() const const
int width() const const
void clear()
bool contains(const Key &key) const const
bool isEmpty() const const
QList< Key > keys() const const
T & last()
const Key & lastKey() const const
QList< T > values() const const
virtual qint64 cacheSize() const const override
virtual void clear() override
void setCacheDirectory(const QString &cacheDir)
void setMaximumCacheSize(qint64 size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * sender() const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
bool remove(const T &value)
QString arg(Args &&... args) const const
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString simplified() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
uint toUInt(bool *ok, int base) const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void clear()
void setScheme(const QString &scheme)
void show()
void resize(const QSize &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:38:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.