Kstars

catalogscomponent.cpp
1 /*
2  SPDX-FileCopyrightText: 2001 Jason Harris <[email protected]>
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 
26 constexpr std::size_t expectedKnownMagObjectsPerTrixel = 500;
27 constexpr 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 
53 double 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 = [&](
134  TrixelCache<ObjectList>::element& cacheElement,
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];
201  fillCache(objectsKnownMag, &CatalogsDB::DBManager::get_objects_in_trixel_no_nulls, 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
228  drawObjects(drawListKnownMag);
229  }
230 
231  // Handle the objects of unknown magnitude
232  if (showUnknownMagObjects)
233  {
234  std::vector<CatalogObject*> drawListUnknownMag;
235  drawListUnknownMag.reserve(expectedUnknownMagObjectsPerTrixel);
236  QMutex drawListUnknownMagLock;
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];
246  fillCache(objectsUnknownMag, &CatalogsDB::DBManager::get_objects_in_trixel_null_mag, 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
276  drawObjects(drawListUnknownMag);
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 
287 void 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 };
379  CatalogObject nearest{};
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 
415  maxrad = smallest_r;
416 
417  return &insertStaticObject(nearest);
418 }
419 
420 void CatalogsComponent::tryImportSkyComponents()
421 {
422  auto skycom_db = SkyComponentsImport::get_skycomp_db();
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  {
441  move_skycompdb();
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()));
462  move_skycompdb();
463  }
464 };
void append(const T &value)
CatalogObjectVector get_objects_in_trixel_null_mag(const int trixel)
Definition: catalogsdb.h:266
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:380
std::pair< bool, QString > add_objects(const int catalog_id, const CatalogObjectVector &objects)
Add the objects to a table with catalog_id.
Stores dms coordinates for a point in the sky. for converting between coordinate systems.
Definition: skypoint.h:44
bool rename(const QString &newName)
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
virtual void setPen(const QPen &pen)=0
Set the pen of the painter.
CatalogObjectVector get_objects_in_trixel_no_nulls(const int trixel)
Definition: catalogsdb.h:259
qint64 currentMSecsSinceEpoch()
Manages the catalog database and provides an interface to provide an interface to query and modify th...
Definition: catalogsdb.h:181
std::tuple< bool, QString, CatalogsDB::CatalogObjectVector > get_objects(QSqlDatabase db, const std::list< int > &ids={ 1, 2 })
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.
bool selected() override
Wether to show the DSOs.
QString i18n(const char *text, const TYPE &arg...)
CatalogObjectList find_objects_by_name(const QString &name, const int limit=-1, const bool exactMatchOnly=false)
Find an objects by name.
Definition: catalogsdb.cpp:489
CatalogObject & insertStaticObject(const CatalogObject &obj)
Insert an object obj into m_static_objects and return a reference to the newly inserted object.
QHash::const_iterator constBegin() const const
QHash::const_iterator constEnd() const const
ColorScheme * colorScheme()
Definition: kstarsdata.h:171
void JITupdate()
Update the cooridnates and the horizontal coordinates if updateID or updateNumID have changed (global...
virtual void setBrush(const QBrush &brush)=0
Set the brush of the painter.
void draw(SkyPainter *skyp) override
Draws the objects in the currently visible trixels by dynamically loading them from the database.
Q_INVOKABLE QString colorSchemeFileName()
Definition: kstarsdata.h:177
Draws things on the sky, without regard to backend.
Definition: skypainter.h:38
bool hasNext() const
true if there are more trixel to iterate over.
Definition: MeshIterator.h:27
CatalogObjectVector get_objects_in_trixel(const int trixel)
Definition: catalogsdb.h:252
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
int htmesh_level() const
Definition: catalogsdb.h:357
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void detailedError(QWidget *parent, const QString &text, const QString &details, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
Canvas widget for displaying the sky bitmap; also handles user interaction events.
Definition: skymap.h:52
void objectsInArea(QList< SkyObject * > &list, const SkyRegion &region) override
Searches the region(s) and appends the SkyObjects found to the list of sky objects.
ButtonCode questionYesNoCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), 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)
virtual bool drawCatalogObject(const CatalogObject &obj)=0
Draw a deep sky object (loaded from the new implementation)
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.
Trixel index(const SkyPoint *p)
returns the index of the trixel containing p.
Definition: skymesh.cpp:74
void blockingMap(Sequence &sequence, MapFunctor function)
QFuture< void > map(Sequence &sequence, MapFunctor function)
std::pair< bool, QSqlDatabase > get_skycomp_db(const QString &path)
Get the skycomponent database from the specified path.
QColor colorNamed(const QString &name) const
Retrieve a color by name.
Definition: colorscheme.cpp:87
A simple container object to hold the minimum information for a Deep Sky Object to be drawn on the sk...
Definition: catalogobject.h:40
Information about an object in the sky.
Definition: skyobject.h:41
static SkyMesh * Create(int level)
creates the single instance of SkyMesh.
Definition: skymesh.cpp:25
A simple integer indexed elastically cached wrapper around std::vector to hold container types conten...
Definition: trixelcache.h:59
Trixel next() const
returns the next trixel
Definition: MeshIterator.h:31
Database related error, thrown when database access fails or an action does not succeed.
Definition: catalogsdb.h:686
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:57:29 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.