16d1c47664
Route-manager was explicitly deleting its FP, which was incorrect. Switch to correct use of SGSharedPtr to refer to flight plans. Thanks to Roland Haeder for catching this.
1257 lines
30 KiB
C++
1257 lines
30 KiB
C++
// FlightPlan.cxx - flight plan object
|
|
|
|
// Written by James Turner, started 2012.
|
|
//
|
|
// Copyright (C) 2012 Curtis L. Olson
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "FlightPlan.hxx"
|
|
|
|
// std
|
|
#include <map>
|
|
#include <fstream>
|
|
#include <cassert>
|
|
|
|
// Boost
|
|
#include <boost/algorithm/string/case_conv.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/foreach.hpp>
|
|
|
|
// SimGear
|
|
#include <simgear/structure/exception.hxx>
|
|
#include <simgear/misc/sg_path.hxx>
|
|
#include <simgear/magvar/magvar.hxx>
|
|
#include <simgear/timing/sg_time.hxx>
|
|
#include <simgear/misc/sgstream.hxx>
|
|
#include <simgear/misc/strutils.hxx>
|
|
#include <simgear/props/props_io.hxx>
|
|
|
|
// FlightGear
|
|
#include <Main/globals.hxx>
|
|
#include "Main/fg_props.hxx"
|
|
#include <Navaids/procedure.hxx>
|
|
#include <Navaids/waypoint.hxx>
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
using std::endl;
|
|
using std::fstream;
|
|
|
|
namespace flightgear {
|
|
|
|
typedef std::vector<FlightPlan::DelegateFactory*> FPDelegateFactoryVec;
|
|
static FPDelegateFactoryVec static_delegateFactories;
|
|
|
|
FlightPlan::FlightPlan() :
|
|
_delegateLock(0),
|
|
_currentIndex(-1),
|
|
_departureRunway(NULL),
|
|
_destinationRunway(NULL),
|
|
_sid(NULL),
|
|
_star(NULL),
|
|
_approach(NULL),
|
|
_delegate(NULL)
|
|
{
|
|
_departureChanged = _arrivalChanged = _waypointsChanged = _currentWaypointChanged = false;
|
|
|
|
BOOST_FOREACH(DelegateFactory* factory, static_delegateFactories) {
|
|
Delegate* d = factory->createFlightPlanDelegate(this);
|
|
if (d) { // factory might not always create a delegate
|
|
d->_deleteWithPlan = true;
|
|
addDelegate(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
FlightPlan::~FlightPlan()
|
|
{
|
|
// delete all delegates which we own.
|
|
Delegate* d = _delegate;
|
|
while (d) {
|
|
Delegate* cur = d;
|
|
d = d->_inner;
|
|
if (cur->_deleteWithPlan) {
|
|
delete cur;
|
|
}
|
|
}
|
|
|
|
// delete legs
|
|
BOOST_FOREACH(Leg* l, _legs) {
|
|
delete l;
|
|
}
|
|
}
|
|
|
|
FlightPlan* FlightPlan::clone(const string& newIdent) const
|
|
{
|
|
FlightPlan* c = new FlightPlan();
|
|
c->_ident = newIdent.empty() ? _ident : newIdent;
|
|
c->lockDelegate();
|
|
|
|
// copy destination / departure data.
|
|
c->setDeparture(_departure);
|
|
c->setDeparture(_departureRunway);
|
|
|
|
if (_approach) {
|
|
c->setApproach(_approach);
|
|
} else if (_destinationRunway) {
|
|
c->setDestination(_destinationRunway);
|
|
} else if (_destination) {
|
|
c->setDestination(_destination);
|
|
}
|
|
|
|
c->setSTAR(_star);
|
|
c->setSID(_sid);
|
|
|
|
// copy legs
|
|
c->_waypointsChanged = true;
|
|
for (int l=0; l < numLegs(); ++l) {
|
|
c->_legs.push_back(_legs[l]->cloneFor(c));
|
|
}
|
|
c->unlockDelegate();
|
|
return c;
|
|
}
|
|
|
|
void FlightPlan::setIdent(const string& s)
|
|
{
|
|
_ident = s;
|
|
}
|
|
|
|
string FlightPlan::ident() const
|
|
{
|
|
return _ident;
|
|
}
|
|
|
|
FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
|
|
{
|
|
if (!aWpt) {
|
|
return NULL;
|
|
}
|
|
|
|
WayptVec wps;
|
|
wps.push_back(aWpt);
|
|
|
|
int index = aIndex;
|
|
if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
|
|
index = _legs.size();
|
|
}
|
|
|
|
insertWayptsAtIndex(wps, index);
|
|
return legAtIndex(index);
|
|
}
|
|
|
|
void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
|
|
{
|
|
if (wps.empty()) {
|
|
return;
|
|
}
|
|
|
|
int index = aIndex;
|
|
if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
|
|
index = _legs.size();
|
|
}
|
|
|
|
LegVec::iterator it = _legs.begin();
|
|
it += index;
|
|
|
|
int endIndex = index + wps.size() - 1;
|
|
if (_currentIndex >= endIndex) {
|
|
_currentIndex += wps.size();
|
|
}
|
|
|
|
LegVec newLegs;
|
|
BOOST_FOREACH(WayptRef wp, wps) {
|
|
newLegs.push_back(new Leg(this, wp));
|
|
}
|
|
|
|
lockDelegate();
|
|
_waypointsChanged = true;
|
|
_legs.insert(it, newLegs.begin(), newLegs.end());
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::deleteIndex(int aIndex)
|
|
{
|
|
int index = aIndex;
|
|
if (aIndex < 0) { // negative indices count the the end
|
|
index = _legs.size() + index;
|
|
}
|
|
|
|
if ((index < 0) || (index >= numLegs())) {
|
|
SG_LOG(SG_NAVAID, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_waypointsChanged = true;
|
|
|
|
LegVec::iterator it = _legs.begin();
|
|
it += index;
|
|
Leg* l = *it;
|
|
_legs.erase(it);
|
|
delete l;
|
|
|
|
if (_currentIndex == index) {
|
|
// current waypoint was removed
|
|
_currentWaypointChanged = true;
|
|
} else if (_currentIndex > index) {
|
|
--_currentIndex; // shift current index down if necessary
|
|
}
|
|
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::clear()
|
|
{
|
|
lockDelegate();
|
|
_waypointsChanged = true;
|
|
_currentWaypointChanged = true;
|
|
_arrivalChanged = true;
|
|
_departureChanged = true;
|
|
|
|
_currentIndex = -1;
|
|
BOOST_FOREACH(Leg* l, _legs) {
|
|
delete l;
|
|
}
|
|
_legs.clear();
|
|
|
|
if (_delegate) {
|
|
_delegate->runCleared();
|
|
}
|
|
unlockDelegate();
|
|
}
|
|
|
|
class RemoveWithFlag
|
|
{
|
|
public:
|
|
RemoveWithFlag(WayptFlag f) : flag(f), delCount(0) { }
|
|
|
|
int numDeleted() const { return delCount; }
|
|
|
|
bool operator()(FlightPlan::Leg* leg) const
|
|
{
|
|
if (leg->waypoint()->flag(flag)) {
|
|
delete leg;
|
|
++delCount;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
private:
|
|
WayptFlag flag;
|
|
mutable int delCount;
|
|
};
|
|
|
|
int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
|
|
{
|
|
int count = 0;
|
|
// first pass, fix up currentIndex
|
|
for (int i=0; i<_currentIndex; ++i) {
|
|
Leg* l = _legs[i];
|
|
if (l->waypoint()->flag(flag)) {
|
|
++count;
|
|
}
|
|
}
|
|
|
|
// test if the current leg will be removed
|
|
bool currentIsBeingCleared = false;
|
|
if (_currentIndex >= 0) {
|
|
currentIsBeingCleared = _legs[_currentIndex]->waypoint()->flag(flag);
|
|
}
|
|
|
|
_currentIndex -= count;
|
|
|
|
// now delete and remove
|
|
RemoveWithFlag rf(flag);
|
|
LegVec::iterator it = std::remove_if(_legs.begin(), _legs.end(), rf);
|
|
if (it == _legs.end()) {
|
|
return 0; // nothing was cleared, don't fire the delegate
|
|
}
|
|
|
|
lockDelegate();
|
|
_waypointsChanged = true;
|
|
if ((count > 0) || currentIsBeingCleared) {
|
|
_currentWaypointChanged = true;
|
|
}
|
|
|
|
_legs.erase(it, _legs.end());
|
|
|
|
if (_legs.empty()) { // maybe all legs were deleted
|
|
if (_delegate) {
|
|
_delegate->runCleared();
|
|
}
|
|
}
|
|
|
|
unlockDelegate();
|
|
return rf.numDeleted();
|
|
}
|
|
|
|
void FlightPlan::setCurrentIndex(int index)
|
|
{
|
|
if ((index < -1) || (index >= numLegs())) {
|
|
throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
|
|
}
|
|
|
|
if (index == _currentIndex) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_currentIndex = index;
|
|
_currentWaypointChanged = true;
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::finish()
|
|
{
|
|
if (_currentIndex == -1) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_currentIndex = -1;
|
|
_currentWaypointChanged = true;
|
|
|
|
if (_delegate) {
|
|
_delegate->runFinished();
|
|
}
|
|
|
|
unlockDelegate();
|
|
}
|
|
|
|
int FlightPlan::findWayptIndex(const SGGeod& aPos) const
|
|
{
|
|
for (int i=0; i<numLegs(); ++i) {
|
|
if (_legs[i]->waypoint()->matches(aPos)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
FlightPlan::Leg* FlightPlan::currentLeg() const
|
|
{
|
|
if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
|
|
return NULL;
|
|
return legAtIndex(_currentIndex);
|
|
}
|
|
|
|
FlightPlan::Leg* FlightPlan::previousLeg() const
|
|
{
|
|
if (_currentIndex <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
return legAtIndex(_currentIndex - 1);
|
|
}
|
|
|
|
FlightPlan::Leg* FlightPlan::nextLeg() const
|
|
{
|
|
if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
|
|
return NULL;
|
|
}
|
|
|
|
return legAtIndex(_currentIndex + 1);
|
|
}
|
|
|
|
FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
|
|
{
|
|
if ((index < 0) || (index >= numLegs())) {
|
|
throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
|
|
}
|
|
|
|
return _legs[index];
|
|
}
|
|
|
|
int FlightPlan::findLegIndex(const Leg *l) const
|
|
{
|
|
for (unsigned int i=0; i<_legs.size(); ++i) {
|
|
if (_legs[i] == l) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void FlightPlan::setDeparture(FGAirport* apt)
|
|
{
|
|
if (apt == _departure) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_departureChanged = true;
|
|
_departure = apt;
|
|
_departureRunway = NULL;
|
|
setSID((SID*)NULL);
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::setDeparture(FGRunway* rwy)
|
|
{
|
|
if (_departureRunway == rwy) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_departureChanged = true;
|
|
|
|
_departureRunway = rwy;
|
|
if (rwy->airport() != _departure) {
|
|
_departure = rwy->airport();
|
|
setSID((SID*)NULL);
|
|
}
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::setSID(SID* sid, const std::string& transition)
|
|
{
|
|
if (sid == _sid) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_departureChanged = true;
|
|
_sid = sid;
|
|
_sidTransition = transition;
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::setSID(Transition* trans)
|
|
{
|
|
if (!trans) {
|
|
setSID((SID*) NULL);
|
|
return;
|
|
}
|
|
|
|
if (trans->parent()->type() != PROCEDURE_SID)
|
|
throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
|
|
|
|
setSID((SID*) trans->parent(), trans->ident());
|
|
}
|
|
|
|
Transition* FlightPlan::sidTransition() const
|
|
{
|
|
if (!_sid || _sidTransition.empty()) {
|
|
return NULL;
|
|
}
|
|
|
|
return _sid->findTransitionByName(_sidTransition);
|
|
}
|
|
|
|
void FlightPlan::setDestination(FGAirport* apt)
|
|
{
|
|
if (apt == _destination) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_arrivalChanged = true;
|
|
_destination = apt;
|
|
_destinationRunway = NULL;
|
|
setSTAR((STAR*)NULL);
|
|
setApproach(NULL);
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::setDestination(FGRunway* rwy)
|
|
{
|
|
if (_destinationRunway == rwy) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_arrivalChanged = true;
|
|
_destinationRunway = rwy;
|
|
if (_destination != rwy->airport()) {
|
|
_destination = rwy->airport();
|
|
setSTAR((STAR*)NULL);
|
|
}
|
|
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::setSTAR(STAR* star, const std::string& transition)
|
|
{
|
|
if (_star == star) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_arrivalChanged = true;
|
|
_star = star;
|
|
_starTransition = transition;
|
|
unlockDelegate();
|
|
}
|
|
|
|
void FlightPlan::setSTAR(Transition* trans)
|
|
{
|
|
if (!trans) {
|
|
setSTAR((STAR*) NULL);
|
|
return;
|
|
}
|
|
|
|
if (trans->parent()->type() != PROCEDURE_STAR)
|
|
throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
|
|
|
|
setSTAR((STAR*) trans->parent(), trans->ident());
|
|
}
|
|
|
|
Transition* FlightPlan::starTransition() const
|
|
{
|
|
if (!_star || _starTransition.empty()) {
|
|
return NULL;
|
|
}
|
|
|
|
return _star->findTransitionByName(_starTransition);
|
|
}
|
|
|
|
void FlightPlan::setApproach(flightgear::Approach *app)
|
|
{
|
|
if (_approach == app) {
|
|
return;
|
|
}
|
|
|
|
lockDelegate();
|
|
_arrivalChanged = true;
|
|
_approach = app;
|
|
if (app) {
|
|
// keep runway + airport in sync
|
|
if (_destinationRunway != _approach->runway()) {
|
|
_destinationRunway = _approach->runway();
|
|
}
|
|
|
|
if (_destination != _destinationRunway->airport()) {
|
|
_destination = _destinationRunway->airport();
|
|
}
|
|
}
|
|
unlockDelegate();
|
|
}
|
|
|
|
bool FlightPlan::save(const SGPath& path)
|
|
{
|
|
SG_LOG(SG_NAVAID, SG_INFO, "Saving route to " << path.str());
|
|
try {
|
|
SGPropertyNode_ptr d(new SGPropertyNode);
|
|
d->setIntValue("version", 2);
|
|
|
|
if (_departure) {
|
|
d->setStringValue("departure/airport", _departure->ident());
|
|
if (_sid) {
|
|
d->setStringValue("departure/sid", _sid->ident());
|
|
}
|
|
|
|
if (_departureRunway) {
|
|
d->setStringValue("departure/runway", _departureRunway->ident());
|
|
}
|
|
}
|
|
|
|
if (_destination) {
|
|
d->setStringValue("destination/airport", _destination->ident());
|
|
if (_star) {
|
|
d->setStringValue("destination/star", _star->ident());
|
|
}
|
|
|
|
if (_approach) {
|
|
d->setStringValue("destination/approach", _approach->ident());
|
|
}
|
|
|
|
//d->setStringValue("destination/transition", destination->getStringValue("transition"));
|
|
|
|
if (_destinationRunway) {
|
|
d->setStringValue("destination/runway", _destinationRunway->ident());
|
|
}
|
|
}
|
|
|
|
// route nodes
|
|
SGPropertyNode* routeNode = d->getChild("route", 0, true);
|
|
for (unsigned int i=0; i<_legs.size(); ++i) {
|
|
Waypt* wpt = _legs[i]->waypoint();
|
|
wpt->saveAsNode(routeNode->getChild("wp", i, true));
|
|
} // of waypoint iteration
|
|
writeProperties(path.str(), d, true /* write-all */);
|
|
return true;
|
|
} catch (sg_exception& e) {
|
|
SG_LOG(SG_NAVAID, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FlightPlan::load(const SGPath& path)
|
|
{
|
|
if (!path.exists())
|
|
{
|
|
SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << path.str()
|
|
<< "'. The file does not exist.");
|
|
return false;
|
|
}
|
|
|
|
SGPropertyNode_ptr routeData(new SGPropertyNode);
|
|
SG_LOG(SG_NAVAID, SG_INFO, "going to read flight-plan from:" << path.str());
|
|
|
|
bool Status = false;
|
|
lockDelegate();
|
|
try {
|
|
readProperties(path.str(), routeData);
|
|
} catch (sg_exception& ) {
|
|
// if XML parsing fails, the file might be simple textual list of waypoints
|
|
Status = loadPlainTextRoute(path);
|
|
routeData = 0;
|
|
_waypointsChanged = true;
|
|
}
|
|
|
|
if (routeData.valid())
|
|
{
|
|
try {
|
|
int version = routeData->getIntValue("version", 1);
|
|
if (version == 1) {
|
|
loadVersion1XMLRoute(routeData);
|
|
} else if (version == 2) {
|
|
loadVersion2XMLRoute(routeData);
|
|
} else {
|
|
throw sg_io_exception("unsupported XML route version");
|
|
}
|
|
Status = true;
|
|
} catch (sg_exception& e) {
|
|
SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
|
|
<< "'. " << e.getMessage());
|
|
Status = false;
|
|
}
|
|
}
|
|
|
|
unlockDelegate();
|
|
return Status;
|
|
}
|
|
|
|
void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
|
|
{
|
|
// departure nodes
|
|
SGPropertyNode* dep = routeData->getChild("departure");
|
|
if (dep) {
|
|
string depIdent = dep->getStringValue("airport");
|
|
setDeparture((FGAirport*) fgFindAirportID(depIdent));
|
|
if (_departure) {
|
|
string rwy(dep->getStringValue("runway"));
|
|
if (_departure->hasRunwayWithIdent(rwy)) {
|
|
setDeparture(_departure->getRunwayByIdent(rwy));
|
|
}
|
|
|
|
if (dep->hasChild("sid")) {
|
|
setSID(_departure->findSIDWithIdent(dep->getStringValue("sid")));
|
|
}
|
|
// departure->setStringValue("transition", dep->getStringValue("transition"));
|
|
}
|
|
}
|
|
|
|
// destination
|
|
SGPropertyNode* dst = routeData->getChild("destination");
|
|
if (dst) {
|
|
setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
|
|
if (_destination) {
|
|
string rwy(dst->getStringValue("runway"));
|
|
if (_destination->hasRunwayWithIdent(rwy)) {
|
|
setDestination(_destination->getRunwayByIdent(rwy));
|
|
}
|
|
|
|
if (dst->hasChild("star")) {
|
|
setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star")));
|
|
}
|
|
|
|
if (dst->hasChild("approach")) {
|
|
setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
|
|
}
|
|
}
|
|
|
|
// destination->setStringValue("transition", dst->getStringValue("transition"));
|
|
}
|
|
|
|
// alternate
|
|
SGPropertyNode* alt = routeData->getChild("alternate");
|
|
if (alt) {
|
|
//alternate->setStringValue(alt->getStringValue("airport"));
|
|
}
|
|
|
|
// cruise
|
|
SGPropertyNode* crs = routeData->getChild("cruise");
|
|
if (crs) {
|
|
// cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
|
|
// cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
|
|
// cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
|
|
} // of cruise data loading
|
|
|
|
}
|
|
|
|
void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
|
|
{
|
|
loadXMLRouteHeader(routeData);
|
|
|
|
// route nodes
|
|
_legs.clear();
|
|
SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
|
|
for (int i=0; i<routeNode->nChildren(); ++i) {
|
|
SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
|
|
Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
|
|
_legs.push_back(l);
|
|
} // of route iteration
|
|
_waypointsChanged = true;
|
|
}
|
|
|
|
void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
|
|
{
|
|
loadXMLRouteHeader(routeData);
|
|
|
|
// _legs nodes
|
|
_legs.clear();
|
|
SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
|
|
for (int i=0; i<routeNode->nChildren(); ++i) {
|
|
SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
|
|
Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
|
|
_legs.push_back(l);
|
|
} // of route iteration
|
|
_waypointsChanged = true;
|
|
}
|
|
|
|
WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
|
|
{
|
|
SGGeod lastPos;
|
|
if (!_legs.empty()) {
|
|
lastPos = _legs.back()->waypoint()->position();
|
|
} else if (_departure) {
|
|
lastPos = _departure->geod();
|
|
}
|
|
|
|
WayptRef w;
|
|
string ident(aWP->getStringValue("ident"));
|
|
if (aWP->hasChild("longitude-deg")) {
|
|
// explicit longitude/latitude
|
|
w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
|
|
aWP->getDoubleValue("latitude-deg")), ident, NULL);
|
|
|
|
} else {
|
|
string nid = aWP->getStringValue("navid", ident.c_str());
|
|
FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
|
|
SGGeod pos;
|
|
|
|
if (p) {
|
|
pos = p->geod();
|
|
} else {
|
|
SG_LOG(SG_GENERAL, SG_WARN, "unknown navaid in flightplan:" << nid);
|
|
pos = SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
|
|
aWP->getDoubleValue("latitude-deg"));
|
|
}
|
|
|
|
if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
|
|
double radialDeg = aWP->getDoubleValue("offset-radial");
|
|
// convert magnetic radial to a true radial!
|
|
radialDeg += magvarDegAt(pos);
|
|
double offsetNm = aWP->getDoubleValue("offset-nm");
|
|
double az2;
|
|
SGGeodesy::direct(pos, radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
|
|
}
|
|
|
|
w = new BasicWaypt(pos, ident, NULL);
|
|
}
|
|
|
|
double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
|
|
if (altFt > -9990.0) {
|
|
w->setAltitude(altFt, RESTRICT_AT);
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
bool FlightPlan::loadPlainTextRoute(const SGPath& path)
|
|
{
|
|
try {
|
|
sg_gzifstream in(path.str().c_str());
|
|
if (!in.is_open()) {
|
|
throw sg_io_exception("Cannot open file for reading.");
|
|
}
|
|
|
|
_legs.clear();
|
|
while (!in.eof()) {
|
|
string line;
|
|
getline(in, line, '\n');
|
|
// trim CR from end of line, if found
|
|
if (line[line.size() - 1] == '\r') {
|
|
line.erase(line.size() - 1, 1);
|
|
}
|
|
|
|
line = simgear::strutils::strip(line);
|
|
if (line.empty() || (line[0] == '#')) {
|
|
continue; // ignore empty/comment lines
|
|
}
|
|
|
|
WayptRef w = waypointFromString(line);
|
|
if (!w) {
|
|
throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
|
|
}
|
|
|
|
_legs.push_back(new Leg(this, w));
|
|
} // of line iteration
|
|
} catch (sg_exception& e) {
|
|
SG_LOG(SG_NAVAID, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
|
|
_legs.clear();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
double FlightPlan::magvarDegAt(const SGGeod& pos) const
|
|
{
|
|
double jd = globals->get_time_params()->getJD();
|
|
return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
|
|
}
|
|
|
|
WayptRef FlightPlan::waypointFromString(const string& tgt )
|
|
{
|
|
string target(boost::to_upper_copy(tgt));
|
|
WayptRef wpt;
|
|
|
|
// extract altitude
|
|
double altFt = 0.0;
|
|
RouteRestriction altSetting = RESTRICT_NONE;
|
|
|
|
size_t pos = target.find( '@' );
|
|
if ( pos != string::npos ) {
|
|
altFt = atof( target.c_str() + pos + 1 );
|
|
target = target.substr( 0, pos );
|
|
if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
|
|
altFt *= SG_METER_TO_FEET;
|
|
altSetting = RESTRICT_AT;
|
|
}
|
|
|
|
// check for lon,lat
|
|
pos = target.find( ',' );
|
|
if ( pos != string::npos ) {
|
|
double lon = atof( target.substr(0, pos).c_str());
|
|
double lat = atof( target.c_str() + pos + 1);
|
|
char buf[32];
|
|
char ew = (lon < 0.0) ? 'W' : 'E';
|
|
char ns = (lat < 0.0) ? 'S' : 'N';
|
|
snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
|
|
|
|
wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
|
|
if (altSetting != RESTRICT_NONE) {
|
|
wpt->setAltitude(altFt, altSetting);
|
|
}
|
|
return wpt;
|
|
}
|
|
|
|
SGGeod basePosition;
|
|
if (_legs.empty()) {
|
|
// route is empty, use current position
|
|
basePosition = globals->get_aircraft_position();
|
|
} else {
|
|
basePosition = _legs.back()->waypoint()->position();
|
|
}
|
|
|
|
string_list pieces(simgear::strutils::split(target, "/"));
|
|
FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
|
|
if (!p) {
|
|
SG_LOG( SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
|
|
return NULL;
|
|
}
|
|
|
|
double magvar = magvarDegAt(basePosition);
|
|
|
|
if (pieces.size() == 1) {
|
|
wpt = new NavaidWaypoint(p, NULL);
|
|
} else if (pieces.size() == 3) {
|
|
// navaid/radial/distance-nm notation
|
|
double radial = atof(pieces[1].c_str()),
|
|
distanceNm = atof(pieces[2].c_str());
|
|
radial += magvar;
|
|
wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
|
|
} else if (pieces.size() == 2) {
|
|
FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
|
|
if (!apt) {
|
|
SG_LOG(SG_NAVAID, SG_INFO, "Waypoint is not an airport:" << pieces.front());
|
|
return NULL;
|
|
}
|
|
|
|
if (!apt->hasRunwayWithIdent(pieces[1])) {
|
|
SG_LOG(SG_NAVAID, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
|
|
return NULL;
|
|
}
|
|
|
|
FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
|
|
wpt = new NavaidWaypoint(runway, NULL);
|
|
} else if (pieces.size() == 4) {
|
|
// navid/radial/navid/radial notation
|
|
FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
|
|
if (!p2) {
|
|
SG_LOG( SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
|
|
return NULL;
|
|
}
|
|
|
|
double r1 = atof(pieces[1].c_str()),
|
|
r2 = atof(pieces[3].c_str());
|
|
r1 += magvar;
|
|
r2 += magvar;
|
|
|
|
SGGeod intersection;
|
|
bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
|
|
if (!ok) {
|
|
SG_LOG(SG_NAVAID, SG_INFO, "no valid intersection for:" << target);
|
|
return NULL;
|
|
}
|
|
|
|
std::string name = p->ident() + "-" + p2->ident();
|
|
wpt = new BasicWaypt(intersection, name, NULL);
|
|
}
|
|
|
|
if (!wpt) {
|
|
SG_LOG(SG_NAVAID, SG_INFO, "Unable to parse waypoint:" << target);
|
|
return NULL;
|
|
}
|
|
|
|
if (altSetting != RESTRICT_NONE) {
|
|
wpt->setAltitude(altFt, altSetting);
|
|
}
|
|
return wpt;
|
|
}
|
|
|
|
FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
|
|
_parent(owner),
|
|
_speedRestrict(RESTRICT_NONE),
|
|
_altRestrict(RESTRICT_NONE),
|
|
_waypt(wpt)
|
|
{
|
|
if (!wpt.valid()) {
|
|
throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
|
|
}
|
|
_speed = _altitudeFt = 0;
|
|
}
|
|
|
|
FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
|
|
{
|
|
Leg* c = new Leg(owner, _waypt);
|
|
// clone local data
|
|
c->_speed = _speed;
|
|
c->_speedRestrict = _speedRestrict;
|
|
c->_altitudeFt = _altitudeFt;
|
|
c->_altRestrict = _altRestrict;
|
|
|
|
return c;
|
|
}
|
|
|
|
FlightPlan::Leg* FlightPlan::Leg::nextLeg() const
|
|
{
|
|
return _parent->legAtIndex(index() + 1);
|
|
}
|
|
|
|
unsigned int FlightPlan::Leg::index() const
|
|
{
|
|
return _parent->findLegIndex(this);
|
|
}
|
|
|
|
int FlightPlan::Leg::altitudeFt() const
|
|
{
|
|
if (_altRestrict != RESTRICT_NONE) {
|
|
return _altitudeFt;
|
|
}
|
|
|
|
return _waypt->altitudeFt();
|
|
}
|
|
|
|
int FlightPlan::Leg::speed() const
|
|
{
|
|
if (_speedRestrict != RESTRICT_NONE) {
|
|
return _speed;
|
|
}
|
|
|
|
return _waypt->speed();
|
|
}
|
|
|
|
int FlightPlan::Leg::speedKts() const
|
|
{
|
|
return speed();
|
|
}
|
|
|
|
double FlightPlan::Leg::speedMach() const
|
|
{
|
|
if (!isMachRestrict(_speedRestrict)) {
|
|
return 0.0;
|
|
}
|
|
|
|
return -(_speed / 100.0);
|
|
}
|
|
|
|
RouteRestriction FlightPlan::Leg::altitudeRestriction() const
|
|
{
|
|
if (_altRestrict != RESTRICT_NONE) {
|
|
return _altRestrict;
|
|
}
|
|
|
|
return _waypt->altitudeRestriction();
|
|
}
|
|
|
|
RouteRestriction FlightPlan::Leg::speedRestriction() const
|
|
{
|
|
if (_speedRestrict != RESTRICT_NONE) {
|
|
return _speedRestrict;
|
|
}
|
|
|
|
return _waypt->speedRestriction();
|
|
}
|
|
|
|
void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
|
|
{
|
|
_speedRestrict = ty;
|
|
if (isMachRestrict(ty)) {
|
|
_speed = (speed * -100);
|
|
} else {
|
|
_speed = speed;
|
|
}
|
|
}
|
|
|
|
void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
|
|
{
|
|
_altRestrict = ty;
|
|
_altitudeFt = altFt;
|
|
}
|
|
|
|
double FlightPlan::Leg::courseDeg() const
|
|
{
|
|
return _courseDeg;
|
|
}
|
|
|
|
double FlightPlan::Leg::distanceNm() const
|
|
{
|
|
return _pathDistance;
|
|
}
|
|
|
|
double FlightPlan::Leg::distanceAlongRoute() const
|
|
{
|
|
return _distanceAlongPath;
|
|
}
|
|
|
|
void FlightPlan::rebuildLegData()
|
|
{
|
|
_totalDistance = 0.0;
|
|
int lastLeg = static_cast<int>(_legs.size()) - 1;
|
|
for (int l=0; l<lastLeg; ++l) {
|
|
Leg* cur = _legs[l];
|
|
Leg* next = _legs[l + 1];
|
|
|
|
std::pair<double, double> crsDist =
|
|
next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position());
|
|
_legs[l]->_courseDeg = crsDist.first;
|
|
_legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM;
|
|
_legs[l]->_distanceAlongPath = _totalDistance;
|
|
_totalDistance += crsDist.second * SG_METER_TO_NM;
|
|
} // of legs iteration
|
|
|
|
// set some data on the final leg
|
|
if (lastLeg > 0) {
|
|
// keep the same course as the final leg, when passing the final
|
|
// waypoint
|
|
_legs[lastLeg]->_courseDeg = _legs[lastLeg - 1]->_courseDeg;
|
|
_legs[lastLeg]->_pathDistance = 0.0;
|
|
_legs[lastLeg]->_distanceAlongPath = _totalDistance;
|
|
}
|
|
}
|
|
|
|
SGGeod FlightPlan::pointAlongRoute(int aIndex, double aOffsetNm) const
|
|
{
|
|
if (aIndex >= (int) _legs.size()) {
|
|
throw sg_range_exception();
|
|
}
|
|
|
|
const int lastLeg = static_cast<int>(_legs.size()) - 1;
|
|
// convert the relative offset and leg index into an absolute, positive
|
|
// distance in nm from the route origin. This means we can simply walk
|
|
// forwards to find the actual leg.
|
|
Leg* leg = _legs[(aIndex >= 0) ? aIndex : lastLeg];
|
|
double absolutePathDistance = leg->_distanceAlongPath + aOffsetNm;
|
|
if (absolutePathDistance < 0.0) {
|
|
return _legs[0]->waypoint()->position(); // begining of route
|
|
}
|
|
|
|
if (absolutePathDistance > _totalDistance) {
|
|
return _legs[lastLeg]->waypoint()->position(); // end of route
|
|
}
|
|
|
|
// find the leg containing the absolute distance
|
|
for (int l=0; l<lastLeg; ++l) {
|
|
leg = _legs[l];
|
|
if (absolutePathDistance < leg->_pathDistance) {
|
|
break; // found our matching leg
|
|
}
|
|
absolutePathDistance -= leg->_pathDistance;
|
|
} // of forwards walk along route to find leg
|
|
|
|
return SGGeodesy::direct(leg->waypoint()->position(),
|
|
leg->_courseDeg, absolutePathDistance * SG_NM_TO_METER);
|
|
}
|
|
|
|
void FlightPlan::lockDelegate()
|
|
{
|
|
if (_delegateLock == 0) {
|
|
assert(!_departureChanged && !_arrivalChanged &&
|
|
!_waypointsChanged && !_currentWaypointChanged);
|
|
}
|
|
|
|
++_delegateLock;
|
|
}
|
|
|
|
void FlightPlan::unlockDelegate()
|
|
{
|
|
assert(_delegateLock > 0);
|
|
if (_delegateLock > 1) {
|
|
--_delegateLock;
|
|
return;
|
|
}
|
|
|
|
if (_departureChanged) {
|
|
_departureChanged = false;
|
|
if (_delegate) {
|
|
_delegate->runDepartureChanged();
|
|
}
|
|
}
|
|
|
|
if (_arrivalChanged) {
|
|
_arrivalChanged = false;
|
|
if (_delegate) {
|
|
_delegate->runArrivalChanged();
|
|
}
|
|
}
|
|
|
|
if (_waypointsChanged) {
|
|
_waypointsChanged = false;
|
|
rebuildLegData();
|
|
if (_delegate) {
|
|
_delegate->runWaypointsChanged();
|
|
}
|
|
}
|
|
|
|
if (_currentWaypointChanged) {
|
|
_currentWaypointChanged = false;
|
|
if (_delegate) {
|
|
_delegate->runCurrentWaypointChanged();
|
|
}
|
|
}
|
|
|
|
--_delegateLock;
|
|
}
|
|
|
|
void FlightPlan::registerDelegateFactory(DelegateFactory* df)
|
|
{
|
|
FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
|
|
static_delegateFactories.end(), df);
|
|
if (it != static_delegateFactories.end()) {
|
|
throw sg_exception("duplicate delegate factory registration");
|
|
}
|
|
|
|
static_delegateFactories.push_back(df);
|
|
}
|
|
|
|
void FlightPlan::unregisterDelegateFactory(DelegateFactory* df)
|
|
{
|
|
FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
|
|
static_delegateFactories.end(), df);
|
|
if (it == static_delegateFactories.end()) {
|
|
return;
|
|
}
|
|
|
|
static_delegateFactories.erase(it);
|
|
}
|
|
|
|
void FlightPlan::addDelegate(Delegate* d)
|
|
{
|
|
// wrap any existing delegate(s) in the new one
|
|
d->_inner = _delegate;
|
|
_delegate = d;
|
|
}
|
|
|
|
void FlightPlan::removeDelegate(Delegate* d)
|
|
{
|
|
if (d == _delegate) {
|
|
_delegate = _delegate->_inner;
|
|
} else if (_delegate) {
|
|
_delegate->removeInner(d);
|
|
}
|
|
}
|
|
|
|
FlightPlan::Delegate::Delegate() :
|
|
_deleteWithPlan(false),
|
|
_inner(NULL)
|
|
{
|
|
}
|
|
|
|
FlightPlan::Delegate::~Delegate()
|
|
{
|
|
}
|
|
|
|
void FlightPlan::Delegate::removeInner(Delegate* d)
|
|
{
|
|
if (!_inner) {
|
|
throw sg_exception("FlightPlan delegate not found");
|
|
}
|
|
|
|
if (_inner == d) {
|
|
// replace with grand-child
|
|
_inner = d->_inner;
|
|
} else { // recurse downwards
|
|
_inner->removeInner(d);
|
|
}
|
|
}
|
|
|
|
void FlightPlan::Delegate::runDepartureChanged()
|
|
{
|
|
if (_inner) _inner->runDepartureChanged();
|
|
departureChanged();
|
|
}
|
|
|
|
void FlightPlan::Delegate::runArrivalChanged()
|
|
{
|
|
if (_inner) _inner->runArrivalChanged();
|
|
arrivalChanged();
|
|
}
|
|
|
|
void FlightPlan::Delegate::runWaypointsChanged()
|
|
{
|
|
if (_inner) _inner->runWaypointsChanged();
|
|
waypointsChanged();
|
|
}
|
|
|
|
void FlightPlan::Delegate::runCurrentWaypointChanged()
|
|
{
|
|
if (_inner) _inner->runCurrentWaypointChanged();
|
|
currentWaypointChanged();
|
|
}
|
|
|
|
void FlightPlan::Delegate::runCleared()
|
|
{
|
|
if (_inner) _inner->runCleared();
|
|
cleared();
|
|
}
|
|
|
|
void FlightPlan::Delegate::runFinished()
|
|
{
|
|
if (_inner) _inner->runFinished();
|
|
endOfFlightPlan();
|
|
}
|
|
|
|
} // of namespace flightgear
|