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()
186 return Options::showGround();
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 if (livePreview.get())
613 if ((livePreview->points() !=
nullptr) && (livePreview->points()->size() > 0))
620 auto tempLineList = std::shared_ptr<LineList>(
new LineList);
621 *tempLineList = *livePreview;
622 sampleLineList(&livePreview, &tempLineList);
625 drawSelectedPoint(livePreview.get(), selectedPreviewPoint, skyp);
627 drawHorizonPoints(livePreview.get(), skyp);
634 horizon.drawPolygons(skyp, ®ions);
637bool ArtificialHorizon::enabled(
int i)
const
639 return m_HorizonList.
at(i)->enabled();
642ArtificialHorizonEntity *ArtificialHorizon::findRegion(
const QString ®ionName)
644 ArtificialHorizonEntity *regionHorizon =
nullptr;
646 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
648 if (horizon->region() == regionName)
650 regionHorizon = horizon;
655 return regionHorizon;
658void ArtificialHorizon::removeRegion(
const QString ®ionName,
bool lineOnly)
660 ArtificialHorizonEntity *regionHorizon = findRegion(regionName);
662 if (regionHorizon ==
nullptr)
666 regionHorizon->clearList();
670 delete (regionHorizon);
672 resetPrecomputeConstraints();
676void ArtificialHorizonComponent::removeRegion(
const QString ®ionName,
bool lineOnly)
678 ArtificialHorizonEntity *regionHorizon = horizon.findRegion(regionName);
679 if (regionHorizon !=
nullptr && regionHorizon->list())
680 removeLine(regionHorizon->list());
681 horizon.removeRegion(regionName, lineOnly);
684void ArtificialHorizon::checkForCeilings()
686 noCeilingConstraints =
true;
687 for (
const auto &r : m_HorizonList)
689 if (r->ceiling() && r->enabled())
691 noCeilingConstraints =
false;
697void ArtificialHorizon::addRegion(
const QString ®ionName,
bool enabled,
const std::shared_ptr<LineList> &list,
700 ArtificialHorizonEntity *horizon =
new ArtificialHorizonEntity;
702 horizon->setRegion(regionName);
703 horizon->setEnabled(enabled);
704 horizon->setCeiling(ceiling);
705 horizon->setList(list);
707 m_HorizonList.append(horizon);
708 resetPrecomputeConstraints();
712void ArtificialHorizonComponent::addRegion(
const QString ®ionName,
bool enabled,
const std::shared_ptr<LineList> &list,
715 horizon.addRegion(regionName, enabled, list, ceiling);
719bool ArtificialHorizon::altitudeConstraintsExist()
const
721 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
723 if (horizon->enabled())
729const ArtificialHorizonEntity *ArtificialHorizon::getConstraintAbove(
double azimuthDegrees,
double altitudeDegrees,
730 const ArtificialHorizonEntity *ignore)
const
732 double closestAbove = 1e6;
733 const ArtificialHorizonEntity *entity =
nullptr;
735 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
737 if (!horizon->enabled())
continue;
738 if (horizon == ignore)
continue;
739 bool constraintExists =
false;
740 double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
742 if (!constraintExists)
continue;
744 double altitudeDiff = constraint - altitudeDegrees;
745 if (altitudeDiff > 0 && constraint < closestAbove)
747 closestAbove = constraint;
756constexpr int PRECOMPUTED_RESOLUTION = 10;
758double ArtificialHorizon::altitudeConstraint(
double azimuthDegrees)
const
760 if (precomputedConstraints.size() != 360 * PRECOMPUTED_RESOLUTION)
761 precomputeConstraints();
762 return precomputedConstraint(azimuthDegrees);
765double ArtificialHorizon::altitudeConstraintInternal(
double azimuthDegrees)
const
767 const ArtificialHorizonEntity *horizonBelow = getConstraintBelow(azimuthDegrees, 90.0,
nullptr);
768 if (horizonBelow ==
nullptr)
769 return UNDEFINED_ALTITUDE;
771 return horizonBelow->altitudeConstraint(azimuthDegrees, &ignore);
776void ArtificialHorizon::precomputeConstraints()
const
778 precomputedConstraints.clear();
779 precomputedConstraints.fill(0, 360 * PRECOMPUTED_RESOLUTION);
780 for (
int i = 0; i < 360 * PRECOMPUTED_RESOLUTION; ++i)
782 const double az = i /
static_cast<double>(PRECOMPUTED_RESOLUTION);
783 precomputedConstraints[i] = altitudeConstraintInternal(az);
787void ArtificialHorizon::resetPrecomputeConstraints()
const
789 precomputedConstraints.clear();
792double ArtificialHorizon::precomputedConstraint(
double azimuth)
const
794 constexpr int maxval = 360 * PRECOMPUTED_RESOLUTION;
795 int index = azimuth * PRECOMPUTED_RESOLUTION + 0.5;
798 if (index < 0 || index >= precomputedConstraints.size())
799 return UNDEFINED_ALTITUDE;
800 return precomputedConstraints[index];
803const ArtificialHorizonEntity *ArtificialHorizon::getConstraintBelow(
double azimuthDegrees,
double altitudeDegrees,
804 const ArtificialHorizonEntity *ignore)
const
806 double closestBelow = -1e6;
807 const ArtificialHorizonEntity *entity =
nullptr;
809 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
811 if (!horizon->enabled())
continue;
812 if (horizon == ignore)
continue;
813 bool constraintExists =
false;
814 double constraint = horizon->altitudeConstraint(azimuthDegrees, &constraintExists);
816 if (!constraintExists)
continue;
818 double altitudeDiff = constraint - altitudeDegrees;
819 if (altitudeDiff < 0 && constraint > closestBelow)
821 closestBelow = constraint;
828bool ArtificialHorizon::isAltitudeOK(
double azimuthDegrees,
double altitudeDegrees,
QString *reason)
const
830 if (noCeilingConstraints)
832 const double constraint = altitudeConstraint(azimuthDegrees);
833 if (altitudeDegrees >= constraint)
835 if (reason !=
nullptr)
836 *reason =
QString(
"altitude %1 < horizon %2").
arg(altitudeDegrees, 0,
'f', 1).
arg(constraint, 0,
'f', 1);
840 return isVisible(azimuthDegrees, altitudeDegrees, reason);
846bool ArtificialHorizon::isVisible(
double azimuthDegrees,
double altitudeDegrees,
QString *reason)
const
848 const ArtificialHorizonEntity *above = getConstraintAbove(azimuthDegrees, altitudeDegrees);
849 if (above !=
nullptr && !above->ceiling())
851 if (reason !=
nullptr)
854 double constraint = above->altitudeConstraint(azimuthDegrees, &ignoreMe);
855 *reason =
QString(
"altitude %1 < horizon %2").
arg(altitudeDegrees, 0,
'f', 1).
arg(constraint, 0,
'f', 1);
859 const ArtificialHorizonEntity *below = getConstraintBelow(azimuthDegrees, altitudeDegrees);
860 if (below !=
nullptr && below->ceiling())
862 if (reason !=
nullptr)
865 double constraint = below->altitudeConstraint(azimuthDegrees, &ignoreMe);
866 *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)