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:
parent
3a3ff07883
commit
f5b5828bd0
4 changed files with 202 additions and 80 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue