1 // FlightPlan.cxx - flight plan object
3 // Written by James Turner, started 2012.
5 // Copyright (C) 2012 Curtis L. Olson
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 #include "FlightPlan.hxx"
32 #include <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string.hpp>
34 #include <boost/foreach.hpp>
37 #include <simgear/structure/exception.hxx>
38 #include <simgear/misc/sg_path.hxx>
39 #include <simgear/magvar/magvar.hxx>
40 #include <simgear/timing/sg_time.hxx>
41 #include <simgear/misc/sgstream.hxx>
42 #include <simgear/misc/strutils.hxx>
43 #include <simgear/props/props_io.hxx>
46 #include <Main/globals.hxx>
47 #include "Main/fg_props.hxx"
48 #include <Navaids/procedure.hxx>
49 #include <Navaids/waypoint.hxx>
56 namespace flightgear {
58 typedef std::vector<FlightPlan::DelegateFactory*> FPDelegateFactoryVec;
59 static FPDelegateFactoryVec static_delegateFactories;
61 FlightPlan::FlightPlan() :
63 _departureRunway(NULL),
64 _destinationRunway(NULL),
70 BOOST_FOREACH(DelegateFactory* factory, static_delegateFactories) {
71 Delegate* d = factory->createFlightPlanDelegate(this);
72 if (d) { // factory might not always create a delegate
73 d->_deleteWithPlan = true;
79 FlightPlan::~FlightPlan()
81 // delete all delegates which we own.
82 Delegate* d = _delegate;
86 if (cur->_deleteWithPlan) {
92 FlightPlan* FlightPlan::clone(const string& newIdent) const
94 FlightPlan* c = new FlightPlan();
95 c->_ident = newIdent.empty() ? _ident : newIdent;
97 // copy destination / departure data.
98 c->setDeparture(_departure);
99 c->setDeparture(_departureRunway);
102 c->setApproach(_approach);
103 } else if (_destinationRunway) {
104 c->setDestination(_destinationRunway);
105 } else if (_destination) {
106 c->setDestination(_destination);
113 for (int l=0; l < numLegs(); ++l) {
114 c->_legs.push_back(_legs[l]->cloneFor(c));
120 void FlightPlan::setIdent(const string& s)
125 string FlightPlan::ident() const
130 FlightPlan::Leg* FlightPlan::insertWayptAtIndex(Waypt* aWpt, int aIndex)
140 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
141 index = _legs.size();
144 insertWayptsAtIndex(wps, index);
145 return legAtIndex(aIndex);
148 void FlightPlan::insertWayptsAtIndex(const WayptVec& wps, int aIndex)
155 if ((aIndex == -1) || (aIndex > (int) _legs.size())) {
156 index = _legs.size();
159 LegVec::iterator it = _legs.begin();
162 int endIndex = index + wps.size() - 1;
163 if (_currentIndex >= endIndex) {
164 _currentIndex += wps.size();
168 BOOST_FOREACH(WayptRef wp, wps) {
169 newLegs.push_back(new Leg(this, wp));
172 _legs.insert(it, newLegs.begin(), newLegs.end());
176 _delegate->runWaypointsChanged();
180 void FlightPlan::deleteIndex(int aIndex)
183 if (aIndex < 0) { // negative indices count the the end
184 index = _legs.size() + index;
187 if ((index < 0) || (index >= numLegs())) {
188 SG_LOG(SG_AUTOPILOT, SG_WARN, "removeAtIndex with invalid index:" << aIndex);
191 LegVec::iterator it = _legs.begin();
197 bool curChanged = false;
198 if (_currentIndex == index) {
199 // current waypoint was removed
201 } else if (_currentIndex > index) {
202 --_currentIndex; // shift current index down if necessary
207 _delegate->runWaypointsChanged();
209 _delegate->runCurrentWaypointChanged();
214 void FlightPlan::clear()
217 BOOST_FOREACH(Leg* l, _legs) {
223 _delegate->runDepartureChanged();
224 _delegate->runArrivalChanged();
225 _delegate->runWaypointsChanged();
226 _delegate->runCurrentWaypointChanged();
230 int FlightPlan::clearWayptsWithFlag(WayptFlag flag)
233 for (unsigned int i=0; i<_legs.size(); ++i) {
235 if (!l->waypoint()->flag(flag)) {
239 // okay, we're going to clear this leg
241 if (_currentIndex > (int) i) {
246 LegVec::iterator it = _legs.begin();
252 return 0; // nothing was cleared, don't fire the delegate
257 _delegate->runWaypointsChanged();
258 _delegate->runCurrentWaypointChanged();
264 void FlightPlan::setCurrentIndex(int index)
266 if ((index < -1) || (index >= numLegs())) {
267 throw sg_range_exception("invalid leg index", "FlightPlan::setCurrentIndex");
270 if (index == _currentIndex) {
274 _currentIndex = index;
276 _delegate->runCurrentWaypointChanged();
280 int FlightPlan::findWayptIndex(const SGGeod& aPos) const
282 for (int i=0; i<numLegs(); ++i) {
283 if (_legs[i]->waypoint()->matches(aPos)) {
291 FlightPlan::Leg* FlightPlan::currentLeg() const
293 if ((_currentIndex < 0) || (_currentIndex >= numLegs()))
295 return legAtIndex(_currentIndex);
298 FlightPlan::Leg* FlightPlan::previousLeg() const
300 if (_currentIndex == 0) {
304 return legAtIndex(_currentIndex - 1);
307 FlightPlan::Leg* FlightPlan::nextLeg() const
309 if ((_currentIndex < 0) || ((_currentIndex + 1) >= numLegs())) {
313 return legAtIndex(_currentIndex + 1);
316 FlightPlan::Leg* FlightPlan::legAtIndex(int index) const
318 if ((index < 0) || (index >= numLegs())) {
319 throw sg_range_exception("index out of range", "FlightPlan::legAtIndex");
325 int FlightPlan::findLegIndex(const Leg *l) const
327 for (unsigned int i=0; i<_legs.size(); ++i) {
336 void FlightPlan::setDeparture(FGAirport* apt)
338 if (apt == _departure) {
343 _departureRunway = NULL;
347 _delegate->runDepartureChanged();
351 void FlightPlan::setDeparture(FGRunway* rwy)
353 if (_departureRunway == rwy) {
357 _departureRunway = rwy;
358 if (rwy->airport() != _departure) {
359 _departure = rwy->airport();
364 _delegate->runDepartureChanged();
368 void FlightPlan::setSID(SID* sid, const std::string& transition)
375 _sidTransition = transition;
378 _delegate->runDepartureChanged();
382 void FlightPlan::setSID(Transition* trans)
389 if (trans->parent()->type() != PROCEDURE_SID)
390 throw sg_exception("FlightPlan::setSID: transition does not belong to a SID");
392 setSID((SID*) trans->parent(), trans->ident());
395 Transition* FlightPlan::sidTransition() const
397 if (!_sid || _sidTransition.empty()) {
401 return _sid->findTransitionByName(_sidTransition);
404 void FlightPlan::setDestination(FGAirport* apt)
406 if (apt == _destination) {
411 _destinationRunway = NULL;
412 setSTAR((STAR*)NULL);
415 _delegate->runArrivalChanged();
419 void FlightPlan::setDestination(FGRunway* rwy)
421 if (_destinationRunway == rwy) {
425 _destinationRunway = rwy;
426 if (_destination != rwy->airport()) {
427 _destination = rwy->airport();
428 setSTAR((STAR*)NULL);
432 _delegate->runArrivalChanged();
436 void FlightPlan::setSTAR(STAR* star, const std::string& transition)
443 _starTransition = transition;
446 _delegate->runArrivalChanged();
450 void FlightPlan::setSTAR(Transition* trans)
453 setSTAR((STAR*) NULL);
457 if (trans->parent()->type() != PROCEDURE_STAR)
458 throw sg_exception("FlightPlan::setSTAR: transition does not belong to a STAR");
460 setSTAR((STAR*) trans->parent(), trans->ident());
463 Transition* FlightPlan::starTransition() const
465 if (!_star || _starTransition.empty()) {
469 return _star->findTransitionByName(_starTransition);
472 void FlightPlan::setApproach(flightgear::Approach *app)
474 if (_approach == app) {
480 // keep runway + airport in sync
481 if (_destinationRunway != _approach->runway()) {
482 _destinationRunway = _approach->runway();
485 if (_destination != _destinationRunway->airport()) {
486 _destination = _destinationRunway->airport();
491 _delegate->runArrivalChanged();
495 bool FlightPlan::save(const SGPath& path)
497 SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
499 SGPropertyNode_ptr d(new SGPropertyNode);
500 d->setIntValue("version", 2);
503 d->setStringValue("departure/airport", _departure->ident());
505 d->setStringValue("departure/sid", _sid->ident());
508 if (_departureRunway) {
509 d->setStringValue("departure/runway", _departureRunway->ident());
514 d->setStringValue("destination/airport", _destination->ident());
516 d->setStringValue("destination/star", _star->ident());
520 d->setStringValue("destination/approach", _approach->ident());
523 //d->setStringValue("destination/transition", destination->getStringValue("transition"));
525 if (_destinationRunway) {
526 d->setStringValue("destination/runway", _destinationRunway->ident());
531 SGPropertyNode* routeNode = d->getChild("route", 0, true);
532 for (unsigned int i=0; i<_legs.size(); ++i) {
533 Waypt* wpt = _legs[i]->waypoint();
534 wpt->saveAsNode(routeNode->getChild("wp", i, true));
535 } // of waypoint iteration
536 writeProperties(path.str(), d, true /* write-all */);
538 } catch (sg_exception& e) {
539 SG_LOG(SG_IO, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
544 bool FlightPlan::load(const SGPath& path)
548 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << path.str()
549 << "'. The file does not exist.");
553 SGPropertyNode_ptr routeData(new SGPropertyNode);
554 SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
558 readProperties(path.str(), routeData);
559 } catch (sg_exception& ) {
560 // if XML parsing fails, the file might be simple textual list of waypoints
561 Status = loadPlainTextRoute(path);
565 if (routeData.valid())
568 int version = routeData->getIntValue("version", 1);
570 loadVersion1XMLRoute(routeData);
571 } else if (version == 2) {
572 loadVersion2XMLRoute(routeData);
574 throw sg_io_exception("unsupported XML route version");
577 } catch (sg_exception& e) {
578 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
579 << "'. " << e.getMessage());
586 _delegate->runWaypointsChanged();
592 void FlightPlan::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
595 SGPropertyNode* dep = routeData->getChild("departure");
597 string depIdent = dep->getStringValue("airport");
598 setDeparture((FGAirport*) fgFindAirportID(depIdent));
600 if (dep->hasChild("runway")) {
601 setDeparture(_departure->getRunwayByIdent(dep->getStringValue("runway")));
604 if (dep->hasChild("sid")) {
605 setSID(_departure->findSIDWithIdent(dep->getStringValue("sid")));
607 // departure->setStringValue("transition", dep->getStringValue("transition"));
612 SGPropertyNode* dst = routeData->getChild("destination");
614 setDestination((FGAirport*) fgFindAirportID(dst->getStringValue("airport")));
616 if (dst->hasChild("runway")) {
617 setDestination(_destination->getRunwayByIdent(dst->getStringValue("runway")));
620 if (dst->hasChild("star")) {
621 setSTAR(_destination->findSTARWithIdent(dst->getStringValue("star")));
624 if (dst->hasChild("approach")) {
625 setApproach(_destination->findApproachWithIdent(dst->getStringValue("approach")));
629 // destination->setStringValue("transition", dst->getStringValue("transition"));
633 SGPropertyNode* alt = routeData->getChild("alternate");
635 //alternate->setStringValue(alt->getStringValue("airport"));
639 SGPropertyNode* crs = routeData->getChild("cruise");
641 // cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
642 // cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
643 // cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
644 } // of cruise data loading
648 void FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
650 loadXMLRouteHeader(routeData);
654 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
655 for (int i=0; i<routeNode->nChildren(); ++i) {
656 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
657 Leg* l = new Leg(this, Waypt::createFromProperties(NULL, wpNode));
659 } // of route iteration
662 void FlightPlan::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
664 loadXMLRouteHeader(routeData);
668 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
669 for (int i=0; i<routeNode->nChildren(); ++i) {
670 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
671 Leg* l = new Leg(this, parseVersion1XMLWaypt(wpNode));
673 } // of route iteration
677 WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
680 if (!_legs.empty()) {
681 lastPos = _legs.back()->waypoint()->position();
682 } else if (_departure) {
683 lastPos = _departure->geod();
687 string ident(aWP->getStringValue("ident"));
688 if (aWP->hasChild("longitude-deg")) {
689 // explicit longitude/latitude
690 w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
691 aWP->getDoubleValue("latitude-deg")), ident, NULL);
694 string nid = aWP->getStringValue("navid", ident.c_str());
695 FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
697 throw sg_io_exception("bad route file, unknown navid:" + nid);
700 SGGeod pos(p->geod());
701 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
702 double radialDeg = aWP->getDoubleValue("offset-radial");
703 // convert magnetic radial to a true radial!
704 radialDeg += magvarDegAt(pos);
705 double offsetNm = aWP->getDoubleValue("offset-nm");
707 SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
710 w = new BasicWaypt(pos, ident, NULL);
713 double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
714 if (altFt > -9990.0) {
715 w->setAltitude(altFt, RESTRICT_AT);
721 bool FlightPlan::loadPlainTextRoute(const SGPath& path)
724 sg_gzifstream in(path.str().c_str());
726 throw sg_io_exception("Cannot open file for reading.");
732 getline(in, line, '\n');
733 // trim CR from end of line, if found
734 if (line[line.size() - 1] == '\r') {
735 line.erase(line.size() - 1, 1);
738 line = simgear::strutils::strip(line);
739 if (line.empty() || (line[0] == '#')) {
740 continue; // ignore empty/comment lines
743 WayptRef w = waypointFromString(line);
745 throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
748 _legs.push_back(new Leg(this, w));
749 } // of line iteration
750 } catch (sg_exception& e) {
751 SG_LOG(SG_IO, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
759 double FlightPlan::magvarDegAt(const SGGeod& pos) const
761 double jd = globals->get_time_params()->getJD();
762 return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
765 WayptRef FlightPlan::waypointFromString(const string& tgt )
767 string target(boost::to_upper_copy(tgt));
772 RouteRestriction altSetting = RESTRICT_NONE;
774 size_t pos = target.find( '@' );
775 if ( pos != string::npos ) {
776 altFt = atof( target.c_str() + pos + 1 );
777 target = target.substr( 0, pos );
778 if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
779 altFt *= SG_METER_TO_FEET;
780 altSetting = RESTRICT_AT;
784 pos = target.find( ',' );
785 if ( pos != string::npos ) {
786 double lon = atof( target.substr(0, pos).c_str());
787 double lat = atof( target.c_str() + pos + 1);
789 char ew = (lon < 0.0) ? 'W' : 'E';
790 char ns = (lat < 0.0) ? 'S' : 'N';
791 snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
793 wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
794 if (altSetting != RESTRICT_NONE) {
795 wpt->setAltitude(altFt, altSetting);
802 // route is empty, use current position
803 basePosition = globals->get_aircraft_position();
805 basePosition = _legs.back()->waypoint()->position();
808 string_list pieces(simgear::strutils::split(target, "/"));
809 FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
811 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
815 double magvar = magvarDegAt(basePosition);
817 if (pieces.size() == 1) {
818 wpt = new NavaidWaypoint(p, NULL);
819 } else if (pieces.size() == 3) {
820 // navaid/radial/distance-nm notation
821 double radial = atof(pieces[1].c_str()),
822 distanceNm = atof(pieces[2].c_str());
824 wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
825 } else if (pieces.size() == 2) {
826 FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
828 SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
832 if (!apt->hasRunwayWithIdent(pieces[1])) {
833 SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
837 FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
838 wpt = new NavaidWaypoint(runway, NULL);
839 } else if (pieces.size() == 4) {
840 // navid/radial/navid/radial notation
841 FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
843 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
847 double r1 = atof(pieces[1].c_str()),
848 r2 = atof(pieces[3].c_str());
853 bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
855 SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target);
859 std::string name = p->ident() + "-" + p2->ident();
860 wpt = new BasicWaypt(intersection, name, NULL);
864 SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
868 if (altSetting != RESTRICT_NONE) {
869 wpt->setAltitude(altFt, altSetting);
874 FlightPlan::Leg::Leg(FlightPlan* owner, WayptRef wpt) :
876 _speedRestrict(RESTRICT_NONE),
877 _altRestrict(RESTRICT_NONE),
881 throw sg_exception("can't create FlightPlan::Leg without underlying waypoint");
883 _speed = _altitudeFt = 0;
886 FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
888 Leg* c = new Leg(owner, _waypt);
891 c->_speedRestrict = _speedRestrict;
892 c->_altitudeFt = _altitudeFt;
893 c->_altRestrict = _altRestrict;
898 FlightPlan::Leg* FlightPlan::Leg::nextLeg() const
900 return _parent->legAtIndex(index() + 1);
903 unsigned int FlightPlan::Leg::index() const
905 return _parent->findLegIndex(this);
908 int FlightPlan::Leg::altitudeFt() const
910 if (_altRestrict != RESTRICT_NONE) {
914 return _waypt->altitudeFt();
917 int FlightPlan::Leg::speed() const
919 if (_speedRestrict != RESTRICT_NONE) {
923 return _waypt->speed();
926 int FlightPlan::Leg::speedKts() const
931 double FlightPlan::Leg::speedMach() const
933 if (!isMachRestrict(_speedRestrict)) {
937 return -(_speed / 100.0);
940 RouteRestriction FlightPlan::Leg::altitudeRestriction() const
942 if (_altRestrict != RESTRICT_NONE) {
946 return _waypt->altitudeRestriction();
949 RouteRestriction FlightPlan::Leg::speedRestriction() const
951 if (_speedRestrict != RESTRICT_NONE) {
952 return _speedRestrict;
955 return _waypt->speedRestriction();
958 void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
961 if (isMachRestrict(ty)) {
962 _speed = (speed * -100);
968 void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
974 double FlightPlan::Leg::courseDeg() const
979 double FlightPlan::Leg::distanceNm() const
981 return _pathDistance;
984 double FlightPlan::Leg::distanceAlongRoute() const
986 return _distanceAlongPath;
989 void FlightPlan::rebuildLegData()
991 _totalDistance = 0.0;
992 int lastLeg = static_cast<int>(_legs.size()) - 1;
993 for (int l=0; l<lastLeg; ++l) {
995 Leg* next = _legs[l + 1];
997 std::pair<double, double> crsDist =
998 next->waypoint()->courseAndDistanceFrom(cur->waypoint()->position());
999 _legs[l]->_courseDeg = crsDist.first;
1000 _legs[l]->_pathDistance = crsDist.second * SG_METER_TO_NM;
1001 _legs[l]->_distanceAlongPath = _totalDistance;
1002 _totalDistance += crsDist.second * SG_METER_TO_NM;
1003 } // of legs iteration
1006 void FlightPlan::registerDelegateFactory(DelegateFactory* df)
1008 FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
1009 static_delegateFactories.end(), df);
1010 if (it != static_delegateFactories.end()) {
1011 throw sg_exception("duplicate delegate factory registration");
1014 static_delegateFactories.push_back(df);
1017 void FlightPlan::unregisterDelegateFactory(DelegateFactory* df)
1019 FPDelegateFactoryVec::iterator it = std::find(static_delegateFactories.begin(),
1020 static_delegateFactories.end(), df);
1021 if (it == static_delegateFactories.end()) {
1025 static_delegateFactories.erase(it);
1028 void FlightPlan::addDelegate(Delegate* d)
1030 // wrap any existing delegate(s) in the new one
1031 d->_inner = _delegate;
1035 void FlightPlan::removeDelegate(Delegate* d)
1037 if (d == _delegate) {
1038 _delegate = _delegate->_inner;
1039 } else if (_delegate) {
1040 _delegate->removeInner(d);
1044 FlightPlan::Delegate::Delegate() :
1045 _deleteWithPlan(false),
1051 FlightPlan::Delegate::~Delegate()
1056 void FlightPlan::Delegate::removeInner(Delegate* d)
1063 // replace with grand-child
1065 } else { // recurse downwards
1066 _inner->removeInner(d);
1070 void FlightPlan::Delegate::runDepartureChanged()
1072 if (_inner) _inner->runDepartureChanged();
1076 void FlightPlan::Delegate::runArrivalChanged()
1078 if (_inner) _inner->runArrivalChanged();
1082 void FlightPlan::Delegate::runWaypointsChanged()
1084 if (_inner) _inner->runWaypointsChanged();
1088 void FlightPlan::Delegate::runCurrentWaypointChanged()
1090 if (_inner) _inner->runCurrentWaypointChanged();
1091 currentWaypointChanged();
1094 } // of namespace flightgear