Kstars

artificialhorizoncomponent.cpp
1 /*
2  SPDX-FileCopyrightText: 2015 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "artificialhorizoncomponent.h"
8 
9 #include "greatcircle.h"
10 #include "kstarsdata.h"
11 #include "linelist.h"
12 #include "Options.h"
13 #include "skymap.h"
14 #include "skymapcomposite.h"
15 #include "skypainter.h"
16 #include "projections/projector.h"
17 
18 #define UNDEFINED_ALTITUDE -90
19 
20 ArtificialHorizonEntity::~ArtificialHorizonEntity()
21 {
22  clearList();
23 }
24 
25 QString ArtificialHorizonEntity::region() const
26 {
27  return m_Region;
28 }
29 
30 void ArtificialHorizonEntity::setRegion(const QString &Region)
31 {
32  m_Region = Region;
33 }
34 
35 bool ArtificialHorizonEntity::enabled() const
36 {
37  return m_Enabled;
38 }
39 
40 void ArtificialHorizonEntity::setEnabled(bool Enabled)
41 {
42  m_Enabled = Enabled;
43 }
44 
45 bool ArtificialHorizonEntity::ceiling() const
46 {
47  return m_Ceiling;
48 }
49 
50 void ArtificialHorizonEntity::setCeiling(bool value)
51 {
52  m_Ceiling = value;
53 }
54 
55 void ArtificialHorizonEntity::setList(const std::shared_ptr<LineList> &list)
56 {
57  m_List = list;
58 }
59 
60 std::shared_ptr<LineList> ArtificialHorizonEntity::list() const
61 {
62  return m_List;
63 }
64 
65 void ArtificialHorizonEntity::clearList()
66 {
67  m_List.reset();
68 }
69 
70 namespace
71 {
72 
73 // Returns true if angle is "in between" range1 and range2, two other angles,
74 // where in-between means "the short way".
75 bool inBetween(const dms &angle, const dms &range1, const dms &range2)
76 {
77  const double rangeDelta = fabs(range1.deltaAngle(range2).Degrees());
78  const double delta1 = fabs(range1.deltaAngle(angle).Degrees());
79  const double delta2 = fabs(range2.deltaAngle(angle).Degrees());
80  // The angle is between range1 and range2 if its two distances to each are both
81  // less than the range distance.
82  return delta1 <= rangeDelta && delta2 <= rangeDelta;
83 }
84 } // namespace
85 
86 double ArtificialHorizonEntity::altitudeConstraint(double azimuthDegrees, bool *constraintExists) const
87 {
88  *constraintExists = false;
89  if (m_List == nullptr)
90  return UNDEFINED_ALTITUDE;
91 
92  SkyList *points = m_List->points();
93  if (points == nullptr)
94  return UNDEFINED_ALTITUDE;
95 
96  double constraint = !m_Ceiling ? UNDEFINED_ALTITUDE : 90.0;
97  dms desiredAzimuth(azimuthDegrees);
98  dms lastAz;
99  double lastAlt = 0;
100  bool firstOne = true;
101  for (auto &p : *points)
102  {
103  const dms az = p->az();
104  const double alt = p->alt().Degrees();
105  if (qIsNaN(az.Degrees()) || qIsNaN(alt)) continue;
106  if (!firstOne && inBetween(desiredAzimuth, lastAz, az))
107  {
108  *constraintExists = true;
109  // If the input angle is in the interval between the last two points,
110  // interpolate the altitude constraint, and use that value.
111  // If there are other line segments which also contain the point,
112  // we use the max constraint. Convert to GreatCircle?
113  const double totalDelta = fabs(lastAz.deltaAngle(az).Degrees());
114  if (totalDelta <= 0)
115  {
116  if (!m_Ceiling)
117  constraint = std::max(constraint, alt);
118  else
119  constraint = std::min(constraint, alt);
120  }
121  else
122  {
123  const double deltaToLast = fabs(lastAz.deltaAngle(desiredAzimuth).Degrees());
124  const double weight = deltaToLast / totalDelta;
125  const double newConstraint = (1.0 - weight) * lastAlt + weight * alt;
126  if (!m_Ceiling)
127  constraint = std::max(constraint, newConstraint);
128  else
129  constraint = std::min(constraint, newConstraint);
130  }
131  }
132  firstOne = false;
133  lastAz = az;
134  lastAlt = alt;
135  }
136  return constraint;
137 }
138 
139 ArtificialHorizonComponent::ArtificialHorizonComponent(SkyComposite *parent)
140  : NoPrecessIndex(parent, i18n("Artificial Horizon"))
141 {
142  load();
143 }
144 
145 ArtificialHorizonComponent::~ArtificialHorizonComponent()
146 {
147 }
148 
149 ArtificialHorizon::~ArtificialHorizon()
150 {
151  qDeleteAll(m_HorizonList);
152  m_HorizonList.clear();
153 }
154 
155 void ArtificialHorizon::load(const QList<ArtificialHorizonEntity *> &list)
156 {
157  m_HorizonList = list;
158  resetPrecomputeConstraints();
159 }
160 
161 bool ArtificialHorizonComponent::load()
162 {
163  horizon.load(KStarsData::Instance()->userdb()->GetAllHorizons());
164 
165  foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
166  appendLine(horizon->list());
167 
168  return true;
169 }
170 
171 void ArtificialHorizonComponent::save()
172 {
173  KStarsData::Instance()->userdb()->DeleteAllHorizons();
174 
175  foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
176  KStarsData::Instance()->userdb()->AddHorizon(horizon);
177 }
178 
179 bool ArtificialHorizonComponent::selected()
180 {
181  return Options::showGround();
182 }
183 
184 void ArtificialHorizonComponent::preDraw(SkyPainter *skyp)
185 {
186  QColor color(KStarsData::Instance()->colorScheme()->colorNamed("ArtificialHorizonColor"));
187  color.setAlpha(40);
188  skyp->setBrush(QBrush(color));
189  skyp->setPen(Qt::NoPen);
190 }
191 
192 namespace
193 {
194 
195 // Returns an equivalent degrees in the range 0 <= 0 < 360
196 double normalizeDegrees(double degrees)
197 {
198  while (degrees < 0)
199  degrees += 360;
200  while (degrees >= 360.0)
201  degrees -= 360.0;
202  return degrees;
203 }
204 
205 // Draws a "round polygon", sampling a circle every 45 degrees, with the given radius,
206 // centered on the SkyPoint.
207 void drawHorizonPoint(const SkyPoint &pt, double radius, SkyPainter *painter)
208 
209 {
210  LineList region;
211  double az = pt.az().Degrees(), alt = pt.alt().Degrees();
212 
213  for (double angle = 0; angle < 360; angle += 45)
214  {
215  double radians = angle * 2 * M_PI / 360.0;
216  double az1 = az + radius * cos(radians);
217  double alt1 = alt + radius * sin(radians);
218  std::shared_ptr<SkyPoint> sp(new SkyPoint());
219  sp->setAz(az1);
220  sp->setAlt(alt1);
221  sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
222  region.append(sp);
223  }
224  // Repeat the first point.
225  double az1 = az + radius * cos(0);
226  double alt1 = alt + radius * sin(0);
227  std::shared_ptr<SkyPoint> sp(new SkyPoint());
228  sp->setAz(az1);
229  sp->setAlt(alt1);
230  sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
231  region.append(sp);
232 
233  painter->drawSkyPolygon(&region, false);
234 }
235 
236 // Draws a series of points whose coordinates are given by the LineList.
237 void drawHorizonPoints(LineList *lineList, SkyPainter *painter)
238 {
239  const SkyList &points = *(lineList->points());
240  for (int i = 0; i < points.size(); ++i)
241  {
242  const SkyPoint &pt = *points[i];
243  if (qIsNaN(pt.az().Degrees()) || qIsNaN(pt.alt().Degrees()))
244  continue;
245  drawHorizonPoint(pt, .5, painter);
246  }
247 }
248 
249 // Draws a points that is larger than the one drawn by drawHorizonPoint().
250 // The point's coordinates are the ith (index) point in the LineList.
251 void drawSelectedPoint(LineList *lineList, int index, SkyPainter *painter)
252 {
253  if (index >= 0 && index < lineList->points()->size())
254  {
255  const SkyList &points = *(lineList->points());
256  const SkyPoint &pt = *points[index];
257  if (qIsNaN(pt.az().Degrees()) || qIsNaN(pt.alt().Degrees()))
258  return;
259  drawHorizonPoint(pt, 1.0, painter);
260  }
261 }
262 
263 // This creates a set of connected line segments from az1,alt1 to az2,alt2, sampling
264 // points on the great circle between az1,alt1 and az2,alt2 every 2 degrees or so.
265 // The errors would be obvious for longer lines if we just drew a standard line.
266 // If testing is true, HorizontalToEquatorial is not called.
267 void appendGreatCirclePoints(double az1, double alt1, double az2, double alt2, LineList *region, bool testing)
268 {
269  constexpr double sampling = 2.0; // degrees
270  const double maxAngleDiff = std::max(fabs(az1 - az2), fabs(alt1 - alt2));
271  const int numSamples = maxAngleDiff / sampling;
272 
273  if (numSamples > 1)
274  {
275  GreatCircle gc(az1, alt1, az2, alt2);
276  for (int i = 1; i < numSamples; ++i)
277  {
278  const double fraction = i / static_cast<double>(numSamples);
279  double az, alt;
280  gc.waypoint(fraction, &az, &alt);
281  std::shared_ptr<SkyPoint> sp(new SkyPoint());
282  sp->setAz(az);
283  sp->setAlt(alt);
284  if (!testing)
285  sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
286  region->append(sp);
287  }
288  }
289  std::shared_ptr<SkyPoint> sp(new SkyPoint());
290  sp->setAz(az2);
291  sp->setAlt(alt2);
292  // Is HorizontalToEquatorial necessary in any case?
293  if (!testing)
294  sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
295  region->append(sp);
296 }
297 
298 } // namespace
299 
300 // Draws a polygon, where one of the sides is az1,alt1 --> az2,alt2 (except that's implemented as series
301 // of connected line segments along a great circle).
302 // It figures out the opposite side depending on the type of the constraint for this entity
303 // (horizon line or ceiling) and the other contraints that are enabled.
304 bool ArtificialHorizon::computePolygon(int entity, double az1, double alt1, double az2, double alt2,
305  LineList *region)
306 {
307  const bool ceiling = horizonList()->at(entity)->ceiling();
308  const ArtificialHorizonEntity *thisOne = horizonList()->at(entity);
309  double alt1b = 0, alt2b = 0;
310  bool exists = false;
311  if (!ceiling)
312  {
313  // For standard horizon lines, the polygon is drawn down to the next lower-altitude
314  // enabled line, or to the horizon if a lower line doesn't exist.
315  const ArtificialHorizonEntity *constraint = getConstraintBelow(az1, alt1, thisOne);
316  if (constraint != nullptr)
317  {
318  double alt = constraint->altitudeConstraint(az1, &exists);
319  if (exists)
320  alt1b = alt;
321  }
322  constraint = getConstraintBelow(az2, alt2, thisOne);
323  if (constraint != nullptr)
324  {
325  double alt = constraint->altitudeConstraint(az2, &exists);
326  if (exists)
327  alt2b = alt;
328  }
329  }
330  else
331  {
332  // For ceiling lines, the polygon is drawn up to the next higher-altitude enabled line
333  // but only if that line is another cieling, otherwise it not drawn at all (because that
334  // horizon line will do the drawing).
335  const ArtificialHorizonEntity *constraint = getConstraintAbove(az1, alt1, thisOne);
336  alt1b = 90;
337  alt2b = 90;
338  if (constraint != nullptr)
339  {
340  if (!constraint->ceiling()) return false;
341  double alt = constraint->altitudeConstraint(az1, &exists);
342  if (exists) alt1b = alt;
343  }
344  constraint = getConstraintAbove(az2, alt2, thisOne);
345  if (constraint != nullptr)
346  {
347  if (!constraint->ceiling()) return false;
348  double alt = constraint->altitudeConstraint(az2, &exists);
349  if (exists) alt2b = alt;
350  }
351  }
352 
353  std::shared_ptr<SkyPoint> sp(new SkyPoint());
354  sp->setAz(az1);
355  sp->setAlt(alt1b);
356  if (!testing)
357  sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
358  region->append(sp);
359 
360  appendGreatCirclePoints(az1, alt1b, az1, alt1, region, testing);
361  appendGreatCirclePoints(az1, alt1, az2, alt2, region, testing);
362  appendGreatCirclePoints(az2, alt2, az2, alt2b, region, testing);
363  return true;
364 }
365 
366 // Draws a series of polygons of width in azimuth of "sampling degrees".
367 // Drawing a single polygon would have "great-circle issues". This looks a lot better.
368 // Assumes az1 and az2 in range 0-360 and az1 < az2.
369 // regions is only not nullptr during testing. In this wasy we can test
370 // whether the appropriate regions are drawn.
371 void ArtificialHorizon::drawSampledPolygons(int entity, double az1, double alt1, double az2, double alt2,
372  double sampling, SkyPainter *painter, QList<LineList> *regions)
373 {
374  if (az1 > az2)
375  {
376  // Should not happen.
377  fprintf(stderr, "Bad input to artificialhorizoncomponent.cpp::DrawSampledPolygons\n");
378  return;
379  }
380  double lastAz = az1;
381  double lastAlt = alt1;
382  const double azRange = az2 - az1, altRange = alt2 - alt1;
383  if (azRange == 0) return;
384  for (double az = az1 + sampling; az < az2; az += sampling)
385  {
386  double alt = alt1 + altRange * (az - az1) / azRange;
387 
388  LineList region;
389  if (computePolygon(entity, lastAz, lastAlt, az, alt, &region))
390  {
391  if (painter != nullptr)
392  painter->drawSkyPolygon(&region, false);
393  if (regions != nullptr)
394  regions->append(region);
395  }
396  lastAz = az;
397  lastAlt = alt;
398  }
399  LineList region;
400  if (computePolygon(entity, lastAz, lastAlt, az2, alt2, &region))
401  {
402  if (painter != nullptr)
403  painter->drawSkyPolygon(&region, false);
404  if (regions != nullptr)
405  regions->append(region);
406  }
407 }
408 
409 // This draws a series of polygons that fill the area that the horizon entity with index "entity"
410 // is responsible for. If that is a horizon line, it draws it down to the horizon, or to the next
411 // lower line. It draws the polygons one pair of points at a time, and deals with complications
412 // of when the azimuth angle wraps around 360 degrees.
413 void ArtificialHorizon::drawPolygons(int entity, SkyPainter *painter, QList<LineList> *regions)
414 {
415  const ArtificialHorizonEntity &ah = *(horizonList()->at(entity));
416  const SkyList &points = *(ah.list()->points());
417 
418  // The skylist shouldn't contain NaN values, but, it has in the past,
419  // and, to be cautious, this checks for them and removes points with NaNs.
420  int start = 0;
421  for (; start < points.size(); ++start)
422  {
423  const SkyPoint &p = *points[start];
424  if (!qIsNaN(p.az().Degrees()) && !qIsNaN(p.alt().Degrees()))
425  break;
426  }
427  for (int i = start + 1; i < points.size(); ++i)
428  {
429  const SkyPoint &p2 = *points[i];
430  if (qIsNaN(p2.az().Degrees()) || qIsNaN(p2.alt().Degrees()))
431  continue;
432  const SkyPoint &p1 = *points[start];
433  start = i;
434 
435  const double az1 = normalizeDegrees(p1.az().Degrees());
436  const double az2 = normalizeDegrees(p2.az().Degrees());
437 
438  double minAz, maxAz, minAzAlt, maxAzAlt;
439  if (az1 < az2)
440  {
441  minAz = az1;
442  minAzAlt = p1.alt().Degrees();
443  maxAz = az2;
444  maxAzAlt = p2.alt().Degrees();
445  }
446  else
447  {
448  minAz = az2;
449  minAzAlt = p2.alt().Degrees();
450  maxAz = az1;
451  maxAzAlt = p1.alt().Degrees();
452  }
453  const bool wrapAround = !inBetween(dms((minAz + maxAz) / 2.0), dms(minAz), dms(maxAz));
454  constexpr double sampling = 0.1; // Draw a polygon for every degree in Azimuth
455  if (wrapAround)
456  {
457  // We've detected that the line segment crosses 0 degrees.
458  // Draw one polygon on one side of 0 degrees, and another on the other side.
459  // Compute the altitude at wrap-around.
460  const double fraction = fabs(dms(360.0).deltaAngle(dms(maxAz)).Degrees() /
461  p1.az().deltaAngle(p2.az()).Degrees());
462  const double midAlt = minAzAlt + fraction * (maxAzAlt - minAzAlt);
463  // Draw polygons form maxAz upto 0 degrees, then again from 0 to minAz.
464  drawSampledPolygons(entity, maxAz, maxAzAlt, 360.0, midAlt, sampling, painter, regions);
465  drawSampledPolygons(entity, 0, midAlt, minAz, minAzAlt, sampling, painter, regions);
466  }
467  else
468  {
469  // Draw the polygons without wraparound
470  drawSampledPolygons(entity, minAz, minAzAlt, maxAz, maxAzAlt, sampling, painter, regions);
471  }
472  }
473 }
474 
475 void ArtificialHorizon::drawPolygons(SkyPainter *painter, QList<LineList> *regions)
476 {
477  for (int i = 0; i < horizonList()->size(); i++)
478  {
479  if (enabled(i))
480  drawPolygons(i, painter, regions);
481  }
482 }
483 
484 void ArtificialHorizonComponent::draw(SkyPainter *skyp)
485 {
486  if (!selected())
487  return;
488 
489  if (livePreview.get())
490  {
491  if ((livePreview->points() != nullptr) && (livePreview->points()->size() > 0))
492  {
493  // Draws a series of line segments, overlayed by the vertices.
494  // One vertex (the current selection) is emphasized.
495  skyp->setPen(QPen(Qt::white, 2));
496  skyp->drawSkyPolyline(livePreview.get());
497  skyp->setBrush(QBrush(Qt::yellow));
498  drawSelectedPoint(livePreview.get(), selectedPreviewPoint, skyp);
499  skyp->setBrush(QBrush(Qt::red));
500  drawHorizonPoints(livePreview.get(), skyp);
501  }
502  }
503 
504  preDraw(skyp);
505 
506  QList<LineList> regions;
507  horizon.drawPolygons(skyp, &regions);
508 }
509 
510 bool ArtificialHorizon::enabled(int i) const
511 {
512  return m_HorizonList.at(i)->enabled();
513 }
514 
515 ArtificialHorizonEntity *ArtificialHorizon::findRegion(const QString &regionName)
516 {
517  ArtificialHorizonEntity *regionHorizon = nullptr;
518 
519  foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
520  {
521  if (horizon->region() == regionName)
522  {
523  regionHorizon = horizon;
524  break;
525  }
526  }
527 
528  return regionHorizon;
529 }
530 
531 void ArtificialHorizon::removeRegion(const QString &regionName, bool lineOnly)
532 {
533  ArtificialHorizonEntity *regionHorizon = findRegion(regionName);
534 
535  if (regionHorizon == nullptr)
536  return;
537 
538  if (lineOnly)
539  regionHorizon->clearList();
540  else
541  {
542  m_HorizonList.removeOne(regionHorizon);
543  delete (regionHorizon);
544  }
545  resetPrecomputeConstraints();
546 }
547 
548 void ArtificialHorizonComponent::removeRegion(const QString &regionName, bool lineOnly)
549 {
550  ArtificialHorizonEntity *regionHorizon = horizon.findRegion(regionName);
551  if (regionHorizon != nullptr && regionHorizon->list())
552  removeLine(regionHorizon->list());
553  horizon.removeRegion(regionName, lineOnly);
554 }
555 
556 void ArtificialHorizon::addRegion(const QString &regionName, bool enabled, const std::shared_ptr<LineList> &list,
557  bool ceiling)
558 {
559  ArtificialHorizonEntity *horizon = new ArtificialHorizonEntity;
560 
561  horizon->setRegion(regionName);
562  horizon->setEnabled(enabled);
563  horizon->setCeiling(ceiling);
564  horizon->setList(list);
565 
566  m_HorizonList.append(horizon);
567  resetPrecomputeConstraints();
568 }
569 
570 void ArtificialHorizonComponent::addRegion(const QString &regionName, bool enabled, const std::shared_ptr<LineList> &list,
571  bool ceiling)
572 {
573  horizon.addRegion(regionName, enabled, list, ceiling);
574  appendLine(list);
575 }
576 
577 bool ArtificialHorizon::altitudeConstraintsExist() const
578 {
579  foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
580  {
581  if (horizon->enabled())
582  return true;
583  }
584  return false;
585 }
586 
587 const ArtificialHorizonEntity *ArtificialHorizon::getConstraintAbove(double azimuthDegrees, double altitudeDegrees,
588  const ArtificialHorizonEntity *ignore) const
589 {
590  double closestAbove = 1e6;
591  const ArtificialHorizonEntity *entity = nullptr;
592 
593  foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
594  {
595  if (!horizon->enabled()) continue;
596  if (horizon == ignore) continue;
597  bool constraintExists = false;
598  double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
599  // This horizon doesn't constrain this azimuth.
600  if (!constraintExists) continue;
601 
602  double altitudeDiff = constraint - altitudeDegrees;
603  if (altitudeDiff > 0 && constraint < closestAbove)
604  {
605  closestAbove = constraint;
606  entity = horizon;
607  }
608  }
609  return entity;
610 }
611 
612 // Estimate the horizon contraint to .1 degrees.
613 // This significantly speeds up computation.
614 constexpr int PRECOMPUTED_RESOLUTION = 10;
615 
616 double ArtificialHorizon::altitudeConstraint(double azimuthDegrees) const
617 {
618  if (precomputedConstraints.size() != 360 * PRECOMPUTED_RESOLUTION)
619  precomputeConstraints();
620  return precomputedConstraint(azimuthDegrees);
621 }
622 
623 double ArtificialHorizon::altitudeConstraintInternal(double azimuthDegrees) const
624 {
625  const ArtificialHorizonEntity *horizonBelow = getConstraintBelow(azimuthDegrees, 90.0, nullptr);
626  if (horizonBelow == nullptr)
627  return UNDEFINED_ALTITUDE;
628  bool ignore = false;
629  return horizonBelow->altitudeConstraint(azimuthDegrees, &ignore);
630 }
631 
632 // Quantize the constraints to within .1 degrees (so there are 360*10=3600
633 // precomputed values).
634 void ArtificialHorizon::precomputeConstraints() const
635 {
636  precomputedConstraints.clear();
637  precomputedConstraints.fill(0, 360 * PRECOMPUTED_RESOLUTION);
638  for (int i = 0; i < 360 * PRECOMPUTED_RESOLUTION; ++i)
639  {
640  const double az = i / static_cast<double>(PRECOMPUTED_RESOLUTION);
641  precomputedConstraints[i] = altitudeConstraintInternal(az);
642  }
643 }
644 
645 void ArtificialHorizon::resetPrecomputeConstraints() const
646 {
647  precomputedConstraints.clear();
648 }
649 
650 double ArtificialHorizon::precomputedConstraint(double azimuth) const
651 {
652  constexpr int maxval = 360 * PRECOMPUTED_RESOLUTION;
653  int index = azimuth * PRECOMPUTED_RESOLUTION + 0.5;
654  if (index == maxval)
655  index = 0;
656  if (index < 0 || index >= precomputedConstraints.size())
657  return UNDEFINED_ALTITUDE;
658  return precomputedConstraints[index];
659 }
660 
661 const ArtificialHorizonEntity *ArtificialHorizon::getConstraintBelow(double azimuthDegrees, double altitudeDegrees,
662  const ArtificialHorizonEntity *ignore) const
663 {
664  double closestBelow = -1e6;
665  const ArtificialHorizonEntity *entity = nullptr;
666 
667  foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
668  {
669 
670  if (!horizon->enabled()) continue;
671  if (horizon == ignore) continue;
672  bool constraintExists = false;
673  double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
674  // This horizon doesn't constrain this azimuth.
675  if (!constraintExists) continue;
676 
677  double altitudeDiff = constraint - altitudeDegrees;
678  if (altitudeDiff < 0 && constraint > closestBelow)
679  {
680  closestBelow = constraint;
681  entity = horizon;
682  }
683  }
684  return entity;
685 }
686 
687 // An altitude is blocked (not visible) if either:
688 // - there are constraints above and the closest above constraint is not a ceiling, or
689 // - there are constraints below and the closest below constraint is a ceiling.
690 bool ArtificialHorizon::isVisible(double azimuthDegrees, double altitudeDegrees) const
691 {
692  const ArtificialHorizonEntity *above = getConstraintAbove(azimuthDegrees, altitudeDegrees);
693  if (above != nullptr && !above->ceiling()) return false;
694  const ArtificialHorizonEntity *below = getConstraintBelow(azimuthDegrees, altitudeDegrees);
695  if (below != nullptr && below->ceiling()) return false;
696  return true;
697 }
void append(const T &value)
const dms & alt() const
Definition: skypoint.h:281
QVector< T > & fill(const T &value, int size)
Stores dms coordinates for a point in the sky. for converting between coordinate systems.
Definition: skypoint.h:44
virtual void setPen(const QPen &pen)=0
Set the pen of the painter.
A class to compute points along a great circle from one az/alt to another.
Definition: greatcircle.h:27
QAction * load(const QObject *recvr, const char *slot, QObject *parent)
KSUserDB * userdb()
Definition: kstarsdata.h:214
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
int size() const const
void clear()
QString i18n(const char *text, const TYPE &arg...)
bool removeOne(const T &value)
const T & at(int i) const const
virtual void setBrush(const QBrush &brush)=0
Set the brush of the painter.
GeoCoordinates geo(const QVariant &location)
Draws things on the sky, without regard to backend.
Definition: skypainter.h:37
virtual void drawSkyPolygon(LineList *list, bool forceClip=true)=0
Draw a polygon in the sky.
SkyList * points()
return the list of points for iterating or appending (or whatever).
Definition: linelist.h:33
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
const double & Degrees() const
Definition: dms.h:141
const dms deltaAngle(dms angle) const
deltaAngle Return the shortest difference (path) between this angle and the supplied angle.
Definition: dms.cpp:259
void clear()
int size() const const
virtual void drawSkyPolyline(LineList *list, SkipHashList *skipList=nullptr, LineListLabel *label=nullptr)=0
Draw a polyline in the sky.
const dms & az() const
Definition: skypoint.h:275
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Aug 13 2022 04:01:50 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.