Kstars

catalogscomponent.cpp
1/*
2 SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
3 SPDX-FileCopyrightText: 2021 Valentin Boettcher <hiro at protagon.space; @hiro98:tchncs.de>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "catalogscomponent.h"
9#include "skypainter.h"
10#include "skymap.h"
11#include "kstarsdata.h"
12#include "Options.h"
13#include "MeshIterator.h"
14#include "projections/projector.h"
15#include "skylabeler.h"
16#include "kstars_debug.h"
17#include "kstars.h"
18#include "skymapcomposite.h"
19#include "kspaths.h"
20#include "import_skycomp.h"
21
22#include <QtConcurrent>
23
24#include <cmath>
25
26constexpr std::size_t expectedKnownMagObjectsPerTrixel = 500;
27constexpr std::size_t expectedUnknownMagObjectsPerTrixel = 1500;
28
30 bool load_default)
31 : SkyComponent(parent)
32 , m_db_manager(db_filename)
33 , m_skyMesh{ SkyMesh::Create(m_db_manager.htmesh_level()) }
34 , m_mainCache(m_skyMesh->size(), calculateCacheSize(Options::dSOCachePercentage()))
35 , m_unknownMagCache(m_skyMesh->size(), calculateCacheSize(Options::dSOCachePercentage()))
36{
37 if (load_default)
38 {
39 const auto &default_file = KSPaths::locate(QStandardPaths::AppLocalDataLocation,
40 Options::dSODefaultCatalogFilename());
41
42 if (QFile(default_file).exists())
43 {
44 m_db_manager.import_catalog(default_file, false);
45 }
46 }
47
48 m_catalog_colors = m_db_manager.get_catalog_colors();
49 tryImportSkyComponents();
50 qCInfo(KSTARS) << "Loaded DSO catalogs.";
51}
52
53double compute_maglim()
54{
55 double maglim = Options::magLimitDrawDeepSky();
56
57 //adjust maglimit for ZoomLevel
58 static const double lgmin{ log10(MINZOOM) };
59 static const double lgmax{ log10(MAXZOOM) };
60 double lgz = log10(Options::zoomFactor());
61 if (lgz <= 0.75 * lgmax)
62 maglim -=
63 (Options::magLimitDrawDeepSky() - Options::magLimitDrawDeepSkyZoomOut()) *
64 (0.75 * lgmax - lgz) / (0.75 * lgmax - lgmin);
65
66 return maglim;
67}
68
70{
71 if (!selected() || Options::zoomFactor() < Options::dSOMinZoomFactor())
72 return;
73
74 KStarsData *data = KStarsData::Instance();
75 const auto &default_color = data->colorScheme()->colorNamed("DSOColor");
76 skyp->setPen(default_color);
77 skyp->setBrush(Qt::NoBrush);
78
79 bool showUnknownMagObjects = Options::showUnknownMagObjects();
80 auto maglim = compute_maglim();
81
82 auto &labeler = *SkyLabeler::Instance();
83 labeler.setPen(
84 QColor(KStarsData::Instance()->colorScheme()->colorNamed("DSNameColor")));
85 const auto &color_scheme = KStarsData::Instance()->colorSchemeFileName();
86
87 auto &map = *SkyMap::Instance();
88 auto hideLabels = (map.isSlewing() && Options::hideOnSlew()) ||
89 !(Options::showDeepSkyMagnitudes() || Options::showDeepSkyNames());
90
91 const auto label_padding{ 1 + (1 - (Options::deepSkyLabelDensity() / 100)) * 50 };
92 auto &proj = *map.projector();
93
94 updateSkyMesh(map);
95
96 size_t num_trixels{ 0 };
97 const auto zoomFactor = Options::zoomFactor();
98 const double sizeScale = dms::PI * zoomFactor / 10800.0; // FIXME: magic number 10800
99
100 // Note: This function handles objects of known and unknown
101 // magnitudes differently. This is mostly because objects in the
102 // PGC catalog do not have magnitude information, which leads to
103 // hundreds of thousands of objects of unknown magnitude. This
104 // means we must find some way to:
105 // (a) Prevent PGC objects from flooding the screen at low zoom
106 // levels
107 // (b) Prevent PGC labels from crowding out more common NGC/M
108 // labels
109 // (c) Process the large number of PGC objects efficiently at low
110 // zoom levels (even when they are not actually displayed)
111
112 // The problem is that normally, we have relied on magnitude as a
113 // proxy to prioritize object labels, as well as to short-circuit
114 // filtering decisions by having our objects sorted by
115 // magnitude. We can no longer do that.
116
117 // Therefore, we first handle objects of known magnitude, given
118 // them priority in SkyLabeler label-space, solving (b). Then, we
119 // place aggressive filters on objects of unknown magnitude and
120 // size, by explicitly special-casing galaxies: generally
121 // speaking, large nebulae and small galaxies don't have known
122 // magnitudes; so we allow objects of unknown size and unknown
123 // magnitudes to be displayed at lower zoom levels provided they
124 // are not galaxies. With these series of tricks, lest we say
125 // hacks, we solve (a). Finally, we make the unknown mag loop
126 // concurrent since we are unable to bail-out when its time like
127 // we can with the objects of known magnitude, addressing
128 // (c). Thus, the user experience with the PGC flood of 1 million
129 // galaxies of unknown magnitude, and many of them also of unknown
130 // size, remains smooth.
131
132 // Helper lambda to fill the appropriate cache for a given trixel
133 auto fillCache = [&](
135 ObjectList (CatalogsDB::DBManager::*fillFunction)(const int),
136 Trixel trixel
137 ) -> void
138 {
139 if (!cacheElement.is_set())
140 {
141 try
142 {
143 cacheElement = (m_db_manager.*fillFunction)(trixel);
144 }
145 catch (const CatalogsDB::DatabaseError &e)
146 {
147 qCCritical(KSTARS)
148 << "Could not load catalog objects in trixel: " << trixel << ", "
149 << e.what();
150
152 nullptr, i18n("Could not load catalog objects in trixel: %1", trixel),
153 e.what());
154
155 throw; // do not silently fail
156 }
157 }
158 };
159
160 // Helper lambda to JIT update and draw
161 auto drawObjects = [&](std::vector<CatalogObject*> &objects)
162 {
163 // TODO: If we are sure that JITupdate has no side effects
164 // that may cause races etc, it will be worth parallelizing
165
166 for (CatalogObject *object : objects)
167 {
168 object->JITupdate();
169 auto &color = m_catalog_colors[object->catalogId()][color_scheme];
170 if (!color.isValid())
171 {
172 color = m_catalog_colors[object->catalogId()]["default"];
173
174 if (!color.isValid())
175 {
176 color = default_color;
177 }
178 }
179
180 skyp->setPen(color);
181
182 if (Options::showInlineImages())
183 object->load_image();
184
185 if (skyp->drawCatalogObject(*object) && !hideLabels)
186 {
187 labeler.drawNameLabel(object, proj.toScreen(object), label_padding);
188 }
189 }
190 };
191
192 std::vector<CatalogObject*> drawListKnownMag;
193 drawListKnownMag.reserve(expectedKnownMagObjectsPerTrixel);
194
195 // Handle the objects of known magnitude
196 MeshIterator region(m_skyMesh, DRAW_BUF);
197 while (region.hasNext())
198 {
199 Trixel trixel = region.next();
200 num_trixels++;
201
202 // Fill the cache for this trixel
203 auto &objectsKnownMag = m_mainCache[trixel];
204 fillCache(objectsKnownMag, &CatalogsDB::DBManager::get_objects_in_trixel_no_nulls, trixel);
205 drawListKnownMag.clear();
206
207 // Filter based on magnitude and size
208 for (const auto &object : objectsKnownMag.data())
209 {
210 const auto mag = object.mag();
211 const auto a = object.a(); // major axis
212 const double size = a * sizeScale;
213 const bool magCriterion = (mag < maglim);
214
215 if (!magCriterion)
216 {
217 break; // the known-mag objects are strictly sorted by
218 // magnitude, unknown magnitude first
219 }
220
221 bool sizeCriterion =
222 (size > 1.0 || size == 0 || zoomFactor > 2000.);
223
224 if (!sizeCriterion)
225 break;
226
227 drawListKnownMag.push_back(const_cast<CatalogObject*>(&object));
228 }
229
230 // JIT update and draw
231 drawObjects(drawListKnownMag);
232 }
233
234 // Handle the objects of unknown magnitude
235 if (showUnknownMagObjects)
236 {
237 std::vector<CatalogObject*> drawListUnknownMag;
238 drawListUnknownMag.reserve(expectedUnknownMagObjectsPerTrixel);
239 QMutex drawListUnknownMagLock;
240
241 MeshIterator region(m_skyMesh, DRAW_BUF);
242 while (region.hasNext())
243 {
244 Trixel trixel = region.next();
245 drawListUnknownMag.clear();
246
247 // Fill cache
248 auto &objectsUnknownMag = m_unknownMagCache[trixel];
249 fillCache(objectsUnknownMag, &CatalogsDB::DBManager::get_objects_in_trixel_null_mag, trixel);
250
251 // Filter
253 objectsUnknownMag.data(),
254 [&](const auto & object)
255 {
256 auto a = object.a(); // major axis
257 double size = a * sizeScale;
258
259 // For objects of unknown mag but known size, adjust
260 // display behavior as if it were 22 mags/arcsec² =
261 // 13.1 mag/arcmin² surface brightness, comparing it
262 // to the magnitude limit.
263 bool magCriterion = (a <= 0.0 || (13.1 - 5 * log10(a)) < maglim);
264
265 if (!magCriterion)
266 return;
267
268 bool sizeCriterion =
269 (size > 1.0 || (size == 0 && object.type() != SkyObject::GALAXY) || zoomFactor > 10000.);
270
271 if (!sizeCriterion)
272 return;
273
274 QMutexLocker _{&drawListUnknownMagLock};
275 drawListUnknownMag.push_back(const_cast<CatalogObject*>(&object));
276 });
277
278 // JIT update and draw
279 drawObjects(drawListUnknownMag);
280 }
281
282 }
283
284 // prune only if the to-be-pruned trixels are likely not visible
285 // and we are not zooming
286 m_mainCache.prune(num_trixels * 1.2);
287 m_unknownMagCache.prune(num_trixels * 1.2);
288};
289
290void CatalogsComponent::updateSkyMesh(SkyMap &map, MeshBufNum_t buf)
291{
292 SkyPoint *focus = map.focus();
293 float radius = map.projector()->fov();
294 if (radius > 180.0 && SkyMap::Instance()->projector()->type() != Projector::Stereographic)
295 radius = 180.0;
296
297 m_skyMesh->aperture(focus, radius + 1.0, buf);
298}
299
301{
302 auto trixel = m_skyMesh->index(&obj);
303 auto &lst = m_static_objects[trixel];
304 auto found_iter = std::find(lst.begin(), lst.end(), obj);
305
306 // Ideally, we would remove the object from ObjectsList if it's
307 // respective catalog is disabled, but there ain't a good way to
308 // do this right now
309
310 if (!(found_iter == lst.end()))
311 {
312 auto &found = *found_iter;
313 found = obj;
314 found.JITupdate();
315 return found;
316 }
317
318 auto copy = obj;
319 copy.JITupdate();
320
321 lst.push_back(std::move(copy));
322 auto &inserted = lst.back();
323
324 // we don't bother with translations here
325 auto &object_list = objectLists(inserted.type());
326
327 object_list.append({ inserted.name(), &inserted });
328 if (inserted.longname() != inserted.name())
329 object_list.append({ inserted.longname(), &inserted });
330
331 return inserted;
332}
333
335{
336 auto objects = m_db_manager.find_objects_by_name(name, 1, true);
337 if (objects.empty() && !exact)
338 objects = m_db_manager.find_objects_by_name(name);
339
340 if (objects.size() == 0)
341 return nullptr;
342
343 return &insertStaticObject(objects.front());
344}
345
347{
348 if (!selected())
349 return;
350
351 for (SkyRegion::const_iterator it = region.constBegin(); it != region.constEnd();
352 ++it)
353 {
354 try
355 {
356 for (auto &dso : m_db_manager.get_objects_in_trixel(it.key()))
357 {
358 auto &obj = insertStaticObject(dso);
359 list.append(&obj);
360 }
361 }
362 catch (const CatalogsDB::DatabaseError &e)
363 {
364 qCCritical(KSTARS) << "Could not load catalog objects in trixel: " << it.key()
365 << ", " << e.what();
366
368 nullptr, i18n("Could not load catalog objects in trixel: %1", it.key()),
369 e.what());
370 throw; // do not silently fail
371 }
372 }
373}
374
375SkyObject *CatalogsComponent::objectNearest(SkyPoint *p, double &maxrad)
376{
377 if (!selected())
378 return nullptr;
379
380 m_skyMesh->aperture(p, maxrad, OBJ_NEAREST_BUF);
381 MeshIterator region(m_skyMesh, OBJ_NEAREST_BUF);
382 double smallest_r{ 360 };
383 CatalogObject nearest{};
384 bool found{ false };
385
386 while (region.hasNext())
387 {
388 auto trixel = region.next();
389 try
390 {
391 auto objects = m_db_manager.get_objects_in_trixel(trixel);
392 if (!found)
393 found = objects.size() > 0;
394
395 for (auto &obj : objects)
396 {
397 obj.JITupdate();
398
399 double r = obj.angularDistanceTo(p).Degrees();
400 if (r < smallest_r)
401 {
402 smallest_r = r;
403 nearest = obj;
404 }
405 }
406 }
407 catch (const CatalogsDB::DatabaseError &e)
408 {
410 nullptr, i18n("Could not load catalog objects in trixel: %1", trixel),
411 e.what());
412 throw; // do not silently fail
413 }
414 }
415
416 if (!found)
417 return nullptr;
418
419 maxrad = smallest_r;
420
421 return &insertStaticObject(nearest);
422}
423
424void CatalogsComponent::tryImportSkyComponents()
425{
426 auto skycom_db = SkyComponentsImport::get_skycomp_db();
427 if (!skycom_db.first)
428 return;
429
430 const auto move_skycompdb = [&]()
431 {
432 const auto &path = skycom_db.second.databaseName();
433 const auto &new_path =
434 QString("%1.%2.backup").arg(path).arg(QDateTime::currentMSecsSinceEpoch());
435
436 QFile::rename(path, new_path);
437 };
438
439 const auto resp = KMessageBox::warningContinueCancel(
440 nullptr, i18n("Import custom and internet resolved objects "
441 "from the old DSO database into the new one?"));
442
443 if (resp != KMessageBox::Continue)
444 {
445 move_skycompdb();
446 return;
447 }
448
449 const auto &success = SkyComponentsImport::get_objects(skycom_db.second);
450 if (!std::get<0>(success))
451 KMessageBox::detailedError(nullptr, i18n("Could not import the objects."),
452 std::get<1>(success));
453
454 const auto &add_success =
455 m_db_manager.add_objects(CatalogsDB::user_catalog_id, std::get<2>(success));
456
457 if (!add_success.first)
458 KMessageBox::detailedError(nullptr, i18n("Could not import the objects."),
459 add_success.second);
460 else
461 {
463 nullptr, i18np("Successfully added %1 object to the user catalog.",
464 "Successfully added %1 objects to the user catalog.",
465 std::get<2>(success).size()));
466 move_skycompdb();
467 }
468};
A simple container object to hold the minimum information for a Deep Sky Object to be drawn on the sk...
void JITupdate()
Update the cooridnates and the horizontal coordinates if updateID or updateNumID have changed (global...
SkyObject * objectNearest(SkyPoint *p, double &maxrad) override
Find the SkyObject nearest the given SkyPoint.
SkyObject * findByName(const QString &name, bool exact=true) override
Search the underlying database for an object with the name.
void objectsInArea(QList< SkyObject * > &list, const SkyRegion &region) override
Searches the region(s) and appends the SkyObjects found to the list of sky objects.
CatalogsComponent(SkyComposite *parent, const QString &db_filename, bool load_default=false)
Constructs the Catalogscomponent with a parent and a database file under the path db_filename.
CatalogObject & insertStaticObject(const CatalogObject &obj)
Insert an object obj into m_static_objects and return a reference to the newly inserted object.
bool selected() override
Wether to show the DSOs.
void draw(SkyPainter *skyp) override
Draws the objects in the currently visible trixels by dynamically loading them from the database.
Manages the catalog database and provides an interface to provide an interface to query and modify th...
Definition catalogsdb.h:183
CatalogObjectVector get_objects_in_trixel_null_mag(const int trixel)
Definition catalogsdb.h:267
CatalogObjectList find_objects_by_name(const QString &name, const int limit=-1, const bool exactMatchOnly=false)
Find an objects by name.
ColorMap get_catalog_colors()
CatalogObjectVector get_objects_in_trixel_no_nulls(const int trixel)
Definition catalogsdb.h:260
std::pair< bool, QString > import_catalog(const QString &file_path, const bool overwrite=false)
Loads a dumped catalog from path `file_path`.
std::pair< bool, QString > add_objects(const int catalog_id, const CatalogObjectVector &objects)
Add the `objects` to a table with `catalog_id`.
CatalogObjectVector get_objects_in_trixel(const int trixel)
Definition catalogsdb.h:253
Database related error, thrown when database access fails or an action does not succeed.
Definition catalogsdb.h:682
QColor colorNamed(const QString &name) const
Retrieve a color by name.
KStarsData is the backbone of KStars.
Definition kstarsdata.h:74
ColorScheme * colorScheme()
Definition kstarsdata.h:174
Q_INVOKABLE QString colorSchemeFileName()
Definition kstarsdata.h:180
MeshIterator is a very lightweight class used to iterate over the result set of an HTMesh intersectio...
Trixel next() const
returns the next trixel
bool hasNext() const
true if there are more trixel to iterate over.
SkyComponent represents an object on the sky map.
SkyComposite is a kind of container class for SkyComponent objects.
This is the canvas on which the sky is painted.
Definition skymap.h:54
Provides an interface to the Hierarchical Triangular Mesh (HTM) library written by A.
Definition skymesh.h:74
void aperture(SkyPoint *center, double radius, MeshBufNum_t bufNum=DRAW_BUF)
finds the set of trixels that cover the circular aperture specified after first performing a reverse ...
Definition skymesh.cpp:56
Trixel index(const SkyPoint *p)
returns the index of the trixel containing p.
Definition skymesh.cpp:74
Provides all necessary information about an object in the sky: its coordinates, name(s),...
Definition skyobject.h:42
Draws things on the sky, without regard to backend.
Definition skypainter.h:40
virtual bool drawCatalogObject(const CatalogObject &obj)=0
Draw a deep sky object (loaded from the new implementation)
virtual void setBrush(const QBrush &brush)=0
Set the brush of the painter.
virtual void setPen(const QPen &pen)=0
Set the pen of the painter.
The sky coordinates of a point in the sky.
Definition skypoint.h:45
A container to hold cache elements.
Definition trixelcache.h:70
static constexpr double PI
PI is a const static member; it's public so that it can be used anywhere, as long as dms....
Definition dms.h:385
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
void detailedError(QWidget *parent, const QString &text, const QString &details, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
std::pair< bool, QSqlDatabase > get_skycomp_db()
Get the skycomponent database from the standard path.
std::tuple< bool, QString, CatalogsDB::CatalogObjectVector > get_objects(QSqlDatabase db, const std::list< int > &ids={ 1, 2 })
qint64 currentMSecsSinceEpoch()
bool rename(const QString &newName)
const_iterator constBegin() const const
const_iterator constEnd() const const
void append(QList< T > &&value)
QString arg(Args &&... args) const const
void blockingMap(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 13 2024 11:47:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.