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());
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());
119 HIPSManager::Instance()->setOfflineLevels(orders);
120 _HIPSManager->setCurrentSource(Options::hIPSSource());
121 }
122
123 readSources();
124 KStars::Instance()->repopulateHIPS();
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
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)
This is the main window for KStars.
Definition kstars.h:91
static KStars * Instance()
Definition kstars.h:123
HIPS Settings including download of external sources and enabling/disabling them accordingly.
Definition opships.h:40
QString i18n(const char *text, const TYPE &arg...)
bool remove(const QString &column, const QVariant &value)
QString path(const QString &relativePath)
QStringView level(QStringView ifopt)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
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
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
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
bool operator==(const QGraphicsApiFilter &reference, const QGraphicsApiFilter &sample)
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 Fri May 17 2024 11:48:26 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.