File indexing completed on 2024-04-14 14:12:12

0001 /*
0002     SPDX-FileCopyrightText: 2021 Hy Murveit <hy@murveit.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 /*
0008  * This file contains unit tests for the ArtificialHorizon class.
0009  */
0010 
0011 #include <QObject>
0012 #include <QTest>
0013 #include <memory>
0014 
0015 #include "artificialhorizoncomponent.h"
0016 #include "linelist.h"
0017 #include "Options.h"
0018 
0019 class TestArtificialHorizon : public QObject
0020 {
0021         Q_OBJECT
0022 
0023     public:
0024         /** @short Constructor */
0025         TestArtificialHorizon();
0026 
0027         /** @short Destructor */
0028         ~TestArtificialHorizon() override = default;
0029 
0030     private slots:
0031         void artificialHorizonTest();
0032         void artificialCeilingTest();
0033 
0034     private:
0035 };
0036 
0037 // This include must go after the class declaration.
0038 #include "testartificialhorizon.moc"
0039 
0040 TestArtificialHorizon::TestArtificialHorizon() : QObject()
0041 {
0042 }
0043 
0044 namespace
0045 {
0046 
0047 // Pass in two lists with azimuth and altitude values to set up an artificial horizon LineList.
0048 std::shared_ptr<LineList> setupHorizonEntities(const QList<double> &az, const QList<double> alt)
0049 {
0050     std::shared_ptr<LineList> list(new LineList());
0051     std::shared_ptr<SkyPoint> p;
0052 
0053     for (int i = 0; i < az.size(); ++i)
0054     {
0055         p.reset(new SkyPoint());
0056         p->setAz(dms(az[i]));
0057         p->setAlt(dms(alt[i]));
0058         list->append(p);
0059     }
0060     return list;
0061 }
0062 
0063 // Checks both that horizon.isVisible(az, alt) == visibility, as well as making sure that the
0064 // given az,alt is contained in one of the polygons (if visibility is false) or not contained
0065 // if visibility is true. Those polygons should be those generated to render the red
0066 // artificial-horizon area on the SkyMap.
0067 bool checkHorizon(const ArtificialHorizon &horizon, double az, double alt,
0068                   bool visibility, QList<LineList> &polygons)
0069 {
0070     bool azAltInPolygon = false;
0071     for (int i = 0; i < polygons.size(); ++i)
0072     {
0073         const SkyList &points = *(polygons[i].points());
0074         if (points.size() < 3)
0075             return false;
0076 
0077         QPolygonF polygon;
0078         for (int j = 0; j < points.size(); ++j)
0079             polygon << QPointF(points[j]->az().Degrees(), points[j]->alt().Degrees());
0080         polygon << QPointF(points[0]->az().Degrees(), points[0]->alt().Degrees());
0081 
0082         if (polygon.containsPoint(QPointF(az, alt), Qt::OddEvenFill))
0083         {
0084             azAltInPolygon = true;
0085             break;
0086         }
0087     }
0088     if (horizon.isVisible(az, alt) != visibility)
0089         qInfo() << QString("isVisible test failed for az %1 alt %2 vis %3").arg(az, 0, 'f', 1).arg(alt, 0, 'f', 1).arg(visibility);
0090     if (azAltInPolygon == visibility)
0091         qInfo() << QString("inPolygon test failed for az %1 alt %2 vis %3\n").arg(az, 0, 'f', 1).arg(alt, 0, 'f',
0092                 1).arg(visibility);
0093     return (horizon.isVisible(az, alt) == visibility) && (azAltInPolygon != visibility);
0094 }
0095 
0096 }  // namespace
0097 
0098 void TestArtificialHorizon::artificialHorizonTest()
0099 {
0100     ArtificialHorizon horizon;
0101 
0102     // This removes a call to KStars::Instance() that's not needed in testing.
0103     horizon.setTesting();
0104 
0105     // Setup a simple 3-point horizon.
0106     QList<double> az1  = {10.0, 20.0, 30.0};
0107     QList<double> alt1 = {20.0, 40.0, 26.0};
0108     auto list1 = setupHorizonEntities(az1, alt1);
0109     horizon.addRegion("R1", true, list1, false);
0110 
0111     QVERIFY(horizon.altitudeConstraintsExist());
0112     QList<LineList> polygons;
0113     horizon.drawPolygons(nullptr, &polygons);
0114 
0115     // Check points above and below the first horizon line.
0116     QVERIFY(checkHorizon(horizon, 20, 41, true, polygons));
0117     QVERIFY(checkHorizon(horizon, 20, 39, false, polygons));
0118     QVERIFY(checkHorizon(horizon, 20.0, 51, true, polygons));
0119     QVERIFY(checkHorizon(horizon, 25.0, 34, true, polygons));
0120     QVERIFY(checkHorizon(horizon, 25.0, 32, false, polygons));
0121     QVERIFY(checkHorizon(horizon, 15.0, 31, true, polygons));
0122     QVERIFY(checkHorizon(horizon, 15.0, 29, false, polygons));
0123     QVERIFY(checkHorizon(horizon, 35.0, -90 + 1, true, polygons));
0124     QVERIFY(checkHorizon(horizon, 5.0, -90 + 1, true, polygons));
0125 
0126     // Try adding and subtracting 360-degrees from the azimuth for
0127     // the same tests. Shouldn't matter.
0128     // Graphics test doesn't make sense for >360-degrees so just test isVisible().
0129     QVERIFY(horizon.isVisible(360.0 + 20.0, 41));
0130     QVERIFY(!horizon.isVisible(360.0 + 20.0, 39));
0131     QVERIFY(horizon.isVisible(-360.0 + 20.0, 41));
0132     QVERIFY(!horizon.isVisible(-360.0 + 20.0, 39));
0133     QVERIFY(horizon.isVisible(360.0 + 25.0, 34));
0134     QVERIFY(!horizon.isVisible(360.0 + 25.0, 32));
0135     QVERIFY(horizon.isVisible(-360.0 + 25.0, 34));
0136     QVERIFY(!horizon.isVisible(-360.0 + 25.0, 32));
0137     QVERIFY(horizon.isVisible(360.0 + 15.0, 31));
0138     QVERIFY(!horizon.isVisible(360.0 + 15.0, 29));
0139     QVERIFY(horizon.isVisible(-360.0 + 15.0, 31));
0140     QVERIFY(!horizon.isVisible(-360.0 + 15.0, 29));
0141     QVERIFY(horizon.isVisible(360.0 + 35.0, -90 + 1));
0142     QVERIFY(horizon.isVisible(360.0 + 5.0, -90 + 1));
0143     QVERIFY(horizon.isVisible(-360.0 + 35.0, -90 + 1));
0144     QVERIFY(horizon.isVisible(-360.0 + 5.0, -90 + 1));
0145 
0146     // Disable that horizon. All tests above shoud be visible.
0147     horizon.findRegion("R1")->setEnabled(false);
0148     polygons.clear();
0149     horizon.drawPolygons(nullptr, &polygons);
0150 
0151     QVERIFY(!horizon.altitudeConstraintsExist());
0152     QVERIFY(checkHorizon(horizon, 20, 41, true, polygons));
0153     QVERIFY(checkHorizon(horizon, 20, 39, true, polygons));
0154     QVERIFY(checkHorizon(horizon, 25.0, 34, true, polygons));
0155     QVERIFY(checkHorizon(horizon, 25.0, 32, true, polygons));
0156     QVERIFY(checkHorizon(horizon, 15.0, 31, true, polygons));
0157     QVERIFY(checkHorizon(horizon, 15.0, 29, true, polygons));
0158     QVERIFY(checkHorizon(horizon, 35.0, -90 + 1, true, polygons));
0159     QVERIFY(checkHorizon(horizon, 5.0, -90 + 1, true, polygons));
0160 
0161     // Re-enable it
0162     horizon.findRegion("R1")->setEnabled(true);
0163     QVERIFY(horizon.altitudeConstraintsExist());
0164 
0165     // Now add a ceiling above the first horizon line.
0166     // Above the ceiling should not be visible
0167     QList<double> az2  = {14.0, 20.0, 24.0};
0168     QList<double> alt2 = {40.0, 46.0, 50.0};
0169     auto list2 = setupHorizonEntities(az2, alt2);
0170     horizon.addRegion("R2", true, list2, true);
0171     polygons.clear();
0172     horizon.drawPolygons(nullptr, &polygons);
0173 
0174     QVERIFY(checkHorizon(horizon, 18, 51, false, polygons));
0175     QVERIFY(checkHorizon(horizon, 18, 60, false, polygons));
0176     QVERIFY(checkHorizon(horizon, 22, 51, false, polygons));
0177     QVERIFY(checkHorizon(horizon, 22, 60, false, polygons));
0178     QVERIFY(checkHorizon(horizon, 18, 42, true, polygons));
0179     // but it doesn't affect things to its side
0180     QVERIFY(checkHorizon(horizon, 13, 51, true, polygons));
0181     QVERIFY(checkHorizon(horizon, 13, 49, true, polygons));
0182     QVERIFY(checkHorizon(horizon, 25, 51, true, polygons));
0183     QVERIFY(checkHorizon(horizon, 25, 49, true, polygons));
0184 
0185     // Disable the ceiling. Those points become visible again.
0186     horizon.findRegion("R2")->setEnabled(false);
0187     polygons.clear();
0188     horizon.drawPolygons(nullptr, &polygons);
0189 
0190     QVERIFY(checkHorizon(horizon, 18, 51, true, polygons));
0191     QVERIFY(checkHorizon(horizon, 18, 60, true, polygons));
0192     QVERIFY(checkHorizon(horizon, 22, 51, true, polygons));
0193     QVERIFY(checkHorizon(horizon, 22, 60, true, polygons));
0194 
0195     // Re-enable the ceiling.
0196     horizon.findRegion("R2")->setEnabled(true);
0197     polygons.clear();
0198     horizon.drawPolygons(nullptr, &polygons);
0199     QVERIFY(checkHorizon(horizon, 18, 51, false, polygons));
0200     QVERIFY(checkHorizon(horizon, 18, 60, false, polygons));
0201     QVERIFY(checkHorizon(horizon, 22.5, 51, false, polygons));
0202     QVERIFY(checkHorizon(horizon, 22.5, 60, false, polygons));
0203 
0204     // Add another horizon line above the ceiling again makes that visible.
0205     QList<double> az3  = {10.0, 19.0};
0206     QList<double> alt3 = {50.5, 50.2};
0207     auto list3 = setupHorizonEntities(az3, alt3);
0208     horizon.addRegion("R3", true, list3, false);
0209     polygons.clear();
0210     horizon.drawPolygons(nullptr, &polygons);
0211 
0212     QVERIFY(checkHorizon(horizon, 18, 51, true, polygons));
0213     QVERIFY(checkHorizon(horizon, 18, 60, true, polygons));
0214     // but it shouldn't affect other az values
0215     QVERIFY(checkHorizon(horizon, 22.5, 51, false, polygons));
0216     QVERIFY(checkHorizon(horizon, 22.5, 60, false, polygons));
0217 
0218 }
0219 
0220 void TestArtificialHorizon::artificialCeilingTest()
0221 {
0222     ArtificialHorizon horizon;
0223 
0224     // This removes a call to KStars::Instance() that's not needed in testing.
0225     horizon.setTesting();
0226 
0227     QList<double> az1  = {259.0, 260.0, 299.0, 300.0, 330.0,   0.0,  30.0,  60.0,  61.0,  90.0, 120.0, 150.0, 180.0, 210.0, 240.0, 259.99};
0228     QList<double> alt1 = { 90.0,  26.0,  26.0,  26.0,  26.0,  26.0,  26.0,  26.0,  90.0,  90.0,  90.0,  90.0,  90.0,  90.0,  90.0,  90.0};
0229     auto list1 = setupHorizonEntities(az1, alt1);
0230     horizon.addRegion("horizon", true, list1, false);
0231 
0232     QList<double> az2  = {260.0, 300.0, 330.0,  0.0,  30.0,  60.0};
0233     QList<double> alt2 = { 66.0,  66.0,  66.0, 66.0,  66.0,  66.0};
0234     auto list2 = setupHorizonEntities(az2, alt2);
0235     horizon.addRegion("ceiling", true, list2, true);
0236 
0237     QVERIFY(horizon.altitudeConstraintsExist());
0238     QList<LineList> polygons;
0239     horizon.drawPolygons(nullptr, &polygons);
0240 
0241     // Check points above and below the first horizon line.
0242     QVERIFY(checkHorizon(horizon, 54, 50, true, polygons));
0243     QVERIFY(checkHorizon(horizon, 54, 60, true, polygons));
0244     QVERIFY(checkHorizon(horizon, 49, 68, false, polygons));
0245     QVERIFY(checkHorizon(horizon, 21, 77, false, polygons));
0246     QVERIFY(checkHorizon(horizon, 321, 74, false, polygons));
0247     QVERIFY(checkHorizon(horizon, 310, 68, false, polygons));
0248     QVERIFY(checkHorizon(horizon, 305, 60, true, polygons));
0249     QVERIFY(checkHorizon(horizon, 306, 48, true, polygons));
0250     QVERIFY(checkHorizon(horizon, 328, 14, false, polygons));
0251     QVERIFY(checkHorizon(horizon, 351, 3, false, polygons));
0252 }
0253 
0254 QTEST_GUILESS_MAIN(TestArtificialHorizon)