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
20 ArtificialHorizonEntity::~ArtificialHorizonEntity()
25 QString ArtificialHorizonEntity::region()
const
30 void ArtificialHorizonEntity::setRegion(
const QString &Region)
35 bool ArtificialHorizonEntity::enabled()
const
40 void ArtificialHorizonEntity::setEnabled(
bool Enabled)
45 bool ArtificialHorizonEntity::ceiling()
const
50 void ArtificialHorizonEntity::setCeiling(
bool value)
55 void ArtificialHorizonEntity::setList(
const std::shared_ptr<LineList> &list)
60 std::shared_ptr<LineList> ArtificialHorizonEntity::list()
const
65 void ArtificialHorizonEntity::clearList()
75 bool inBetween(
const dms &angle,
const dms &range1,
const dms &range2)
82 return delta1 <= rangeDelta && delta2 <= rangeDelta;
86 double 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);
139 ArtificialHorizonComponent::ArtificialHorizonComponent(
SkyComposite *parent)
145 ArtificialHorizonComponent::~ArtificialHorizonComponent()
149 ArtificialHorizon::~ArtificialHorizon()
151 qDeleteAll(m_HorizonList);
152 m_HorizonList.
clear();
157 m_HorizonList =
list;
158 resetPrecomputeConstraints();
162 bool ArtificialHorizonComponent::load()
168 foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
169 appendLine(horizon->list());
174 void ArtificialHorizonComponent::save()
178 foreach (ArtificialHorizonEntity *horizon, *horizon.horizonList())
182 bool ArtificialHorizonComponent::selected()
184 return Options::showGround();
187 void ArtificialHorizonComponent::preDraw(
SkyPainter *skyp)
189 QColor color(KStarsData::Instance()->colorScheme()->colorNamed(
"ArtificialHorizonColor"));
199 double 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);
270 void 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());
315 bool 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));
460 void 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);
556 void 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);
582 bool ArtificialHorizon::enabled(
int i)
const
584 return m_HorizonList.
at(i)->enabled();
587 ArtificialHorizonEntity *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;
603 void ArtificialHorizon::removeRegion(
const QString ®ionName,
bool lineOnly)
605 ArtificialHorizonEntity *regionHorizon = findRegion(regionName);
607 if (regionHorizon ==
nullptr)
611 regionHorizon->clearList();
615 delete (regionHorizon);
617 resetPrecomputeConstraints();
621 void 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);
629 void ArtificialHorizon::checkForCeilings()
631 noCeilingConstraints =
true;
632 for (
const auto &r : m_HorizonList)
634 if (r->ceiling() && r->enabled())
636 noCeilingConstraints =
false;
642 void 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();
657 void ArtificialHorizonComponent::addRegion(
const QString ®ionName,
bool enabled,
const std::shared_ptr<LineList> &list,
660 horizon.addRegion(regionName, enabled, list, ceiling);
664 bool ArtificialHorizon::altitudeConstraintsExist()
const
666 foreach (ArtificialHorizonEntity *horizon, m_HorizonList)
668 if (horizon->enabled())
674 const 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;
701 constexpr
int PRECOMPUTED_RESOLUTION = 10;
703 double ArtificialHorizon::altitudeConstraint(
double azimuthDegrees)
const
705 if (precomputedConstraints.
size() != 360 * PRECOMPUTED_RESOLUTION)
706 precomputeConstraints();
707 return precomputedConstraint(azimuthDegrees);
710 double 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);
721 void 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);
732 void ArtificialHorizon::resetPrecomputeConstraints()
const
734 precomputedConstraints.
clear();
737 double 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];
748 const 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;
773 bool 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);
791 bool 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);