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();
106 const double alt = std::min(89.999, p->alt().Degrees());
108 if (qIsNaN(az.
Degrees()) || qIsNaN(alt))
continue;
109 if (!firstOne && inBetween(desiredAzimuth, lastAz, az))
111 *constraintExists =
true;
120 constraint = std::max(constraint, alt);
122 constraint = std::min(constraint, alt);
127 const double newConstraint = gc.altAtAz(azimuthDegrees);
129 constraint = std::max(constraint, newConstraint);
131 constraint = std::min(constraint, newConstraint);
141ArtificialHorizonComponent::ArtificialHorizonComponent(
SkyComposite *parent)
147ArtificialHorizonComponent::~ArtificialHorizonComponent()
151ArtificialHorizon::~ArtificialHorizon()
153 qDeleteAll(m_HorizonList);
154 m_HorizonList.
clear();
159 m_HorizonList =
list;
160 resetPrecomputeConstraints();
164bool ArtificialHorizonComponent::load()
170 foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
176void ArtificialHorizonComponent::save()
180 foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
184bool ArtificialHorizonComponent::selected()
189void ArtificialHorizonComponent::preDraw(
SkyPainter *skyp)
191 QColor color(KStarsData::Instance()->colorScheme()->colorNamed(
"ArtificialHorizonColor"));
201double normalizeDegrees(
double degrees)
205 while (degrees >= 360.0)
218 for (
double angle = 0; angle < 360; angle += 45)
220 double radians = angle * 2 * M_PI / 360.0;
221 double az1 = az + radius * cos(radians);
222 double alt1 = alt + radius * sin(radians);
223 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
226 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
230 double az1 = az + radius * cos(0);
231 double alt1 = alt + radius * sin(0);
232 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
235 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
245 for (
int i = 0; i < points.size(); ++i)
250 drawHorizonPoint(pt, .5, painter);
258 if (index >= 0 && index < lineList->points()->size())
261 const SkyPoint &pt = *points[index];
264 drawHorizonPoint(pt, 1.0, painter);
272void appendGreatCirclePoints(
double az1,
double alt1,
double az2,
double alt2,
LineList *region,
bool testing)
274 constexpr double sampling = 2.0;
275 const double maxAngleDiff = std::max(fabs(az1 - az2), fabs(alt1 - alt2));
276 const int numSamples = maxAngleDiff / sampling;
289 for (
int i = 1; i < numSamples; ++i)
291 const double fraction = i /
static_cast<double>(numSamples);
293 gc.waypoint(fraction, &az, &alt);
294 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
298 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
302 std::shared_ptr<SkyPoint> sp(
new SkyPoint());
307 sp->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->
geo()->lat());
317bool ArtificialHorizon::computePolygon(
int entity,
double az1,
double alt1,
double az2,
double alt2,
321 if (alt1 >= 90 && alt2 >= 90)
326 const bool ceiling = horizonList()->
at(entity)->ceiling();
327 const ArtificialHorizonEntity *thisOne = horizonList()->
at(entity);
328 double alt1b = 0, alt2b = 0;
333 double lastAlt = alt1;
338 if (az1 + sampling > az2)
339 sampling = (az2 - az1) - 1e-6;
342 double numSamples = (az2 - az1) / sampling;
343 for (
int i = 0; i < numSamples; ++i)
345 double fraction = i / numSamples;
346 if (fraction + (1.0 / numSamples) > (1 + .0001))
350 gc.waypoint(fraction, &az, &alt);
351 double alt1b = 0, alt2b = 0;
357 const ArtificialHorizonEntity *constraint = getConstraintBelow(lastAz, lastAlt, thisOne);
358 if (constraint !=
nullptr)
360 double altTemp = constraint->altitudeConstraint(lastAz, &exists);
364 constraint = getConstraintBelow(az, alt, thisOne);
365 if (constraint !=
nullptr)
367 double altTemp = constraint->altitudeConstraint(az, &exists);
371 appendGreatCirclePoints(lastAz, lastAlt, az, alt, &top, testing);
372 appendGreatCirclePoints(lastAz, alt1b, az, alt2b, &bottom, testing);
379 const ArtificialHorizonEntity *constraint = getConstraintAbove(lastAz, lastAlt, thisOne);
382 if (constraint !=
nullptr)
384 if (constraint->ceiling())
return false;
385 double altTemp = constraint->altitudeConstraint(lastAz, &exists);
386 if (exists) alt1b = altTemp;
388 constraint = getConstraintAbove(az, alt, thisOne);
389 if (constraint !=
nullptr)
391 if (constraint->ceiling())
return false;
392 double altTemp = constraint->altitudeConstraint(az, &exists);
393 if (exists) alt2b = altTemp;
395 appendGreatCirclePoints(lastAz, lastAlt, az, alt, &top, testing);
397 appendGreatCirclePoints(lastAz, alt1b, az, alt2b, &bottom, testing);
407 const ArtificialHorizonEntity *constraint = getConstraintBelow(az1, alt1, thisOne);
408 if (constraint !=
nullptr)
410 double altTemp = constraint->altitudeConstraint(az1, &exists);
414 appendGreatCirclePoints(az1, alt1b, az1, alt1, &left, testing);
416 const ArtificialHorizonEntity *constraint2 = getConstraintBelow(az2, alt2, thisOne);
417 if (constraint2 !=
nullptr)
419 double altTemp = constraint2->altitudeConstraint(az2, &exists);
423 appendGreatCirclePoints(az2, alt2, az2, alt2b, &right, testing);
430 const ArtificialHorizonEntity *constraint = getConstraintAbove(az1, alt1, thisOne);
433 if (constraint !=
nullptr)
435 if (!constraint->ceiling())
return false;
436 double altTemp = constraint->altitudeConstraint(az1, &exists);
437 if (exists) alt1b = altTemp;
439 appendGreatCirclePoints(az1, alt1b, az1, alt1, &left, testing);
441 const ArtificialHorizonEntity *constraint2 = getConstraintAbove(az2, alt2, thisOne);
442 if (constraint2 !=
nullptr)
444 if (!constraint2->ceiling())
return false;
445 double altTemp = constraint2->altitudeConstraint(az2, &exists);
446 if (exists) alt2b = altTemp;
448 appendGreatCirclePoints(az2, alt2, az2, alt2b, &right, testing);
453 for (
const auto &p : * (
left.points()))
455 for (
const auto &p : * (top.
points()))
457 for (
const auto &p : * (
right.points()))
459 for (
int i = bottom.
points()->size() - 1; i >= 0; i--)
460 region->append(bottom.
points()->at(i));
470void ArtificialHorizon::drawSampledPolygons(
int entity,
double az1,
double alt1,
double az2,
double alt2,
481 if (computePolygon(entity, az1, alt1, az2, alt2, sampling, ®ion))
483 if (painter !=
nullptr)
485 if (regions !=
nullptr)
496 const ArtificialHorizonEntity &ah = *(horizonList()->
at(entity));
497 const SkyList &points = *(ah.list()->points());
502 for (; start < points.size(); ++start)
509 for (
int i = start + 1; i < points.size(); ++i)
514 const SkyPoint &p1 = *points[start];
517 const double az1 = normalizeDegrees(p1.
az().
Degrees());
518 const double az2 = normalizeDegrees(p2.
az().
Degrees());
520 double minAz, maxAz, minAzAlt, maxAzAlt;
535 const bool wrapAround = !inBetween(
dms((minAz + maxAz) / 2.0),
dms(minAz),
dms(maxAz));
536 constexpr double sampling = 1.0;
543 const double midAlt = gc.altAtAz(0);
545 drawSampledPolygons(entity, maxAz, maxAzAlt, 360, midAlt, sampling, painter, regions);
546 drawSampledPolygons(entity, 0, midAlt, minAz, minAzAlt, sampling, painter, regions);
551 drawSampledPolygons(entity, minAz, minAzAlt, maxAz, maxAzAlt, sampling, painter, regions);
558 for (
int i = 0; i < horizonList()->
size(); i++)
561 drawPolygons(i, painter, regions);
567void sampleLineList(std::shared_ptr<LineList> *list, std::shared_ptr<LineList> *tempPoints)
569 constexpr double sampling = 0.1;
570 const auto points =
list->get()->points();
571 const int size = points->size();
572 (*tempPoints)->points()->clear();
573 for (
int upto = 0; upto < size - 1; ++upto)
575 const auto p1 = points->at(upto);
576 const auto p2 = points->at(upto + 1);
581 if (maxDelta == 0)
continue;
582 int numP = maxDelta / sampling;
583 if (numP == 0) numP = 2;
584 for (
int i = 0; i < numP; ++i)
586 double newAz = 0, newAlt = 0;
587 gc.waypoint(i * 1.0 / numP, &newAz, &newAlt);
592 (*tempPoints)->append(std::shared_ptr<SkyPoint>(newPt));
595 if (upto == (size - 2))
601 (*tempPoints)->append(std::shared_ptr<SkyPoint>(newPt));
606void ArtificialHorizonComponent::draw(
SkyPainter *skyp)
611 bool showPolygons = Options::showGround();
612 if (livePreview.get())
614 if ((livePreview->points() !=
nullptr) && (livePreview->points()->size() > 0))
621 auto tempLineList = std::shared_ptr<LineList>(
new LineList);
622 *tempLineList = *livePreview;
623 sampleLineList(&livePreview, &tempLineList);
626 drawSelectedPoint(livePreview.get(), selectedPreviewPoint, skyp);
628 drawHorizonPoints(livePreview.get(), skyp);
637 horizon.drawPolygons(skyp, ®ions);
641bool ArtificialHorizon::enabled(
int i)
const
643 return m_HorizonList.
at(i)->enabled();
646ArtificialHorizonEntity *ArtificialHorizon::findRegion(
const QString ®ionName)
648 ArtificialHorizonEntity *regionHorizon =
nullptr;
650 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
652 if (horizon->region() == regionName)
654 regionHorizon = horizon;
659 return regionHorizon;
662void ArtificialHorizon::removeRegion(
const QString ®ionName,
bool lineOnly)
664 ArtificialHorizonEntity *regionHorizon = findRegion(regionName);
666 if (regionHorizon ==
nullptr)
670 regionHorizon->clearList();
674 delete (regionHorizon);
676 resetPrecomputeConstraints();
680void ArtificialHorizonComponent::removeRegion(
const QString ®ionName,
bool lineOnly)
682 ArtificialHorizonEntity *regionHorizon = horizon.findRegion(regionName);
683 if (regionHorizon !=
nullptr && regionHorizon->list())
684 removeLine(regionHorizon->list());
685 horizon.removeRegion(regionName, lineOnly);
688void ArtificialHorizon::checkForCeilings()
690 noCeilingConstraints =
true;
691 for (
const auto &r : m_HorizonList)
693 if (r->ceiling() && r->enabled())
695 noCeilingConstraints =
false;
701void ArtificialHorizon::addRegion(
const QString ®ionName,
bool enabled,
const std::shared_ptr<LineList> &list,
704 ArtificialHorizonEntity *horizon =
new ArtificialHorizonEntity;
706 horizon->setRegion(regionName);
707 horizon->setEnabled(enabled);
708 horizon->setCeiling(ceiling);
709 horizon->setList(list);
711 m_HorizonList.append(horizon);
712 resetPrecomputeConstraints();
716void ArtificialHorizonComponent::addRegion(
const QString ®ionName,
bool enabled,
const std::shared_ptr<LineList> &list,
719 horizon.addRegion(regionName, enabled, list, ceiling);
723bool ArtificialHorizon::altitudeConstraintsExist()
const
725 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
727 if (horizon->enabled())
733const ArtificialHorizonEntity *ArtificialHorizon::getConstraintAbove(
double azimuthDegrees,
double altitudeDegrees,
734 const ArtificialHorizonEntity *ignore)
const
736 double closestAbove = 1e6;
737 const ArtificialHorizonEntity *entity =
nullptr;
739 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
741 if (!horizon->enabled())
continue;
742 if (horizon == ignore)
continue;
743 bool constraintExists =
false;
744 double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
746 if (!constraintExists)
continue;
748 double altitudeDiff = constraint - altitudeDegrees;
749 if (altitudeDiff > 0 && constraint < closestAbove)
751 closestAbove = constraint;
760constexpr int PRECOMPUTED_RESOLUTION = 10;
762double ArtificialHorizon::altitudeConstraint(
double azimuthDegrees)
const
764 if (precomputedConstraints.size() != 360 * PRECOMPUTED_RESOLUTION)
765 precomputeConstraints();
766 return precomputedConstraint(azimuthDegrees);
769double ArtificialHorizon::altitudeConstraintInternal(
double azimuthDegrees)
const
771 const ArtificialHorizonEntity *horizonBelow = getConstraintBelow(azimuthDegrees, 90.0,
nullptr);
772 if (horizonBelow ==
nullptr)
773 return UNDEFINED_ALTITUDE;
775 return horizonBelow->altitudeConstraint(azimuthDegrees, &ignore);
780void ArtificialHorizon::precomputeConstraints()
const
782 precomputedConstraints.clear();
783 precomputedConstraints.fill(0, 360 * PRECOMPUTED_RESOLUTION);
784 for (
int i = 0; i < 360 * PRECOMPUTED_RESOLUTION; ++i)
786 const double az = i /
static_cast<double>(PRECOMPUTED_RESOLUTION);
787 precomputedConstraints[i] = altitudeConstraintInternal(az);
791void ArtificialHorizon::resetPrecomputeConstraints()
const
793 precomputedConstraints.clear();
796double ArtificialHorizon::precomputedConstraint(
double azimuth)
const
798 constexpr int maxval = 360 * PRECOMPUTED_RESOLUTION;
799 int index = azimuth * PRECOMPUTED_RESOLUTION + 0.5;
802 if (index < 0 || index >= precomputedConstraints.size())
803 return UNDEFINED_ALTITUDE;
804 return precomputedConstraints[index];
807const ArtificialHorizonEntity *ArtificialHorizon::getConstraintBelow(
double azimuthDegrees,
double altitudeDegrees,
808 const ArtificialHorizonEntity *ignore)
const
810 double closestBelow = -1e6;
811 const ArtificialHorizonEntity *entity =
nullptr;
813 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
815 if (!horizon->enabled())
continue;
816 if (horizon == ignore)
continue;
817 bool constraintExists =
false;
818 double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
820 if (!constraintExists)
continue;
822 double altitudeDiff = constraint - altitudeDegrees;
823 if (altitudeDiff < 0 && constraint > closestBelow)
825 closestBelow = constraint;
832bool ArtificialHorizon::isAltitudeOK(
double azimuthDegrees,
double altitudeDegrees,
QString *reason)
const
834 if (noCeilingConstraints)
836 const double constraint = altitudeConstraint(azimuthDegrees);
837 if (altitudeDegrees >= constraint)
839 if (reason !=
nullptr)
840 *reason =
QString(
"altitude %1 < horizon %2").
arg(altitudeDegrees, 0,
'f', 1).
arg(constraint, 0,
'f', 1);
844 return isVisible(azimuthDegrees, altitudeDegrees, reason);
850bool ArtificialHorizon::isVisible(
double azimuthDegrees,
double altitudeDegrees,
QString *reason)
const
852 const ArtificialHorizonEntity *above = getConstraintAbove(azimuthDegrees, altitudeDegrees);
853 if (above !=
nullptr && !above->ceiling())
855 if (reason !=
nullptr)
858 double constraint = above->altitudeConstraint(azimuthDegrees, &ignoreMe);
859 *reason =
QString(
"altitude %1 < horizon %2").
arg(altitudeDegrees, 0,
'f', 1).
arg(constraint, 0,
'f', 1);
863 const ArtificialHorizonEntity *below = getConstraintBelow(azimuthDegrees, altitudeDegrees);
864 if (below !=
nullptr && below->ceiling())
866 if (reason !=
nullptr)
869 double constraint = below->altitudeConstraint(azimuthDegrees, &ignoreMe);
870 *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.
void setAlt(dms alt)
Sets Alt, the Altitude.
void HorizontalToEquatorial(const dms *LST, const dms *lat)
Determine the (RA, Dec) coordinates of the SkyPoint from its (Altitude, Azimuth) coordinates,...
void setAz(dms az)
Sets Az, the Azimuth.
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)