1
0
Fork 0

AIFlightPlan: add test for XML parsing

This meant some slight refactoring to expose some easier APIs for
testing, but the normal methods should be unaffected.
This commit is contained in:
James Turner 2020-09-05 10:11:44 +01:00
parent 3a3ff07883
commit f5b5828bd0
4 changed files with 202 additions and 80 deletions

View file

@ -22,13 +22,14 @@
#include <iterator>
#include <simgear/misc/sg_path.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Main/globals.hxx>
@ -223,82 +224,107 @@ void FGAIFlightPlan::createWaypoints(FGAIAircraft *ac,
bool FGAIFlightPlan::parseProperties(const std::string& filename)
{
SGPath path( globals->get_fg_root() );
path.append( "/AI/FlightPlans/" + filename );
if (!path.exists()) {
return false;
}
SGPropertyNode root;
try {
readProperties(path, &root);
} catch (const sg_exception &e) {
SG_LOG(SG_AI, SG_ALERT, "Error reading AI flight plan: " << path
<< "message:" << e.getFormattedMessage());
return false;
}
SGPropertyNode * node = root.getNode("flightplan");
for (int i = 0; i < node->nChildren(); i++) {
FGAIWaypoint* wpt = new FGAIWaypoint;
SGPropertyNode * wpt_node = node->getChild(i);
bool gear, flaps;
// Calculate some default values if they are not set explicitly in the flightplan
if (wpt_node->getDoubleValue("ktas", 0) < 1.0f ) {
// Not moving so assume shut down.
wpt->setPowerDownLights();
flaps = false;
gear = true;
} else if (wpt_node->getDoubleValue("alt", 0) > 10000.0f ) {
// Cruise flight;
wpt->setCruiseLights();
flaps = false;
gear = false;
} else if (wpt_node->getBoolValue("on-ground", false)) {
// On ground
wpt->setGroundLights();
flaps = true;
gear = true;
} else if (wpt_node->getDoubleValue("alt", 0) < 3000.0f ) {
// In the air below 3000 ft, so flaps and gear down.
wpt->setApproachLights();
flaps = true;
gear = true;
} else {
// In the air 3000-10000 ft
wpt->setApproachLights();
flaps = false;
gear = false;
SGPath fp = globals->findDataPath("AI/FlightPlans/" + filename);
if (!fp.exists()) {
SG_LOG(SG_AI, SG_WARN, "Couldn't find AI flightplan '" << filename << "'");
return false;
}
wpt->setName (wpt_node->getStringValue("name", "END" ));
wpt->setLatitude (wpt_node->getDoubleValue("lat", 0 ));
wpt->setLongitude (wpt_node->getDoubleValue("lon", 0 ));
wpt->setAltitude (wpt_node->getDoubleValue("alt", 0 ));
wpt->setSpeed (wpt_node->getDoubleValue("ktas", 0 ));
wpt->setCrossat (wpt_node->getDoubleValue("crossat", -10000 ));
wpt->setGear_down (wpt_node->getBoolValue("gear-down", gear ));
wpt->setFlaps (wpt_node->getBoolValue("flaps-down", flaps ) ? 1.0 : 0.0);
wpt->setSpoilers (wpt_node->getBoolValue("spoilers", false ) ? 1.0 : 0.0);
wpt->setSpeedBrakes (wpt_node->getBoolValue("speedbrakes", false ) ? 1.0 : 0.0);
wpt->setOn_ground (wpt_node->getBoolValue("on-ground", false ));
wpt->setTime_sec (wpt_node->getDoubleValue("time-sec", 0 ));
wpt->setTime (wpt_node->getStringValue("time", "" ));
wpt->setFinished ((wpt->getName() == "END"));
pushBackWaypoint( wpt );
}
if( getLastWaypoint()->getName().compare("END") != 0 ) {
SG_LOG(SG_AI, SG_ALERT, "FGAIFlightPlan::Flightplan missing END node" );
return false;
}
wpt_iterator = waypoints.begin();
return true;
return readFlightplan(fp);
}
bool FGAIFlightPlan::readFlightplan(const SGPath& file)
{
sg_ifstream f(file);
return readFlightplan(f, file);
}
bool FGAIFlightPlan::readFlightplan(std::istream& stream, const sg_location& loc)
{
SGPropertyNode root;
try {
readProperties(stream, &root);
} catch (const sg_exception& e) {
SG_LOG(SG_AI, SG_ALERT, "Error reading AI flight plan: " << loc.asString() << " message:" << e.getFormattedMessage());
return false;
}
SGPropertyNode* node = root.getNode("flightplan");
if (!node) {
SG_LOG(SG_AI, SG_ALERT, "Error reading AI flight plan: " << loc.asString() << ": no <flightplan> root element");
return false;
}
for (int i = 0; i < node->nChildren(); i++) {
FGAIWaypoint* wpt = new FGAIWaypoint;
SGPropertyNode* wpt_node = node->getChild(i);
bool gear, flaps;
// Calculate some default values if they are not set explicitly in the flightplan
if (wpt_node->getDoubleValue("ktas", 0) < 1.0f) {
// Not moving so assume shut down.
wpt->setPowerDownLights();
flaps = false;
gear = true;
} else if (wpt_node->getDoubleValue("alt", 0) > 10000.0f) {
// Cruise flight;
wpt->setCruiseLights();
flaps = false;
gear = false;
} else if (wpt_node->getBoolValue("on-ground", false)) {
// On ground
wpt->setGroundLights();
flaps = true;
gear = true;
} else if (wpt_node->getDoubleValue("alt", 0) < 3000.0f) {
// In the air below 3000 ft, so flaps and gear down.
wpt->setApproachLights();
flaps = true;
gear = true;
} else {
// In the air 3000-10000 ft
wpt->setApproachLights();
flaps = false;
gear = false;
}
wpt->setName(wpt_node->getStringValue("name", "END"));
wpt->setLatitude(wpt_node->getDoubleValue("lat", 0));
wpt->setLongitude(wpt_node->getDoubleValue("lon", 0));
wpt->setAltitude(wpt_node->getDoubleValue("alt", 0));
wpt->setSpeed(wpt_node->getDoubleValue("ktas", 0));
wpt->setCrossat(wpt_node->getDoubleValue("crossat", -10000));
wpt->setGear_down(wpt_node->getBoolValue("gear-down", gear));
wpt->setFlaps(wpt_node->getBoolValue("flaps-down", flaps) ? 1.0 : 0.0);
wpt->setSpoilers(wpt_node->getBoolValue("spoilers", false) ? 1.0 : 0.0);
wpt->setSpeedBrakes(wpt_node->getBoolValue("speedbrakes", false) ? 1.0 : 0.0);
wpt->setOn_ground(wpt_node->getBoolValue("on-ground", false));
wpt->setTime_sec(wpt_node->getDoubleValue("time-sec", 0));
wpt->setTime(wpt_node->getStringValue("time", ""));
wpt->setFinished((wpt->getName() == "END"));
pushBackWaypoint(wpt);
}
auto lastWp = getLastWaypoint();
if (!lastWp || lastWp->getName().compare("END") != 0) {
SG_LOG(SG_AI, SG_ALERT, "FGAIFlightPlan::Flightplan (" + loc.asString() + ") missing END node");
return false;
}
wpt_iterator = waypoints.begin();
return true;
}
FGAIWaypoint* FGAIFlightPlan::getLastWaypoint()
{
if (waypoints.empty())
return nullptr;
return waypoints.back();
};
FGAIWaypoint* FGAIFlightPlan::getPreviousWaypoint( void ) const
{
if (wpt_iterator == waypoints.begin()) {

View file

@ -22,11 +22,15 @@
#include <vector>
#include <string>
#include <Airports/dynamics.hxx>
#include <Navaids/positioned.hxx>
#include <simgear/compiler.h>
#include <simgear/math/SGMath.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <Navaids/positioned.hxx>
#include <Airports/dynamics.hxx>
#include <simgear/structure/exception.hxx>
// forward decls
class SGPath;
class FGAIWaypoint {
private:
@ -145,6 +149,14 @@ public:
const std::string& airline);
~FGAIFlightPlan();
/**
@brief read a flight-plan from a file.
All current contents of the flight-plan are replaxced, and the current waypoint is reset to the beginning
*/
bool readFlightplan(const SGPath& file);
bool readFlightplan(std::istream& stream, const sg_location& loc = sg_location{});
FGAIWaypoint* getPreviousWaypoint( void ) const;
FGAIWaypoint* getCurrentWaypoint( void ) const;
FGAIWaypoint* getNextWaypoint( void ) const;
@ -199,7 +211,7 @@ public:
void setSID(FGAIFlightPlan* fp) { sid = fp;};
FGAIFlightPlan* getSID() { return sid; };
FGAIWaypoint *getWayPoint(int i) { return waypoints[i]; };
FGAIWaypoint *getLastWaypoint() { return waypoints.back(); };
FGAIWaypoint* getLastWaypoint();
void shortenToFirst(unsigned int number, std::string name);

View file

@ -34,6 +34,8 @@
#include <Airports/airport.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
/////////////////////////////////////////////////////////////////////////////
@ -105,14 +107,22 @@ void AIManagerTests::testAIFlightPlan()
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
FGPositioned::TypeFilter ty(FGPositioned::VOR);
auto cache = flightgear::NavDataCache::instance();
auto shannonVOR = cache->findClosestWithIdent("SHA", SGGeod::fromDeg(-8, 52), &ty);
CPPUNIT_ASSERT_EQUAL(string{"SHANNON VOR-DME"}, shannonVOR->name());
auto wp1 = new FGAIWaypoint;
wp1->setPos(shannonVOR->geod());
wp1->setName("testWp_0");
wp1->setOn_ground(true);
wp1->setGear_down(true);
wp1->setSpeed(100);
auto wp2 = new FGAIWaypoint;
wp2->setName("upImTheAir");
const auto g1 = SGGeodesy::direct(shannonVOR->geod(), 10.0, SG_NM_TO_METER * 5.0);
wp2->setPos(g1);
wp2->setName("upInTheAir");
wp2->setOn_ground(false);
wp2->setGear_down(true);
wp2->setSpeed(150);
@ -126,12 +136,83 @@ void AIManagerTests::testAIFlightPlan()
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
CPPUNIT_ASSERT_DOUBLES_EQUAL(10.0, aiFP->getBearing(wp1, wp2), 0.1);
time_t startTime = 1498;
aiFP->setTime(startTime);
CPPUNIT_ASSERT(!aiFP->isActive(1400));
CPPUNIT_ASSERT(aiFP->isActive(1500));
aiFP->IncrementWaypoint(false);
CPPUNIT_ASSERT_EQUAL(2, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(static_cast<FGAIWaypoint*>(nullptr), aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
auto wp3 = new FGAIWaypoint;
auto diganWpt = cache->findClosestWithIdent("DIGAN", shannonVOR->geod(), nullptr);
wp3->setPos(diganWpt->geod());
wp3->setName("overDIGAN");
wp3->setOn_ground(false);
wp3->setGear_down(false);
wp3->setSpeed(180);
// check that adding a waypoint doesn't mess up the iterators or
// current position
aiFP->addWaypoint(wp3);
CPPUNIT_ASSERT_EQUAL(3, aiFP->getNrOfWayPoints());
CPPUNIT_ASSERT_EQUAL(wp1, aiFP->getPreviousWaypoint());
CPPUNIT_ASSERT_EQUAL(wp2, aiFP->getCurrentWaypoint());
CPPUNIT_ASSERT_EQUAL(wp3, aiFP->getNextWaypoint());
CPPUNIT_ASSERT_EQUAL(0, aiFP->getLeg());
}
void AIManagerTests::testAIFlightPlanLoadXML()
{
const auto xml = R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<flightplan>
<wp>
<name>onGroundWP</name>
<lat></lat>
<lon></lon>
<ktas>10</ktas>
<on-ground>1</on-ground>
</wp>
<wp>
<name>someWP</name>
<lat></lat>
<lon></lon>
<ktas>200</ktas>
<alt>8000</alt>
</wp>
<wp>
<name>END</name>
</wp>
</flightplan>
</PropertyList>
)";
std::istringstream is(xml);
std::unique_ptr<FGAIFlightPlan> aiFP(new FGAIFlightPlan);
bool ok = aiFP->readFlightplan(is, sg_location("In-memory test_ai_fp.xml"));
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(false, aiFP->getCurrentWaypoint()->getInAir());
CPPUNIT_ASSERT_EQUAL(true, aiFP->getCurrentWaypoint()->getGear_down());
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, aiFP->getCurrentWaypoint()->getFlaps(), 0.1);
auto wp2 = aiFP->getNextWaypoint();
CPPUNIT_ASSERT_EQUAL(true, wp2->getInAir());
CPPUNIT_ASSERT_EQUAL(false, wp2->getGear_down());
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, wp2->getFlaps(), 0.1);
}
// test for AIFLightPlan leg / legEnd pieces.
void AIManagerTests::testAircraftWaypoints()
{
auto aim = globals->get_subsystem<FGAIManager>();

View file

@ -36,6 +36,8 @@ class AIManagerTests : public CppUnit::TestFixture
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testAIFlightPlan);
CPPUNIT_TEST(testAircraftWaypoints);
CPPUNIT_TEST(testAIFlightPlanLoadXML);
CPPUNIT_TEST_SUITE_END();
@ -50,4 +52,5 @@ public:
void testBasic();
void testAIFlightPlan();
void testAircraftWaypoints();
void testAIFlightPlanLoadXML();
};