7#include "artificialhorizoncomponent.h"
9#include "greatcircle.h"
10#include "kstarsdata.h"
14#include "skymapcomposite.h"
15#include "skypainter.h"
16#include "projections/projector.h"
18#define UNDEFINED_ALTITUDE -90
20ArtificialHorizonEntity::~ArtificialHorizonEntity()
25QString ArtificialHorizonEntity::region()
const
30void ArtificialHorizonEntity::setRegion(
const QString &Region)
35bool ArtificialHorizonEntity::enabled()
const
40void ArtificialHorizonEntity::setEnabled(
bool Enabled)
45bool ArtificialHorizonEntity::ceiling()
const
50void ArtificialHorizonEntity::setCeiling(
bool value)
55void ArtificialHorizonEntity::setList(
const std::shared_ptr<LineList> &list)
60std::shared_ptr<LineList> ArtificialHorizonEntity::list()
const
65void ArtificialHorizonEntity::clearList()
75bool inBetween(
const dms &angle,
const dms &range1,
const dms &range2)
82 return delta1 <= rangeDelta && delta2 <= rangeDelta;
86double ArtificialHorizonEntity::altitudeConstraint(
double azimuthDegrees,
bool *constraintExists)
const
88 *constraintExists =
false;
89 if (m_List ==
nullptr)
90 return UNDEFINED_ALTITUDE;
92 SkyList *points = m_List->points();
93 if (points ==
nullptr)
94 return UNDEFINED_ALTITUDE;
96 double constraint = !m_Ceiling ? UNDEFINED_ALTITUDE : 90.0;
97 dms desiredAzimuth(azimuthDegrees);
100 bool firstOne =
true;
101 for (
auto &p : *points)
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))
108 *constraintExists =
true;
117 constraint = std::max(constraint, alt);
119 constraint = std::min(constraint, alt);
124 const double weight = deltaToLast / totalDelta;
125 const double newConstraint = (1.0 - weight) * lastAlt + weight * alt;
127 constraint = std::max(constraint, newConstraint);
129 constraint = std::min(constraint, newConstraint);
139ArtificialHorizonComponent::ArtificialHorizonComponent(
SkyComposite *parent)
145ArtificialHorizonComponent::~ArtificialHorizonComponent()
149ArtificialHorizon::~ArtificialHorizon()
151 qDeleteAll(m_HorizonList);
152 m_HorizonList.
clear();
157 m_HorizonList =
list;
158 resetPrecomputeConstraints();
162bool ArtificialHorizonComponent::load()
168 foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
174void ArtificialHorizonComponent::save()
178 foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
182bool ArtificialHorizonComponent::selected()
184 return Options::showGround();
187void ArtificialHorizonComponent::preDraw(
SkyPainter *skyp)
189 QColor color(KStarsData::Instance()->colorScheme()->colorNamed(
"ArtificialHorizonColor"));
199double normalizeDegrees(
double degrees)
203 while (degrees >= 360.0)
216 for (
double angle = 0; angle < 360; angle += 45)
218 double radians = angle * 2 * M_PI / 360.0;
219 double az1 = az + radius * cos(radians);
220 double alt1 = alt + radius * sin(radians);
221 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
224 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
228 double az1 = az + radius * cos(0);
229 double alt1 = alt + radius * sin(0);
230 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
233 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
243 for (
int i = 0; i < points.size(); ++i)
248 drawHorizonPoint(pt, .5, painter);
256 if (index >= 0 && index < lineList->points()->size())
259 const SkyPoint &pt = *points[index];
262 drawHorizonPoint(pt, 1.0, painter);
270void appendGreatCirclePoints(
double az1,
double alt1,
double az2,
double alt2,
LineList *region,
bool testing)
272 constexpr double sampling = 2.0;
273 const double maxAngleDiff = std::max(fabs(az1 - az2), fabs(alt1 - alt2));
274 const int numSamples = maxAngleDiff / sampling;
287 for (
int i = 1; i < numSamples; ++i)
289 const double fraction = i /
static_cast<double>(numSamples);
291 gc.waypoint(fraction, &az, &alt);
292 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
296 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
300 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
305 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
315bool ArtificialHorizon::computePolygon(
int entity,
double az1,
double alt1,
double az2,
double alt2,
318 const bool ceiling = horizonList()->
at(entity)->ceiling();
319 const ArtificialHorizonEntity *thisOne = horizonList()->
at(entity);
320 double alt1b = 0, alt2b = 0;
325 double lastAlt = alt1;
326 const double azRange = az2 - az1, altRange = alt2 - alt1;
331 if (az1 + sampling > az2)
332 sampling = (az2 - az1) - 1e-6;
334 for (
double az = az1 + sampling; az <= az2; az += sampling)
337 if (az + sampling > az2)
340 double alt = alt1 + altRange * (az - az1) / azRange;
341 double alt1b = 0, alt2b = 0;
347 const ArtificialHorizonEntity *constraint = getConstraintBelow(lastAz, lastAlt, thisOne);
348 if (constraint !=
nullptr)
350 double altTemp = constraint->altitudeConstraint(lastAz, &exists);
354 constraint = getConstraintBelow(az, alt, thisOne);
355 if (constraint !=
nullptr)
357 double altTemp = constraint->altitudeConstraint(az, &exists);
361 appendGreatCirclePoints(lastAz, lastAlt, az, alt, &top, testing);
362 appendGreatCirclePoints(lastAz, alt1b, az, alt2b, &bottom, testing);
369 const ArtificialHorizonEntity *constraint = getConstraintAbove(lastAz, lastAlt, thisOne);
372 if (constraint !=
nullptr)
374 if (constraint->ceiling())
return false;
375 double altTemp = constraint->altitudeConstraint(lastAz, &exists);
376 if (exists) alt1b = altTemp;
378 constraint = getConstraintAbove(az, alt, thisOne);
379 if (constraint !=
nullptr)
381 if (constraint->ceiling())
return false;
382 double altTemp = constraint->altitudeConstraint(az, &exists);
383 if (exists) alt2b = altTemp;
385 appendGreatCirclePoints(lastAz, lastAlt, az, alt, &top, testing);
387 appendGreatCirclePoints(lastAz, alt1b, az, alt2b, &bottom, testing);
397 const ArtificialHorizonEntity *constraint = getConstraintBelow(az1, alt1, thisOne);
398 if (constraint !=
nullptr)
400 double altTemp = constraint->altitudeConstraint(az1, &exists);
404 appendGreatCirclePoints(az1, alt1b, az1, alt1, &left, testing);
406 const ArtificialHorizonEntity *constraint2 = getConstraintBelow(az2, alt2, thisOne);
407 if (constraint2 !=
nullptr)
409 double altTemp = constraint2->altitudeConstraint(az2, &exists);
413 appendGreatCirclePoints(az2, alt2, az2, alt2b, &right, testing);
420 const ArtificialHorizonEntity *constraint = getConstraintAbove(az1, alt1, thisOne);
423 if (constraint !=
nullptr)
425 if (!constraint->ceiling())
return false;
426 double altTemp = constraint->altitudeConstraint(az1, &exists);
427 if (exists) alt1b = altTemp;
429 appendGreatCirclePoints(az1, alt1b, az1, alt1, &left, testing);
431 const ArtificialHorizonEntity *constraint2 = getConstraintAbove(az2, alt2, thisOne);
432 if (constraint2 !=
nullptr)
434 if (!constraint2->ceiling())
return false;
435 double altTemp = constraint2->altitudeConstraint(az2, &exists);
436 if (exists) alt2b = altTemp;
438 appendGreatCirclePoints(az2, alt2, az2, alt2b, &right, testing);
443 for (
const auto &p : * (
left.points()))
445 for (
const auto &p : * (top.
points()))
447 for (
const auto &p : * (
right.points()))
449 for (
int i = bottom.
points()->size() - 1; i >= 0; i--)
450 region->append(bottom.
points()->at(i));
460void ArtificialHorizon::drawSampledPolygons(
int entity,
double az1,
double alt1,
double az2,
double alt2,
471 if (computePolygon(entity, az1, alt1, az2, alt2, sampling, ®ion))
473 if (painter !=
nullptr)
475 if (regions !=
nullptr)
486 const ArtificialHorizonEntity &ah = *(horizonList()->
at(entity));
487 const SkyList &points = *(ah.list()->points());
492 for (; start < points.size(); ++start)
499 for (
int i = start + 1; i < points.size(); ++i)
504 const SkyPoint &p1 = *points[start];
507 const double az1 = normalizeDegrees(p1.
az().
Degrees());
508 const double az2 = normalizeDegrees(p2.
az().
Degrees());
510 double minAz, maxAz, minAzAlt, maxAzAlt;
525 const bool wrapAround = !inBetween(
dms((minAz + maxAz) / 2.0),
dms(minAz),
dms(maxAz));
526 constexpr double sampling = 1.0;
532 const double fraction = fabs(
dms(360.0).deltaAngle(
dms(maxAz)).Degrees() /
534 const double midAlt = minAzAlt + fraction * (maxAzAlt - minAzAlt);
536 drawSampledPolygons(entity, maxAz, maxAzAlt, 360, midAlt, sampling, painter, regions);
537 drawSampledPolygons(entity, 0, midAlt, minAz, minAzAlt, sampling, painter, regions);
542 drawSampledPolygons(entity, minAz, minAzAlt, maxAz, maxAzAlt, sampling, painter, regions);
549 for (
int i = 0; i < horizonList()->
size(); i++)
552 drawPolygons(i, painter, regions);
556void ArtificialHorizonComponent::draw(
SkyPainter *skyp)
561 if (livePreview.get())
563 if ((livePreview->points() !=
nullptr) && (livePreview->points()->size() > 0))
570 drawSelectedPoint(livePreview.get(), selectedPreviewPoint, skyp);
572 drawHorizonPoints(livePreview.get(), skyp);
579 horizon.drawPolygons(skyp, ®ions);
582bool ArtificialHorizon::enabled(
int i)
const
584 return m_HorizonList.
at(i)->enabled();
587ArtificialHorizonEntity *ArtificialHorizon::findRegion(
const QString ®ionName)
589 ArtificialHorizonEntity *regionHorizon =
nullptr;
591 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
593 if (horizon->region() == regionName)
595 regionHorizon = horizon;
600 return regionHorizon;
603void ArtificialHorizon::removeRegion(
const QString ®ionName,
bool lineOnly)
605 ArtificialHorizonEntity *regionHorizon = findRegion(regionName);
607 if (regionHorizon ==
nullptr)
611 regionHorizon->clearList();
615 delete (regionHorizon);
617 resetPrecomputeConstraints();
621void ArtificialHorizonComponent::removeRegion(
const QString ®ionName,
bool lineOnly)
623 ArtificialHorizonEntity *regionHorizon = horizon.findRegion(regionName);
624 if (regionHorizon !=
nullptr && regionHorizon->list())
625 removeLine(regionHorizon->list());
626 horizon.removeRegion(regionName, lineOnly);
629void ArtificialHorizon::checkForCeilings()
631 noCeilingConstraints =
true;
632 for (
const auto &r : m_HorizonList)
634 if (r->ceiling() && r->enabled())
636 noCeilingConstraints =
false;
642void ArtificialHorizon::addRegion(
const QString ®ionName,
bool enabled,
const std::shared_ptr<LineList> &list,
645 ArtificialHorizonEntity *horizon =
new ArtificialHorizonEntity;
647 horizon->setRegion(regionName);
648 horizon->setEnabled(enabled);
649 horizon->setCeiling(ceiling);
650 horizon->setList(list);
652 m_HorizonList.append(horizon);
653 resetPrecomputeConstraints();
657void ArtificialHorizonComponent::addRegion(
const QString ®ionName,
bool enabled,
const std::shared_ptr<LineList> &list,
660 horizon.addRegion(regionName, enabled, list, ceiling);
664bool ArtificialHorizon::altitudeConstraintsExist()
const
666 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
668 if (horizon->enabled())
674const ArtificialHorizonEntity *ArtificialHorizon::getConstraintAbove(
double azimuthDegrees,
double altitudeDegrees,
675 const ArtificialHorizonEntity *ignore)
const
677 double closestAbove = 1e6;
678 const ArtificialHorizonEntity *entity =
nullptr;
680 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
682 if (!horizon->enabled())
continue;
683 if (horizon == ignore)
continue;
684 bool constraintExists =
false;
685 double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
687 if (!constraintExists)
continue;
689 double altitudeDiff = constraint - altitudeDegrees;
690 if (altitudeDiff > 0 && constraint < closestAbove)
692 closestAbove = constraint;
701constexpr int PRECOMPUTED_RESOLUTION = 10;
703double ArtificialHorizon::altitudeConstraint(
double azimuthDegrees)
const
705 if (precomputedConstraints.size() != 360 * PRECOMPUTED_RESOLUTION)
706 precomputeConstraints();
707 return precomputedConstraint(azimuthDegrees);
710double ArtificialHorizon::altitudeConstraintInternal(
double azimuthDegrees)
const
712 const ArtificialHorizonEntity *horizonBelow = getConstraintBelow(azimuthDegrees, 90.0,
nullptr);
713 if (horizonBelow ==
nullptr)
714 return UNDEFINED_ALTITUDE;
716 return horizonBelow->altitudeConstraint(azimuthDegrees, &ignore);
721void ArtificialHorizon::precomputeConstraints()
const
723 precomputedConstraints.clear();
724 precomputedConstraints.fill(0, 360 * PRECOMPUTED_RESOLUTION);
725 for (
int i = 0; i < 360 * PRECOMPUTED_RESOLUTION; ++i)
727 const double az = i /
static_cast<double>(PRECOMPUTED_RESOLUTION);
728 precomputedConstraints[i] = altitudeConstraintInternal(az);
732void ArtificialHorizon::resetPrecomputeConstraints()
const
734 precomputedConstraints.clear();
737double ArtificialHorizon::precomputedConstraint(
double azimuth)
const
739 constexpr int maxval = 360 * PRECOMPUTED_RESOLUTION;
740 int index = azimuth * PRECOMPUTED_RESOLUTION + 0.5;
743 if (index < 0 || index >= precomputedConstraints.size())
744 return UNDEFINED_ALTITUDE;
745 return precomputedConstraints[index];
748const ArtificialHorizonEntity *ArtificialHorizon::getConstraintBelow(
double azimuthDegrees,
double altitudeDegrees,
749 const ArtificialHorizonEntity *ignore)
const
751 double closestBelow = -1e6;
752 const ArtificialHorizonEntity *entity =
nullptr;
754 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
756 if (!horizon->enabled())
continue;
757 if (horizon == ignore)
continue;
758 bool constraintExists =
false;
759 double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
761 if (!constraintExists)
continue;
763 double altitudeDiff = constraint - altitudeDegrees;
764 if (altitudeDiff < 0 && constraint > closestBelow)
766 closestBelow = constraint;
773bool ArtificialHorizon::isAltitudeOK(
double azimuthDegrees,
double altitudeDegrees,
QString *reason)
const
775 if (noCeilingConstraints)
777 const double constraint = altitudeConstraint(azimuthDegrees);
778 if (altitudeDegrees >= constraint)
780 if (reason !=
nullptr)
781 *reason =
QString(
"altitude %1 < horizon %2").
arg(altitudeDegrees, 0,
'f', 1).
arg(constraint, 0,
'f', 1);
785 return isVisible(azimuthDegrees, altitudeDegrees, reason);
791bool ArtificialHorizon::isVisible(
double azimuthDegrees,
double altitudeDegrees,
QString *reason)
const
793 const ArtificialHorizonEntity *above = getConstraintAbove(azimuthDegrees, altitudeDegrees);
794 if (above !=
nullptr && !above->ceiling())
796 if (reason !=
nullptr)
799 double constraint = above->altitudeConstraint(azimuthDegrees, &ignoreMe);
800 *reason =
QString(
"altitude %1 < horizon %2").
arg(altitudeDegrees, 0,
'f', 1).
arg(constraint, 0,
'f', 1);
804 const ArtificialHorizonEntity *below = getConstraintBelow(azimuthDegrees, altitudeDegrees);
805 if (below !=
nullptr && below->ceiling())
807 if (reason !=
nullptr)
810 double constraint = below->altitudeConstraint(azimuthDegrees, &ignoreMe);
811 *reason =
QString(
"altitude %1 > ceiling %2").
arg(altitudeDegrees, 0,
'f', 1).
arg(constraint, 0,
'f', 1);
A class to compute points along a great circle from one az/alt to another.
bool GetAllHorizons(QList< ArtificialHorizonEntity * > &horizonList)
Gets all the artificial horizon rows from the database.
bool AddHorizon(ArtificialHorizonEntity *horizon)
Adds a new artificial horizon row into the database.
bool DeleteAllHorizons()
Deletes all artificial horizon rows from the database.
void appendLine(const std::shared_ptr< LineList > &lineList)
Typically called from within a subclasses constructors.
A simple data container used by LineListIndex.
SkyList * points()
return the list of points for iterating or appending (or whatever).
SkyComposite is a kind of container class for SkyComponent objects.
Draws things on the sky, without regard to backend.
virtual void drawSkyPolyline(LineList *list, SkipHashList *skipList=nullptr, LineListLabel *label=nullptr)=0
Draw a polyline in the sky.
virtual void drawSkyPolygon(LineList *list, bool forceClip=true)=0
Draw a polygon in the sky.
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.
An angle, stored as degrees, but expressible in many ways.
const dms deltaAngle(dms angle) const
deltaAngle Return the shortest difference (path) between this angle and the supplied angle.
const double & Degrees() const
QString i18n(const char *text, const TYPE &arg...)
QAction * load(const QObject *recvr, const char *slot, QObject *parent)
GeoCoordinates geo(const QVariant &location)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool removeOne(const AT &t)
qsizetype size() const const
QString arg(Args &&... args) const const
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)