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 if (!cacheElement.is_set())
139 {
140 try
141 {
142 cacheElement = (m_db_manager.*fillFunction)(trixel);
143 }
144 catch (const CatalogsDB::DatabaseError &e)
145 {
146 qCCritical(KSTARS)
147 << "Could not load catalog objects in trixel: " << trixel << ", "
148 << e.what();
149
151 nullptr, i18n("Could not load catalog objects in trixel: %1", trixel),
152 e.what());
153
154 throw; // do not silently fail
155 }
156 }
157 };
158
159 // Helper lambda to JIT update and draw
160 auto drawObjects = [&](std::vector<CatalogObject*>& objects) {
161 // TODO: If we are sure that JITupdate has no side effects
162 // that may cause races etc, it will be worth parallelizing
163
164 for (CatalogObject *object : objects) {
165 object->JITupdate();
166 auto &color = m_catalog_colors[object->catalogId()][color_scheme];
167 if (!color.isValid())
168 {
169 color = m_catalog_colors[object->catalogId()]["default"];
170
171 if (!color.isValid())
172 {
173 color = default_color;
174 }
175 }
176
177 skyp->setPen(color);
178
179 if (Options::showInlineImages())
180 object->load_image();
181
182 if (skyp->drawCatalogObject(*object) && !hideLabels)
183 {
184 labeler.drawNameLabel(object, proj.toScreen(object), label_padding);
185 }
186 }
187 };
188
189 std::vector<CatalogObject*> drawListKnownMag;
190 drawListKnownMag.reserve(expectedKnownMagObjectsPerTrixel);
191
192 // Handle the objects of known magnitude
193 MeshIterator region(m_skyMesh, DRAW_BUF);
194 while (region.hasNext())
195 {
196 Trixel trixel = region.next();
197 num_trixels++;
198
199 // Fill the cache for this trixel
200 auto &objectsKnownMag = m_mainCache[trixel];
202 drawListKnownMag.clear();
203
204 // Filter based on magnitude and size
205 for (const auto &object : objectsKnownMag.data())
206 {
207 const auto mag = object.mag();
208 const auto a = object.a(); // major axis
209 const double size = a * sizeScale;
210 const bool magCriterion = (mag < maglim);
211
212 if (!magCriterion)
213 {
214 break; // the known-mag objects are strictly sorted by
215 // magnitude, unknown magnitude first
216 }
217
218 bool sizeCriterion =
219 (size > 1.0 || size == 0 || zoomFactor > 2000.);
220
221 if (!sizeCriterion)
222 break;
223
224 drawListKnownMag.push_back(const_cast<CatalogObject*>(&object));
225 }
226
227 // JIT update and draw
229 }
230
231 // Handle the objects of unknown magnitude
233 {
234 std::vector<CatalogObject*> drawListUnknownMag;
235 drawListUnknownMag.reserve(expectedUnknownMagObjectsPerTrixel);
237
238 MeshIterator region(m_skyMesh, DRAW_BUF);
239 while (region.hasNext())
240 {
241 Trixel trixel = region.next();
242 drawListUnknownMag.clear();
243
244 // Fill cache
245 auto &objectsUnknownMag = m_unknownMagCache[trixel];
247
248 // Filter
250 objectsUnknownMag.data(),
251 [&](const auto &object)
252 {
253 auto a = object.a(); // major axis
254 double size = a * sizeScale;
255
256 // For objects of unknown mag but known size, adjust
257 // display behavior as if it were 22 mags/arcsec² =
258 // 13.1 mag/arcmin² surface brightness, comparing it
259 // to the magnitude limit.
260 bool magCriterion = (a <= 0.0 || (13.1 - 5*log10(a)) < maglim);
261
262 if (!magCriterion)
263 return;
264
265 bool sizeCriterion =
266 (size > 1.0 || (size == 0 && object.type() != SkyObject::GALAXY) || zoomFactor > 10000.);
267
268 if (!sizeCriterion)
269 return;
270
271 QMutexLocker _{&drawListUnknownMagLock};
272 drawListUnknownMag.push_back(const_cast<CatalogObject*>(&object));
273 });
274
275 // JIT update and draw
277 }
278
279 }
280
281 // prune only if the to-be-pruned trixels are likely not visible
282 // and we are not zooming
283 m_mainCache.prune(num_trixels * 1.2);
284 m_unknownMagCache.prune(num_trixels * 1.2);
285};
286
287void CatalogsComponent::updateSkyMesh(SkyMap &map, MeshBufNum_t buf)
288{
289 SkyPoint *focus = map.focus();
290 float radius = map.projector()->fov();
291 if (radius > 180.0)
292 radius = 180.0;
293
294 m_skyMesh->aperture(focus, radius + 1.0, buf);
295}
296
298{
299 auto trixel = m_skyMesh->index(&obj);
300 auto &lst = m_static_objects[trixel];
301 auto found_iter = std::find(lst.begin(), lst.end(), obj);
302
303 // Ideally, we would remove the object from ObjectsList if it's
304 // respective catalog is disabled, but there ain't a good way to
305 // do this right now
306
307 if (!(found_iter == lst.end()))
308 {
309 auto &found = *found_iter;
310 found = obj;
311 found.JITupdate();
312 return found;
313 }
314
315 auto copy = obj;
316 copy.JITupdate();
317
318 lst.push_back(std::move(copy));
319 auto &inserted = lst.back();
320
321 // we don't bother with translations here
322 auto &object_list = objectLists(inserted.type());
323
324 object_list.append({ inserted.name(), &inserted });
325 if (inserted.longname() != inserted.name())
326 object_list.append({ inserted.longname(), &inserted });
327
328 return inserted;
329}
330
332{
333 auto objects = exact ? m_db_manager.find_objects_by_name(name, 1, true)
334 : m_db_manager.find_objects_by_name(name);
335
336 if (objects.size() == 0)
337 return nullptr;
338
339 return &insertStaticObject(objects.front());
340}
341
343{
344 if (!selected())
345 return;
346
347 for (SkyRegion::const_iterator it = region.constBegin(); it != region.constEnd();
348 ++it)
349 {
350 try
351 {
352 for (auto &dso : m_db_manager.get_objects_in_trixel(it.key()))
353 {
354 auto &obj = insertStaticObject(dso);
355 list.append(&obj);
356 }
357 }
358 catch (const CatalogsDB::DatabaseError &e)
359 {
360 qCCritical(KSTARS) << "Could not load catalog objects in trixel: " << it.key()
361 << ", " << e.what();
362
364 nullptr, i18n("Could not load catalog objects in trixel: %1", it.key()),
365 e.what());
366 throw; // do not silently fail
367 }
368 }
369}
370
372{
373 if (!selected())
374 return nullptr;
375
376 m_skyMesh->aperture(p, maxrad, OBJ_NEAREST_BUF);
377 MeshIterator region(m_skyMesh, OBJ_NEAREST_BUF);
378 double smallest_r{ 360 };
380 bool found{ false };
381
382 while (region.hasNext())
383 {
384 auto trixel = region.next();
385 try
386 {
387 auto objects = m_db_manager.get_objects_in_trixel(trixel);
388 if (!found)
389 found = objects.size() > 0;
390
391 for (auto &obj : objects)
392 {
393 obj.JITupdate();
394
395 double r = obj.angularDistanceTo(p).Degrees();
396 if (r < smallest_r)
397 {
398 smallest_r = r;
399 nearest = obj;
400 }
401 }
402 }
403 catch (const CatalogsDB::DatabaseError &e)
404 {
406 nullptr, i18n("Could not load catalog objects in trixel: %1", trixel),
407 e.what());
408 throw; // do not silently fail
409 }
410 }
411
412 if (!found)
413 return nullptr;
414
416
418}
419
420void CatalogsComponent::tryImportSkyComponents()
421{
423 if (!skycom_db.first)
424 return;
425
426 const auto move_skycompdb = [&]()
427 {
428 const auto &path = skycom_db.second.databaseName();
429 const auto &new_path =
430 QString("%1.%2.backup").arg(path).arg(QDateTime::currentMSecsSinceEpoch());
431
432 QFile::rename(path, new_path);
433 };
434
435 const auto resp = KMessageBox::questionYesNoCancel(
436 nullptr, i18n("Import custom and internet resolved objects "
437 "from the old DSO database into the new one?"));
438
439 if (resp != KMessageBox::Yes)
440 {
442 return;
443 }
444
445 const auto &success = SkyComponentsImport::get_objects(skycom_db.second);
446 if (!std::get<0>(success))
447 KMessageBox::detailedError(nullptr, i18n("Could not import the objects."),
448 std::get<1>(success));
449
450 const auto &add_success =
451 m_db_manager.add_objects(CatalogsDB::user_catalog_id, std::get<2>(success));
452
453 if (!add_success.first)
454 KMessageBox::detailedError(nullptr, i18n("Could not import the objects."),
455 add_success.second);
456 else
457 {
459 nullptr, i18np("Successfully added %1 object to the user catalog.",
460 "Successfully added %1 objects to the user catalog.",
461 std::get<2>(success).size()));
463 }
464};
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:72
ColorScheme * colorScheme()
Definition kstarsdata.h:172
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
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...)
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
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 Tue Mar 26 2024 11:19:04 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.