diff --git a/src/Airports/airport.cxx b/src/Airports/airport.cxx index 5bb3cb7c0..e5adcea1f 100644 --- a/src/Airports/airport.cxx +++ b/src/Airports/airport.cxx @@ -452,6 +452,11 @@ bool FGAirport::TypeRunwayFilter::pass(FGPositioned* pos) const return true; } +void FGAirport::clearAirportsCache() +{ + airportCache.clear(); +} + //------------------------------------------------------------------------------ FGAirportRef FGAirport::findByIdent(const std::string& aIdent) { diff --git a/src/Airports/airport.hxx b/src/Airports/airport.hxx index e870da289..ebb347ada 100644 --- a/src/Airports/airport.hxx +++ b/src/Airports/airport.hxx @@ -304,6 +304,8 @@ class FGAirport : public FGPositioned flightgear::CommStationList commStationsOfType(FGPositioned::Type aTy) const; flightgear::CommStationList commStations() const; + + static void clearAirportsCache(); private: static flightgear::AirportCache airportCache; diff --git a/src/Navaids/NavDataCache.cxx b/src/Navaids/NavDataCache.cxx index e42b55bab..5c68ec7bf 100644 --- a/src/Navaids/NavDataCache.cxx +++ b/src/Navaids/NavDataCache.cxx @@ -1264,12 +1264,17 @@ NavDataCache::NavDataCache() NavDataCache::~NavDataCache() { assert(static_instance == this); - static_instance = NULL; + static_instance = nullptr; d.reset(); + +// ensure we wip the airports cache too, or we'll get out +// of sync during tests + FGAirport::clearAirportsCache(); } NavDataCache* NavDataCache::createInstance() { + assert(static_instance == nullptr); static_instance = new NavDataCache; return static_instance; } diff --git a/test_suite/FGTestApi/NavDataCache.cxx b/test_suite/FGTestApi/NavDataCache.cxx index d29161c64..a7a14e2e6 100644 --- a/test_suite/FGTestApi/NavDataCache.cxx +++ b/test_suite/FGTestApi/NavDataCache.cxx @@ -9,6 +9,9 @@ namespace setUp { void initNavDataCache() { + if (flightgear::NavDataCache::instance()) + return; + flightgear::NavDataCache* cache = flightgear::NavDataCache::createInstance(); if (cache->isRebuildRequired()) { std::cerr << "Navcache rebuild for testing" << std::flush; diff --git a/test_suite/FGTestApi/TestPilot.cxx b/test_suite/FGTestApi/TestPilot.cxx index ce2042ccc..fbaf7dc71 100644 --- a/test_suite/FGTestApi/TestPilot.cxx +++ b/test_suite/FGTestApi/TestPilot.cxx @@ -141,9 +141,24 @@ void TestPilot::updateValues(double dt) } // set how aggressively we try to correct our course - const double courseCorrectionFactor = 8.0; + double courseCorrectionFactor = 8.0; + double crossTrack = _gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"); + + double correction = courseCorrectionFactor * deviationDeg; - SG_CLAMP_RANGE(correction, -45.0, 45.0); + double maxOffset = 45; + + // don't make really sharp turns if we're getting close + if (fabs(crossTrack) < 1.0) { + maxOffset *= 0.5; + } + + // *really* don't make sharp turns if we're getting really close :) + if (fabs(crossTrack) < 0.2) { + maxOffset *= 0.5; + } + + SG_CLAMP_RANGE(correction, -maxOffset, maxOffset); _targetCourseDeg += correction; SG_NORMALIZE_RANGE(_targetCourseDeg, 0.0, 360.0); diff --git a/test_suite/FGTestApi/testGlobals.cxx b/test_suite/FGTestApi/testGlobals.cxx index 183aa2e9e..4286cd28e 100644 --- a/test_suite/FGTestApi/testGlobals.cxx +++ b/test_suite/FGTestApi/testGlobals.cxx @@ -214,6 +214,11 @@ void populateFPWithNasal(flightgear::FlightPlanRef f, global_kmlStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl; } + void rawLogCoordinate(const SGGeod& pos) + { + global_kmlStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl; + } + void endCurrentLineString() { global_lineStringOpen = false; @@ -308,6 +313,10 @@ void writeFlightPlanToKML(flightgear::FlightPlanRef fp) for (int i=0; i<fp->numLegs(); ++i) { SGGeodVec legPath = rpath.pathForIndex(i); fullPath.insert(fullPath.end(), legPath.begin(), legPath.end()); + + auto wp = fp->legAtIndex(i)->waypoint(); + SGGeod legWPPosition = wp->position(); + writePointToKML("WP " + wp->ident(), legWPPosition); } writeGeodsToKML("FlightPlan", fullPath); @@ -328,6 +337,18 @@ void writeGeodsToKML(const std::string &label, const flightgear::SGGeodVec& geod endCurrentLineString(); } +void writePointToKML(const std::string& ident, const SGGeod& pos) +{ + global_kmlStream << "<Placemark>\n"; + global_kmlStream << "<name>" << ident << "</name>\n"; + global_kmlStream << "<Point>\n"; + global_kmlStream << "<coordinates>\n"; + rawLogCoordinate(pos); + global_kmlStream << "</coordinates>\n"; + global_kmlStream << "</Point>\n"; + global_kmlStream << "</Placemark>\n"; +} + bool executeNasal(const std::string& code) { auto nasal = globals->get_subsystem<FGNasalSys>(); diff --git a/test_suite/FGTestApi/testGlobals.hxx b/test_suite/FGTestApi/testGlobals.hxx index 051b4fdbf..995753461 100644 --- a/test_suite/FGTestApi/testGlobals.hxx +++ b/test_suite/FGTestApi/testGlobals.hxx @@ -52,6 +52,7 @@ bool runForTimeWithCheck(double t, RunCheck check); void writeFlightPlanToKML(flightgear::FlightPlanRef fp); void writeGeodsToKML(const std::string &label, const flightgear::SGGeodVec& geods); +void writePointToKML(const std::string& ident, const SGGeod& pos); bool executeNasal(const std::string& code); diff --git a/test_suite/unit_tests/Instrumentation/test_gps.cxx b/test_suite/unit_tests/Instrumentation/test_gps.cxx index d83f5e828..d4c60cd86 100644 --- a/test_suite/unit_tests/Instrumentation/test_gps.cxx +++ b/test_suite/unit_tests/Instrumentation/test_gps.cxx @@ -766,7 +766,7 @@ void GPSTests::testOverflightSequencing() FGTestApi::setUp::populateFPWithoutNasal(fp, "NZCH", "02", "NZAA", "05L", "ALADA NS WB WN MAMOD KAPTI OH"); -// FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); // takes the place of the Nasal delegates auto testDelegate = new TestFPDelegate; @@ -1217,7 +1217,7 @@ void GPSTests::testTurnAnticipation() void GPSTests::testRadialIntercept() { - //FGTestApi::setUp::logPositionToKML("gps_radial_intercept"); + // FGTestApi::setUp::logPositionToKML("gps_radial_intercept"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; @@ -1225,6 +1225,9 @@ void GPSTests::testRadialIntercept() FGTestApi::setUp::populateFPWithoutNasal(fp, "LFKC", "36", "LIRF", "25", "BUNAX BEBEV AJO"); + // set BUNAX as overflight + fp->legAtIndex(1)->waypoint()->setFlag(WPT_OVERFLIGHT); + // insert KC502 manually fp->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(8.78333, 42.566), "KC502", fp), 1); @@ -1233,7 +1236,7 @@ void GPSTests::testRadialIntercept() auto intc = new RadialIntercept(fp, "INTC", pos, 230, 5); fp->insertWayptAtIndex(intc, 3); - // FGTestApi::writeFlightPlanToKML(fp); + //FGTestApi::writeFlightPlanToKML(fp); // position slightly before BUNAX SGGeod initPos = fp->pointAlongRoute(2, -3.0); @@ -1273,3 +1276,69 @@ void GPSTests::testRadialIntercept() FGTestApi::runForTime(30.0); } + + +void GPSTests::testDMEIntercept() +{ + //FGTestApi::setUp::logPositionToKML("gps_dme_intercept"); + + auto rm = globals->get_subsystem<FGRouteMgr>(); + auto fp = new FlightPlan; + rm->setFlightPlan(fp); + + // manually consturct something close to the publichsed approach transition for EGPH ILS 06 + + FGTestApi::setUp::populateFPWithoutNasal(fp, "EGLL", "27R", "EGPH", "06", "SHAPP TLA"); + + // DMEIntercept(RouteBase* aOwner, const std::string& aIdent, const SGGeod& aPos, + // double aCourseDeg, double aDistanceNm); + auto dmeArc = new DMEIntercept(fp, "TLA-11", SGGeod::fromDeg(-3.352833, 55.499145), 323.0, 11.0); + fp->insertWayptAtIndex(dmeArc, 3); + + // now a normal WP + fp->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(-3.636708, 55.689981), "D3230", fp), 4); + + auto dmeArc2 = new DMEIntercept(fp, "TLA-20", SGGeod::fromDeg(-3.352833, 55.499145), 323.0, 20.0); + fp->insertWayptAtIndex(dmeArc2, 5); + + // and another normal WP + fp->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(-3.751056, 55.766122), "D323U", fp), 6); + + // and another one + fp->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(-3.690845, 55.841378), "CI06", fp), 7); + + // FGTestApi::writeFlightPlanToKML(fp); + + // position slightly before TLA + SGGeod initPos = fp->pointAlongRoute(2, -2.0); + + // takes the place of the Nasal delegates + auto testDelegate = new TestFPDelegate; + testDelegate->thePlan = fp; + CPPUNIT_ASSERT(rm->activate()); + fp->addDelegate(testDelegate); + auto gps = setupStandardGPS(); + + FGTestApi::setPositionAndStabilise(initPos); + + auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); + gpsNode->setBoolValue("config/delegate-sequencing", true); + gpsNode->setStringValue("command", "leg"); + + fp->setCurrentIndex(2); + + CPPUNIT_ASSERT_EQUAL(string{"TLA"}, string{gpsNode->getStringValue("wp/wp[1]/ID")}); + // CPPUNIT_ASSERT_DOUBLES_EQUAL(312, gpsNode->getDoubleValue("wp/leg-true-course-deg"), 1.0); + + auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); + pilot->resetAtPosition(initPos); + pilot->setCourseTrue(fp->legAtIndex(2)->courseDeg()); + pilot->setSpeedKts(220); + pilot->flyGPSCourse(gps); + + bool ok = FGTestApi::runForTimeWithCheck(1200.0, [fp]() { + return fp->currentIndex() == 8; + }); + CPPUNIT_ASSERT(ok); + +} diff --git a/test_suite/unit_tests/Instrumentation/test_gps.hxx b/test_suite/unit_tests/Instrumentation/test_gps.hxx index 623bab6d9..782b695d2 100644 --- a/test_suite/unit_tests/Instrumentation/test_gps.hxx +++ b/test_suite/unit_tests/Instrumentation/test_gps.hxx @@ -53,6 +53,7 @@ class GPSTests : public CppUnit::TestFixture CPPUNIT_TEST(testDirectToLegOnFlightplanAndResumeBuiltin); CPPUNIT_TEST(testBuiltinRevertToOBSAtEnd); CPPUNIT_TEST(testRadialIntercept); + CPPUNIT_TEST(testDMEIntercept); CPPUNIT_TEST_SUITE_END(); @@ -86,6 +87,7 @@ public: void testDirectToLegOnFlightplanAndResumeBuiltin(); void testBuiltinRevertToOBSAtEnd(); void testRadialIntercept(); + void testDMEIntercept(); }; #endif // _FG_GPS_UNIT_TESTS_HXX diff --git a/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx b/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx index d4d7b6bed..d007413ca 100644 --- a/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx +++ b/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx @@ -462,3 +462,60 @@ void RNAVProcedureTests::testEGPH_TLA6C() CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, fp->legAtIndex(5)->waypoint()->ident()); CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")}); } + +void RNAVProcedureTests::testLFKC_AJO1R() +{ + auto lfkc = FGAirport::findByIdent("LFKC"); + auto sid = lfkc->findSIDWithIdent("AJO1R"); + // procedures not loaded, abandon test + if (!sid) + return; + + // FGTestApi::setUp::logPositionToKML("procedure_LFKC_AJO1R"); + + auto rm = globals->get_subsystem<FGRouteMgr>(); + auto fp = new FlightPlan; + + auto testDelegate = new TestFPDelegate; + testDelegate->thePlan = fp; + fp->addDelegate(testDelegate); + + rm->setFlightPlan(fp); + FGTestApi::setUp::populateFPWithNasal(fp, "LFKC", "36", "EGLL", "27R", ""); + + fp->setSID(sid); + + CPPUNIT_ASSERT_EQUAL(std::string{"BEBEV"}, fp->legAtIndex(4)->waypoint()->ident()); + CPPUNIT_ASSERT_EQUAL(std::string{"AJO"}, fp->legAtIndex(5)->waypoint()->ident()); + double d = fp->legAtIndex(5)->distanceAlongRoute(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(72, d, 1.0); // ensure the route didn't blow up to 0,0 + + FGRunwayRef departureRunway = fp->departureRunway(); + // FGTestApi::writeFlightPlanToKML(fp); + + CPPUNIT_ASSERT(rm->activate()); + + setupStandardGPS(); + + FGTestApi::setPositionAndStabilise(departureRunway->threshold()); + m_gpsNode->setStringValue("command", "leg"); + + auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); + pilot->resetAtPosition(globals->get_aircraft_position()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(departureRunway->headingDeg(), m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5); + pilot->setCourseTrue(m_gpsNode->getDoubleValue("wp/leg-true-course-deg")); + pilot->setSpeedKts(220); + pilot->flyGPSCourse(m_gps); + + FGTestApi::runForTime(20.0); + // check we're somewhere along the runway, on the centerline + // and still on waypoint zero + + bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () { + if (fp->currentIndex() == 1) { + return true; + } + return false; + }); + CPPUNIT_ASSERT(ok); +} diff --git a/test_suite/unit_tests/Instrumentation/test_rnav_procedures.hxx b/test_suite/unit_tests/Instrumentation/test_rnav_procedures.hxx index f03385e7a..384adb75d 100644 --- a/test_suite/unit_tests/Instrumentation/test_rnav_procedures.hxx +++ b/test_suite/unit_tests/Instrumentation/test_rnav_procedures.hxx @@ -41,7 +41,7 @@ class RNAVProcedureTests : public CppUnit::TestFixture CPPUNIT_TEST(testEGPH_TLA6C); CPPUNIT_TEST(testHeadingToAlt); CPPUNIT_TEST(testUglyHeadingToAlt); - + CPPUNIT_TEST(testLFKC_AJO1R); CPPUNIT_TEST_SUITE_END(); void setPositionAndStabilise(const SGGeod& g); @@ -63,6 +63,7 @@ public: void testEGPH_TLA6C(); void testHeadingToAlt(); void testUglyHeadingToAlt(); + void testLFKC_AJO1R(); private: GPS* m_gps = nullptr; SGPropertyNode_ptr m_gpsNode; diff --git a/test_suite/unit_tests/Navaids/test_flightplan.cxx b/test_suite/unit_tests/Navaids/test_flightplan.cxx index 35abafb32..a0a249f37 100644 --- a/test_suite/unit_tests/Navaids/test_flightplan.cxx +++ b/test_suite/unit_tests/Navaids/test_flightplan.cxx @@ -495,6 +495,8 @@ void FlightplanTests::testRadialIntercept() // replicate AJO1R departure FlightPlanRef f = makeTestFP("LFKC", "36", "LIRF", "25", "BUNAX BEBEV AJO"); + // set BUNAX as overflight + f->legAtIndex(1)->waypoint()->setFlag(WPT_OVERFLIGHT); f->insertWayptAtIndex(new BasicWaypt(SGGeod::fromDeg(8.78333, 42.566), "KC502", f), 1); SGGeod pos = SGGeod::fromDeg(8.445556,42.216944);