1 // route_mgr.cxx - manage a route (i.e. a collection of waypoints)
3 // Written by Curtis Olson, started January 2004.
7 // Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
34 #include <simgear/compiler.h>
36 #include "route_mgr.hxx"
38 #include <boost/algorithm/string/case_conv.hpp>
39 #include <boost/tuple/tuple.hpp>
41 #include <simgear/misc/strutils.hxx>
42 #include <simgear/structure/exception.hxx>
43 #include <simgear/structure/commands.hxx>
44 #include <simgear/misc/sgstream.hxx>
46 #include <simgear/props/props_io.hxx>
47 #include <simgear/misc/sg_path.hxx>
48 #include <simgear/route/route.hxx>
49 #include <simgear/sg_inlines.h>
51 #include "Main/fg_props.hxx"
52 #include "Navaids/positioned.hxx"
53 #include <Navaids/waypoint.hxx>
54 #include <Navaids/airways.hxx>
55 #include <Navaids/procedure.hxx>
56 #include "Airports/simple.hxx"
57 #include "Airports/runways.hxx"
59 #define RM "/autopilot/route-manager/"
61 #include <GUI/new_gui.hxx>
62 #include <GUI/dialog.hxx>
64 using namespace flightgear;
66 class PropertyWatcher : public SGPropertyChangeListener
69 void watch(SGPropertyNode* p)
71 p->addChangeListener(this, false);
74 virtual void valueChanged(SGPropertyNode*)
79 virtual void fire() = 0;
83 * Template adapter, created by convenience helper below
86 class MethodPropertyWatcher : public PropertyWatcher
89 typedef void (T::*fire_method)();
91 MethodPropertyWatcher(T* obj, fire_method m) :
98 { // dispatch to the object method we're helping
99 (_object->*_method)();
108 PropertyWatcher* createWatcher(T* obj, void (T::*m)())
110 return new MethodPropertyWatcher<T>(obj, m);
113 static bool commandLoadFlightPlan(const SGPropertyNode* arg)
115 FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
116 SGPath path(arg->getStringValue("path"));
117 return self->loadRoute(path);
120 static bool commandSaveFlightPlan(const SGPropertyNode* arg)
122 FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
123 SGPath path(arg->getStringValue("path"));
124 return self->saveRoute(path);
127 static bool commandActivateFlightPlan(const SGPropertyNode* arg)
129 FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
130 bool activate = arg->getBoolValue("activate", true);
140 static bool commandClearFlightPlan(const SGPropertyNode*)
142 FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
147 static bool commandSetActiveWaypt(const SGPropertyNode* arg)
149 FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
150 int index = arg->getIntValue("index");
151 if ((index < 0) || (index >= self->numWaypts())) {
155 self->jumpToIndex(index);
159 static bool commandInsertWaypt(const SGPropertyNode* arg)
161 FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
162 int index = arg->getIntValue("index");
163 std::string ident(arg->getStringValue("id"));
164 int alt = arg->getIntValue("altitude-ft", -999);
165 int ias = arg->getIntValue("speed-knots", -999);
168 // lat/lon may be supplied to narrow down navaid search, or to specify
171 if (arg->hasChild("longitude-deg")) {
172 pos = SGGeod::fromDeg(arg->getDoubleValue("longitude-deg"),
173 arg->getDoubleValue("latitude-deg"));
176 if (arg->hasChild("navaid")) {
177 FGPositionedRef p = FGPositioned::findClosestWithIdent(arg->getStringValue("navaid"), pos);
179 if (arg->hasChild("navaid", 1)) {
180 // intersection of two radials
181 FGPositionedRef p2 = FGPositioned::findClosestWithIdent(arg->getStringValue("navaid[1]"), pos);
183 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << arg->getStringValue("navaid[1]"));
187 double r1 = arg->getDoubleValue("radial"),
188 r2 = arg->getDoubleValue("radial[1]");
191 bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
193 SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << p->ident()
194 << "," << p2->ident());
198 std::string name = p->ident() + "-" + p2->ident();
199 wp = new BasicWaypt(intersection, name, NULL);
200 } else if (arg->hasChild("offset-nm") && arg->hasChild("radial")) {
201 // offset radial from navaid
202 double radial = arg->getDoubleValue("radial");
203 double distanceNm = arg->getDoubleValue("offset-nm");
204 //radial += magvar->getDoubleValue(); // convert to true bearing
205 wp = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
207 wp = new NavaidWaypoint(p, NULL);
209 } else if (arg->hasChild("airport")) {
210 const FGAirport* apt = fgFindAirportID(arg->getStringValue("airport"));
212 SG_LOG(SG_AUTOPILOT, SG_INFO, "no such airport" << arg->getStringValue("airport"));
216 if (arg->hasChild("runway")) {
217 if (!apt->hasRunwayWithIdent(arg->getStringValue("runway"))) {
218 SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << arg->getStringValue("runway") << " at " << apt->ident());
222 FGRunway* runway = apt->getRunwayByIdent(arg->getStringValue("runway"));
223 wp = new RunwayWaypt(runway, NULL);
225 wp = new NavaidWaypoint((FGAirport*) apt, NULL);
227 } else if (arg->hasChild("text")) {
228 wp = self->waypointFromString(arg->getStringValue("text"));
229 } else if (!(pos == SGGeod())) {
230 // just a raw lat/lon
231 wp = new BasicWaypt(pos, ident, NULL);
233 return false; // failed to build waypoint
237 wp->setAltitude(alt, flightgear::RESTRICT_AT);
241 wp->setSpeed(ias, flightgear::RESTRICT_AT);
244 self->insertWayptAtIndex(wp, index);
248 static bool commandDeleteWaypt(const SGPropertyNode* arg)
250 FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
251 int index = arg->getIntValue("index");
252 self->removeWayptAtIndex(index);
256 /////////////////////////////////////////////////////////////////////////////
258 FGRouteMgr::FGRouteMgr() :
260 input(fgGetNode( RM "input", true )),
261 mirror(fgGetNode( RM "route", true )),
262 _departureWatcher(NULL),
263 _arrivalWatcher(NULL)
265 listener = new InputListener(this);
266 input->setStringValue("");
267 input->addChangeListener(listener);
269 SGCommandMgr::instance()->addCommand("load-flightplan", commandLoadFlightPlan);
270 SGCommandMgr::instance()->addCommand("save-flightplan", commandSaveFlightPlan);
271 SGCommandMgr::instance()->addCommand("activate-flightplan", commandActivateFlightPlan);
272 SGCommandMgr::instance()->addCommand("clear-flightplan", commandClearFlightPlan);
273 SGCommandMgr::instance()->addCommand("set-active-waypt", commandSetActiveWaypt);
274 SGCommandMgr::instance()->addCommand("insert-waypt", commandInsertWaypt);
275 SGCommandMgr::instance()->addCommand("delete-waypt", commandDeleteWaypt);
279 FGRouteMgr::~FGRouteMgr()
281 input->removeChangeListener(listener);
283 delete _departureWatcher;
284 delete _arrivalWatcher;
288 void FGRouteMgr::init() {
289 SGPropertyNode_ptr rm(fgGetNode(RM));
291 lon = fgGetNode( "/position/longitude-deg", true );
292 lat = fgGetNode( "/position/latitude-deg", true );
293 alt = fgGetNode( "/position/altitude-ft", true );
294 magvar = fgGetNode("/environment/magnetic-variation-deg", true);
296 departure = fgGetNode(RM "departure", true);
297 departure->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this,
298 &FGRouteMgr::getDepartureICAO, &FGRouteMgr::setDepartureICAO));
299 departure->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this,
300 &FGRouteMgr::getDepartureName, NULL));
301 departure->setStringValue("runway", "");
303 delete _departureWatcher;
304 _departureWatcher = createWatcher(this, &FGRouteMgr::departureChanged);
305 _departureWatcher->watch(departure->getChild("runway"));
307 departure->getChild("etd", 0, true);
308 _departureWatcher->watch(departure->getChild("sid", 0, true));
309 departure->getChild("takeoff-time", 0, true);
311 destination = fgGetNode(RM "destination", true);
312 destination->getChild("airport", 0, true);
314 destination->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this,
315 &FGRouteMgr::getDestinationICAO, &FGRouteMgr::setDestinationICAO));
316 destination->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this,
317 &FGRouteMgr::getDestinationName, NULL));
319 delete _arrivalWatcher;
320 _arrivalWatcher = createWatcher(this, &FGRouteMgr::arrivalChanged);
321 _arrivalWatcher->watch(destination->getChild("runway", 0, true));
323 destination->getChild("eta", 0, true);
324 _arrivalWatcher->watch(destination->getChild("star", 0, true));
325 _arrivalWatcher->watch(destination->getChild("transition", 0, true));
326 destination->getChild("touchdown-time", 0, true);
328 alternate = fgGetNode(RM "alternate", true);
329 alternate->getChild("airport", 0, true);
330 alternate->getChild("runway", 0, true);
332 cruise = fgGetNode(RM "cruise", true);
333 cruise->getChild("altitude-ft", 0, true);
334 cruise->setDoubleValue("altitude-ft", 10000.0);
335 cruise->getChild("flight-level", 0, true);
336 cruise->getChild("speed-kts", 0, true);
337 cruise->setDoubleValue("speed-kts", 160.0);
339 _routingType = cruise->getChild("routing", 0, true);
340 _routingType->setIntValue(ROUTE_HIGH_AIRWAYS);
342 totalDistance = fgGetNode(RM "total-distance", true);
343 totalDistance->setDoubleValue(0.0);
344 distanceToGo = fgGetNode(RM "distance-remaining-nm", true);
345 distanceToGo->setDoubleValue(0.0);
347 ete = fgGetNode(RM "ete", true);
348 ete->setDoubleValue(0.0);
350 elapsedFlightTime = fgGetNode(RM "flight-time", true);
351 elapsedFlightTime->setDoubleValue(0.0);
353 active = fgGetNode(RM "active", true);
354 active->setBoolValue(false);
356 airborne = fgGetNode(RM "airborne", true);
357 airborne->setBoolValue(false);
359 _edited = fgGetNode(RM "signals/edited", true);
360 _finished = fgGetNode(RM "signals/finished", true);
362 _currentWpt = fgGetNode(RM "current-wp", true);
363 _currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
364 (*this, &FGRouteMgr::currentIndex, &FGRouteMgr::jumpToIndex));
366 // temporary distance / eta calculations, for backward-compatability
367 wp0 = fgGetNode(RM "wp", 0, true);
368 wp0->getChild("id", 0, true);
369 wp0->getChild("dist", 0, true);
370 wp0->getChild("eta", 0, true);
371 wp0->getChild("bearing-deg", 0, true);
373 wp1 = fgGetNode(RM "wp", 1, true);
374 wp1->getChild("id", 0, true);
375 wp1->getChild("dist", 0, true);
376 wp1->getChild("eta", 0, true);
378 wpn = fgGetNode(RM "wp-last", 0, true);
379 wpn->getChild("dist", 0, true);
380 wpn->getChild("eta", 0, true);
383 _pathNode = fgGetNode(RM "file-path", 0, true);
387 void FGRouteMgr::postinit()
389 SGPath path(_pathNode->getStringValue());
390 if (!path.isNull()) {
391 SG_LOG(SG_AUTOPILOT, SG_INFO, "loading flight-plan from: " << path.str());
395 // this code only matters for the --wp option now - perhaps the option
396 // should be deprecated in favour of an explicit flight-plan file?
397 // then the global initial waypoint list could die.
398 string_list *waypoints = globals->get_initial_waypoints();
400 string_list::iterator it;
401 for (it = waypoints->begin(); it != waypoints->end(); ++it) {
402 WayptRef w = waypointFromString(*it);
408 SG_LOG(SG_AUTOPILOT, SG_INFO, "loaded initial waypoints:" << _route.size());
412 weightOnWheels = fgGetNode("/gear/gear[0]/wow", true);
413 // check airbone flag agrees with presets
416 void FGRouteMgr::bind() { }
417 void FGRouteMgr::unbind() { }
419 bool FGRouteMgr::isRouteActive() const
421 return active->getBoolValue();
424 void FGRouteMgr::update( double dt )
427 return; // paused, nothing to do here
430 double groundSpeed = fgGetDouble("/velocities/groundspeed-kt", 0.0);
431 if (airborne->getBoolValue()) {
432 time_t now = time(NULL);
433 elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
434 } else { // not airborne
435 if (weightOnWheels->getBoolValue() || (groundSpeed < 40)) {
439 airborne->setBoolValue(true);
440 _takeoffTime = time(NULL); // start the clock
441 departure->setIntValue("takeoff-time", _takeoffTime);
444 if (!active->getBoolValue()) {
448 // basic course/distance information
449 SGGeod currentPos = SGGeod::fromDegFt(lon->getDoubleValue(),
450 lat->getDoubleValue(),alt->getDoubleValue());
452 Waypt* curWpt = currentWaypt();
459 boost::tie(courseDeg, distanceM) = curWpt->courseAndDistanceFrom(currentPos);
461 // update wp0 / wp1 / wp-last
462 wp0->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
463 wp0->setDoubleValue("true-bearing-deg", courseDeg);
464 courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
465 wp0->setDoubleValue("bearing-deg", courseDeg);
466 setETAPropertyFromDistance(wp0->getChild("eta"), distanceM);
468 double totalPathDistance = totalDistance->getDoubleValue() * SG_NM_TO_METER;
469 double totalDistanceRemaining = distanceM; // distance to current waypoint
470 double pathDistance = cachedWaypointPathTotalDistance(_currentIndex);
472 // total distance to go, is direct distance to wp0, plus the remaining
473 // path distance from wp0
474 totalDistanceRemaining += (totalPathDistance - pathDistance);
476 wp0->setDoubleValue("distance-along-route-nm",
477 pathDistance * SG_METER_TO_NM);
478 wp0->setDoubleValue("remaining-distance-nm",
479 (totalPathDistance - pathDistance) * SG_METER_TO_NM);
481 Waypt* nextWpt = nextWaypt();
483 boost::tie(courseDeg, distanceM) = nextWpt->courseAndDistanceFrom(currentPos);
485 wp1->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
486 wp1->setDoubleValue("true-bearing-deg", courseDeg);
487 courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
488 wp1->setDoubleValue("bearing-deg", courseDeg);
489 setETAPropertyFromDistance(wp1->getChild("eta"), distanceM);
491 double pathDistance = cachedWaypointPathTotalDistance(_currentIndex + 1);
492 wp1->setDoubleValue("distance-along-route-nm",
493 pathDistance * SG_METER_TO_NM);
494 wp1->setDoubleValue("remaining-distance-nm",
495 (totalPathDistance - pathDistance) * SG_METER_TO_NM);
498 distanceToGo->setDoubleValue(totalDistanceRemaining * SG_METER_TO_NM);
499 wpn->setDoubleValue("dist", totalDistanceRemaining * SG_METER_TO_NM);
500 ete->setDoubleValue(totalDistanceRemaining * SG_METER_TO_NM / groundSpeed * 3600.0);
501 setETAPropertyFromDistance(wpn->getChild("eta"), totalDistanceRemaining);
504 void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDistance)
506 double speed = fgGetDouble("/velocities/groundspeed-kt", 0.0);
508 aProp->setStringValue("--:--");
513 double eta = aDistance * SG_METER_TO_NM / speed;
514 if ( eta >= 100.0 ) {
515 eta = 99.999; // clamp
518 if ( eta < (1.0/6.0) ) {
519 eta *= 60.0; // within 10 minutes, bump up to min/secs
522 int major = (int)eta,
523 minor = (int)((eta - (int)eta) * 60.0);
524 snprintf( eta_str, 64, "%d:%02d", major, minor );
525 aProp->setStringValue( eta_str );
528 flightgear::WayptRef FGRouteMgr::removeWayptAtIndex(int aIndex)
531 if (aIndex < 0) { // negative indices count the the end
532 index = _route.size() + index;
535 if ((index < 0) || (index >= numWaypts())) {
536 SG_LOG(SG_AUTOPILOT, SG_WARN, "removeWayptAtIndex with invalid index:" << aIndex);
539 WayptVec::iterator it = _route.begin();
542 WayptRef w = *it; // hold a ref now, in case _route is the only other owner
547 if (_currentIndex == index) {
548 currentWaypointChanged(); // current waypoint was removed
551 if (_currentIndex > index) {
552 --_currentIndex; // shift current index down if necessary
555 _edited->fireValueChanged();
561 struct NotGeneratedWayptPredicate : public std::unary_function<const Waypt*, bool>
563 bool operator() (const Waypt* w) const
565 return (w->flag(WPT_GENERATED) == false);
570 void FGRouteMgr::clearRoute()
572 // erase all non-generated waypoints
573 WayptVec::iterator r =
574 std::remove_if(_route.begin(), _route.end(), NotGeneratedWayptPredicate());
575 _route.erase(r, _route.end());
580 active->setBoolValue(false);
581 _edited->fireValueChanged();
585 * route between index-1 and index, using airways.
587 bool FGRouteMgr::routeToIndex(int index, RouteType aRouteType)
593 index = _route.size(); // can still be zero, of course
598 SG_LOG(SG_AUTOPILOT, SG_WARN, "routeToIndex: no departure set");
602 wp1 = new NavaidWaypoint(_departure.get(), NULL);
604 wp1 = wayptAtIndex(index - 1);
607 if (index >= numWaypts()) {
609 SG_LOG(SG_AUTOPILOT, SG_WARN, "routeToIndex: no destination set");
613 wp2 = new NavaidWaypoint(_destination.get(), NULL);
615 wp2 = wayptAtIndex(index);
618 double distNm = SGGeodesy::distanceNm(wp1->position(), wp2->position());
619 if (distNm < 100.0) {
620 SG_LOG(SG_AUTOPILOT, SG_INFO, "routeToIndex: existing waypoints are nearby, direct route");
625 switch (aRouteType) {
626 case ROUTE_HIGH_AIRWAYS:
627 Airway::highLevel()->route(wp1, wp2, r);
630 case ROUTE_LOW_AIRWAYS:
631 Airway::lowLevel()->route(wp1, wp2, r);
635 throw sg_exception("VOR routing not supported yet");
639 SG_LOG(SG_AUTOPILOT, SG_INFO, "routeToIndex: no route found");
643 WayptVec::iterator it = _route.begin();
645 _route.insert(it, r.begin(), r.end());
648 _edited->fireValueChanged();
652 void FGRouteMgr::autoRoute()
654 if (!_departure || !_destination) {
658 string runwayId(departure->getStringValue("runway"));
659 FGRunway* runway = NULL;
660 if (_departure->hasRunwayWithIdent(runwayId)) {
661 runway = _departure->getRunwayByIdent(runwayId);
664 FGRunway* dstRunway = NULL;
665 runwayId = destination->getStringValue("runway");
666 if (_destination->hasRunwayWithIdent(runwayId)) {
667 dstRunway = _destination->getRunwayByIdent(runwayId);
670 _route.clear(); // clear out the existing, first
672 flightgear::SID* sid;
675 boost::tie(sid, sidTrans) = _departure->selectSID(_destination->geod(), runway);
677 SG_LOG(SG_AUTOPILOT, SG_INFO, "selected SID " << sid->ident());
679 SG_LOG(SG_AUTOPILOT, SG_INFO, "\tvia " << sidTrans->ident() << " transition");
682 sid->route(runway, sidTrans, _route);
683 departure->setStringValue("sid", sid->ident());
685 // use airport location for airway search
686 sidTrans = new NavaidWaypoint(_departure.get(), NULL);
687 departure->setStringValue("sid", "");
691 destination->setStringValue("transition", "");
692 destination->setStringValue("star", "");
696 boost::tie(star, starTrans) = _destination->selectSTAR(_departure->geod(), dstRunway);
698 SG_LOG(SG_AUTOPILOT, SG_INFO, "selected STAR " << star->ident());
700 SG_LOG(SG_AUTOPILOT, SG_INFO, "\tvia " << starTrans->ident() << " transition");
701 destination->setStringValue("transition", starTrans->ident());
703 destination->setStringValue("star", star->ident());
705 // use airport location for search
706 starTrans = new NavaidWaypoint(_destination.get(), NULL);
709 // route between them
710 WayptVec airwayRoute;
711 if (Airway::highLevel()->route(sidTrans, starTrans, airwayRoute)) {
712 _route.insert(_route.end(), airwayRoute.begin(), airwayRoute.end());
715 // add the STAR if we have one
717 _destination->buildApproach(starTrans, star, dstRunway, _route);
721 _edited->fireValueChanged();
724 void FGRouteMgr::departureChanged()
726 // remove existing departure waypoints
727 WayptVec::iterator it = _route.begin();
728 for (; it != _route.end(); ++it) {
729 if (!(*it)->flag(WPT_DEPARTURE)) {
734 // erase() invalidates iterators, so grab now
736 if (it == _route.end()) {
738 enroute = new NavaidWaypoint(_destination.get(), NULL);
744 _route.erase(_route.begin(), it);
751 buildDeparture(enroute, wps);
752 for (it = wps.begin(); it != wps.end(); ++it) {
753 (*it)->setFlag(WPT_DEPARTURE);
754 (*it)->setFlag(WPT_GENERATED);
756 _route.insert(_route.begin(), wps.begin(), wps.end());
762 void FGRouteMgr::buildDeparture(WayptRef enroute, WayptVec& wps)
764 string runwayId(departure->getStringValue("runway"));
765 if (!_departure->hasRunwayWithIdent(runwayId)) {
766 // valid airport, but no runway selected, so just the airport noide itself
767 wps.push_back(new NavaidWaypoint(_departure.get(), NULL));
771 FGRunway* r = _departure->getRunwayByIdent(runwayId);
772 string sidId = departure->getStringValue("sid");
773 flightgear::SID* sid = _departure->findSIDWithIdent(sidId);
775 // valid runway, but no SID selected/found, so just the runway node for now
776 if (!sidId.empty() && (sidId != "(none)")) {
777 SG_LOG(SG_AUTOPILOT, SG_INFO, "SID not found:" << sidId);
780 wps.push_back(new RunwayWaypt(r, NULL));
784 // we have a valid SID, awesome
785 string trans(departure->getStringValue("transition"));
786 WayptRef t = sid->findTransitionByName(trans);
788 t = sid->findBestTransition(enroute->position());
791 sid->route(r, t, wps);
792 if (!wps.empty() && wps.front()->flag(WPT_DYNAMIC)) {
793 // ensure first waypoint is static, to simplify other computations
794 wps.insert(wps.begin(), new RunwayWaypt(r, NULL));
798 void FGRouteMgr::arrivalChanged()
800 // remove existing arrival waypoints
801 WayptVec::reverse_iterator rit = _route.rbegin();
802 for (; rit != _route.rend(); ++rit) {
803 if (!(*rit)->flag(WPT_ARRIVAL)) {
808 // erase() invalidates iterators, so grab now
810 WayptVec::iterator it;
812 if (rit != _route.rend()) {
814 it = rit.base(); // convert to fwd iterator
819 _route.erase(it, _route.end());
822 buildArrival(enroute, wps);
823 for (it = wps.begin(); it != wps.end(); ++it) {
824 (*it)->setFlag(WPT_ARRIVAL);
825 (*it)->setFlag(WPT_GENERATED);
827 _route.insert(_route.end(), wps.begin(), wps.end());
833 void FGRouteMgr::buildArrival(WayptRef enroute, WayptVec& wps)
839 string runwayId(destination->getStringValue("runway"));
840 if (!_destination->hasRunwayWithIdent(runwayId)) {
841 // valid airport, but no runway selected, so just the airport node itself
842 wps.push_back(new NavaidWaypoint(_destination.get(), NULL));
846 FGRunway* r = _destination->getRunwayByIdent(runwayId);
847 string starId = destination->getStringValue("star");
848 STAR* star = _destination->findSTARWithIdent(starId);
850 // valid runway, but no STAR selected/found, so just the runway node for now
851 wps.push_back(new RunwayWaypt(r, NULL));
855 // we have a valid STAR
856 string trans(destination->getStringValue("transition"));
857 WayptRef t = star->findTransitionByName(trans);
859 t = star->findBestTransition(enroute->position());
862 _destination->buildApproach(t, star, r, wps);
865 void FGRouteMgr::waypointsChanged()
870 void FGRouteMgr::insertWayptAtIndex(Waypt* aWpt, int aIndex)
877 if ((aIndex == -1) || (aIndex > (int) _route.size())) {
878 index = _route.size();
881 WayptVec::iterator it = _route.begin();
884 if (_currentIndex >= index) {
888 _route.insert(it, aWpt);
891 _edited->fireValueChanged();
894 WayptRef FGRouteMgr::waypointFromString(const string& tgt )
896 string target(boost::to_upper_copy(tgt));
900 double altFt = cruise->getDoubleValue("altitude-ft");
901 RouteRestriction altSetting = RESTRICT_NONE;
903 size_t pos = target.find( '@' );
904 if ( pos != string::npos ) {
905 altFt = atof( target.c_str() + pos + 1 );
906 target = target.substr( 0, pos );
907 if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
908 altFt *= SG_METER_TO_FEET;
909 altSetting = RESTRICT_AT;
913 pos = target.find( ',' );
914 if ( pos != string::npos ) {
915 double lon = atof( target.substr(0, pos).c_str());
916 double lat = atof( target.c_str() + pos + 1);
918 char ew = (lon < 0.0) ? 'W' : 'E';
919 char ns = (lat < 0.0) ? 'S' : 'N';
920 snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
922 wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
923 if (altSetting != RESTRICT_NONE) {
924 wpt->setAltitude(altFt, altSetting);
930 if (_route.empty()) {
931 // route is empty, use current position
932 basePosition = SGGeod::fromDeg(lon->getDoubleValue(), lat->getDoubleValue());
934 basePosition = _route.back()->position();
937 string_list pieces(simgear::strutils::split(target, "/"));
938 FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
940 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
944 if (pieces.size() == 1) {
945 wpt = new NavaidWaypoint(p, NULL);
946 } else if (pieces.size() == 3) {
947 // navaid/radial/distance-nm notation
948 double radial = atof(pieces[1].c_str()),
949 distanceNm = atof(pieces[2].c_str());
950 radial += magvar->getDoubleValue(); // convert to true bearing
951 wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
952 } else if (pieces.size() == 2) {
953 FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
955 SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
959 if (!apt->hasRunwayWithIdent(pieces[1])) {
960 SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
964 FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
965 wpt = new NavaidWaypoint(runway, NULL);
966 } else if (pieces.size() == 4) {
967 // navid/radial/navid/radial notation
968 FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
970 SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
974 double r1 = atof(pieces[1].c_str()),
975 r2 = atof(pieces[3].c_str());
976 r1 += magvar->getDoubleValue();
977 r2 += magvar->getDoubleValue();
980 bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
982 SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target);
986 std::string name = p->ident() + "-" + p2->ident();
987 wpt = new BasicWaypt(intersection, name, NULL);
991 SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
995 if (altSetting != RESTRICT_NONE) {
996 wpt->setAltitude(altFt, altSetting);
1001 // mirror internal route to the property system for inspection by other subsystems
1002 void FGRouteMgr::update_mirror()
1004 mirror->removeChildren("wp");
1006 int num = numWaypts();
1007 double totalDistanceEnroute = 0.0;
1009 for (int i = 0; i < num; i++) {
1010 Waypt* wp = _route[i];
1011 SGPropertyNode *prop = mirror->getChild("wp", i, 1);
1013 const SGGeod& pos(wp->position());
1014 prop->setStringValue("id", wp->ident().c_str());
1015 prop->setDoubleValue("longitude-deg", pos.getLongitudeDeg());
1016 prop->setDoubleValue("latitude-deg",pos.getLatitudeDeg());
1018 // leg course+distance
1019 if (i < (num - 1)) {
1020 Waypt* next = _route[i+1];
1021 std::pair<double, double> crsDist =
1022 next->courseAndDistanceFrom(pos);
1023 prop->setDoubleValue("leg-bearing-true-deg", crsDist.first);
1024 prop->setDoubleValue("leg-distance-nm", crsDist.second * SG_METER_TO_NM);
1025 prop->setDoubleValue("distance-along-route-nm", totalDistanceEnroute);
1026 totalDistanceEnroute += crsDist.second * SG_METER_TO_NM;
1029 if (wp->altitudeRestriction() != RESTRICT_NONE) {
1030 double ft = wp->altitudeFt();
1031 prop->setDoubleValue("altitude-m", ft * SG_FEET_TO_METER);
1032 prop->setDoubleValue("altitude-ft", ft);
1033 prop->setIntValue("flight-level", static_cast<int>(ft / 1000) * 10);
1035 prop->setDoubleValue("altitude-m", -9999.9);
1036 prop->setDoubleValue("altitude-ft", -9999.9);
1039 if (wp->speedRestriction() == SPEED_RESTRICT_MACH) {
1040 prop->setDoubleValue("speed-mach", wp->speedMach());
1041 } else if (wp->speedRestriction() != RESTRICT_NONE) {
1042 prop->setDoubleValue("speed-kts", wp->speedKts());
1045 if (wp->flag(WPT_ARRIVAL)) {
1046 prop->setBoolValue("arrival", true);
1049 if (wp->flag(WPT_DEPARTURE)) {
1050 prop->setBoolValue("departure", true);
1053 if (wp->flag(WPT_MISS)) {
1054 prop->setBoolValue("missed-approach", true);
1057 prop->setBoolValue("generated", wp->flag(WPT_GENERATED));
1058 } // of waypoint iteration
1060 // set number as listener attachment point
1061 mirror->setIntValue("num", _route.size());
1063 NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
1064 FGDialog* rmDlg = gui->getDialog("route-manager");
1066 rmDlg->updateValues();
1070 departure->setDoubleValue("field-elevation-ft", _departure->getElevation());
1074 destination->setDoubleValue("field-elevation-ft", _destination->getElevation());
1077 totalDistance->setDoubleValue(totalDistanceEnroute);
1080 double FGRouteMgr::cachedLegPathDistanceM(int index) const
1082 SGPropertyNode *prop = mirror->getChild("wp", index, 1);
1083 return prop->getDoubleValue("leg-distance-nm") * SG_NM_TO_METER;
1086 double FGRouteMgr::cachedWaypointPathTotalDistance(int index) const
1088 SGPropertyNode *prop = mirror->getChild("wp", index, 1);
1089 return prop->getDoubleValue("distance-along-route-nm") * SG_NM_TO_METER;
1092 // command interface /autopilot/route-manager/input:
1094 // @CLEAR ... clear route
1095 // @POP ... remove first entry
1096 // @DELETE3 ... delete 4th entry
1097 // @INSERT2:KSFO@900 ... insert "KSFO@900" as 3rd entry
1098 // KSFO@900 ... append "KSFO@900"
1100 void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
1102 const char *s = prop->getStringValue();
1103 if (strlen(s) == 0) {
1107 if (!strcmp(s, "@CLEAR"))
1109 else if (!strcmp(s, "@ACTIVATE"))
1111 else if (!strcmp(s, "@LOAD")) {
1112 SGPath path(mgr->_pathNode->getStringValue());
1113 mgr->loadRoute(path);
1114 } else if (!strcmp(s, "@SAVE")) {
1115 SGPath path(mgr->_pathNode->getStringValue());
1116 mgr->saveRoute(path);
1117 } else if (!strcmp(s, "@NEXT")) {
1118 mgr->jumpToIndex(mgr->_currentIndex + 1);
1119 } else if (!strcmp(s, "@PREVIOUS")) {
1120 mgr->jumpToIndex(mgr->_currentIndex - 1);
1121 } else if (!strncmp(s, "@JUMP", 5)) {
1122 mgr->jumpToIndex(atoi(s + 5));
1123 } else if (!strncmp(s, "@DELETE", 7))
1124 mgr->removeWayptAtIndex(atoi(s + 7));
1125 else if (!strncmp(s, "@INSERT", 7)) {
1127 int pos = strtol(s + 7, &r, 10);
1133 mgr->insertWayptAtIndex(mgr->waypointFromString(r), pos);
1134 } else if (!strncmp(s, "@ROUTE", 6)) {
1136 int endIndex = strtol(s + 6, &r, 10);
1137 RouteType rt = (RouteType) mgr->_routingType->getIntValue();
1138 mgr->routeToIndex(endIndex, rt);
1139 } else if (!strcmp(s, "@AUTOROUTE")) {
1141 } else if (!strcmp(s, "@POSINIT")) {
1142 mgr->initAtPosition();
1144 mgr->insertWayptAtIndex(mgr->waypointFromString(s), -1);
1147 void FGRouteMgr::initAtPosition()
1149 if (isRouteActive()) {
1150 return; // don't mess with the active route
1153 if (haveUserWaypoints()) {
1154 // user has already defined, loaded or entered a route, again
1155 // don't interfere with it
1159 if (airborne->getBoolValue()) {
1160 SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: airborne, clearing departure info");
1162 departure->setStringValue("runway", "");
1167 SGGeod pos = SGGeod::fromDegFt(lon->getDoubleValue(),
1168 lat->getDoubleValue(), alt->getDoubleValue());
1170 _departure = FGAirport::findClosest(pos, 20.0);
1172 SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: couldn't find an airport within 20nm");
1173 departure->setStringValue("runway", "");
1178 std::string rwy = departure->getStringValue("runway");
1180 // runway already set, fine
1184 FGRunway* r = _departure->findBestRunwayForPos(pos);
1189 departure->setStringValue("runway", r->ident().c_str());
1190 SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: starting at "
1191 << _departure->ident() << " on runway " << r->ident());
1194 bool FGRouteMgr::haveUserWaypoints() const
1196 return std::find_if(_route.begin(), _route.end(), NotGeneratedWayptPredicate()) != _route.end();
1199 bool FGRouteMgr::activate()
1201 if (isRouteActive()) {
1202 SG_LOG(SG_AUTOPILOT, SG_WARN, "duplicate route-activation, no-op");
1207 currentWaypointChanged();
1209 /* double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
1210 totalDistance->setDoubleValue(routeDistanceNm);
1211 double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
1212 if (cruiseSpeedKts > 1.0) {
1213 // very very crude approximation, doesn't allow for climb / descent
1214 // performance or anything else at all
1215 ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
1218 active->setBoolValue(true);
1219 SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
1224 void FGRouteMgr::sequence()
1226 if (!active->getBoolValue()) {
1227 SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
1231 if (checkFinished()) {
1236 currentWaypointChanged();
1239 bool FGRouteMgr::checkFinished()
1241 if (_currentIndex < (int) _route.size()) {
1245 SG_LOG(SG_AUTOPILOT, SG_INFO, "reached end of active route");
1246 _finished->fireValueChanged();
1247 active->setBoolValue(false);
1251 void FGRouteMgr::jumpToIndex(int index)
1253 if ((index < 0) || (index >= (int) _route.size())) {
1254 SG_LOG(SG_AUTOPILOT, SG_ALERT, "passed invalid index (" <<
1255 index << ") to FGRouteMgr::jumpToIndex");
1259 if (_currentIndex == index) {
1263 // all the checks out the way, go ahead and update state
1264 _currentIndex = index;
1265 currentWaypointChanged();
1266 _currentWpt->fireValueChanged();
1269 void FGRouteMgr::currentWaypointChanged()
1271 Waypt* cur = currentWaypt();
1272 Waypt* next = nextWaypt();
1274 wp0->getChild("id")->setStringValue(cur ? cur->ident() : "");
1275 wp1->getChild("id")->setStringValue(next ? next->ident() : "");
1277 _currentWpt->fireValueChanged();
1278 SG_LOG(SG_AUTOPILOT, SG_INFO, "route manager, current-wp is now " << _currentIndex);
1281 int FGRouteMgr::findWayptIndex(const SGGeod& aPos) const
1283 for (int i=0; i<numWaypts(); ++i) {
1284 if (_route[i]->matches(aPos)) {
1292 Waypt* FGRouteMgr::currentWaypt() const
1294 if ((_currentIndex < 0) || (_currentIndex >= numWaypts()))
1296 return wayptAtIndex(_currentIndex);
1299 Waypt* FGRouteMgr::previousWaypt() const
1301 if (_currentIndex == 0) {
1305 return wayptAtIndex(_currentIndex - 1);
1308 Waypt* FGRouteMgr::nextWaypt() const
1310 if ((_currentIndex < 0) || ((_currentIndex + 1) >= numWaypts())) {
1314 return wayptAtIndex(_currentIndex + 1);
1317 Waypt* FGRouteMgr::wayptAtIndex(int index) const
1319 if ((index < 0) || (index >= numWaypts())) {
1320 throw sg_range_exception("waypt index out of range", "FGRouteMgr::wayptAtIndex");
1323 return _route[index];
1326 SGPropertyNode_ptr FGRouteMgr::wayptNodeAtIndex(int index) const
1328 if ((index < 0) || (index >= numWaypts())) {
1329 throw sg_range_exception("waypt index out of range", "FGRouteMgr::wayptAtIndex");
1332 return mirror->getChild("wp", index);
1335 bool FGRouteMgr::saveRoute(const SGPath& path)
1337 SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
1339 SGPropertyNode_ptr d(new SGPropertyNode);
1340 SGPath path(_pathNode->getStringValue());
1341 d->setIntValue("version", 2);
1344 d->setStringValue("departure/airport", _departure->ident());
1345 d->setStringValue("departure/sid", departure->getStringValue("sid"));
1346 d->setStringValue("departure/runway", departure->getStringValue("runway"));
1350 d->setStringValue("destination/airport", _destination->ident());
1351 d->setStringValue("destination/star", destination->getStringValue("star"));
1352 d->setStringValue("destination/transition", destination->getStringValue("transition"));
1353 d->setStringValue("destination/runway", destination->getStringValue("runway"));
1357 SGPropertyNode* routeNode = d->getChild("route", 0, true);
1358 for (unsigned int i=0; i<_route.size(); ++i) {
1359 Waypt* wpt = _route[i];
1360 wpt->saveAsNode(routeNode->getChild("wp", i, true));
1361 } // of waypoint iteration
1362 writeProperties(path.str(), d, true /* write-all */);
1364 } catch (sg_exception& e) {
1365 SG_LOG(SG_IO, SG_ALERT, "Failed to save flight-plan '" << path.str() << "'. " << e.getMessage());
1370 bool FGRouteMgr::loadRoute(const SGPath& path)
1374 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << path.str()
1375 << "'. The file does not exist.");
1379 // deactivate route first
1380 active->setBoolValue(false);
1382 SGPropertyNode_ptr routeData(new SGPropertyNode);
1384 SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
1386 bool Status = false;
1388 readProperties(path.str(), routeData);
1389 } catch (sg_exception& ) {
1390 // if XML parsing fails, the file might be simple textual list of waypoints
1391 Status = loadPlainTextRoute(path);
1395 if (routeData.valid())
1398 int version = routeData->getIntValue("version", 1);
1400 loadVersion1XMLRoute(routeData);
1401 } else if (version == 2) {
1402 loadVersion2XMLRoute(routeData);
1404 throw sg_io_exception("unsupported XML route version");
1407 } catch (sg_exception& e) {
1408 SG_LOG(SG_IO, SG_ALERT, "Failed to load flight-plan '" << e.getOrigin()
1409 << "'. " << e.getMessage());
1419 void FGRouteMgr::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
1422 SGPropertyNode* dep = routeData->getChild("departure");
1424 string depIdent = dep->getStringValue("airport");
1425 _departure = (FGAirport*) fgFindAirportID(depIdent);
1426 departure->setStringValue("runway", dep->getStringValue("runway"));
1427 departure->setStringValue("sid", dep->getStringValue("sid"));
1428 departure->setStringValue("transition", dep->getStringValue("transition"));
1432 SGPropertyNode* dst = routeData->getChild("destination");
1434 _destination = (FGAirport*) fgFindAirportID(dst->getStringValue("airport"));
1435 destination->setStringValue("runway", dst->getStringValue("runway"));
1436 destination->setStringValue("star", dst->getStringValue("star"));
1437 destination->setStringValue("transition", dst->getStringValue("transition"));
1441 SGPropertyNode* alt = routeData->getChild("alternate");
1443 alternate->setStringValue(alt->getStringValue("airport"));
1444 } // of cruise data loading
1447 SGPropertyNode* crs = routeData->getChild("cruise");
1449 cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
1450 cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
1451 cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
1452 } // of cruise data loading
1456 void FGRouteMgr::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
1458 loadXMLRouteHeader(routeData);
1462 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
1463 for (int i=0; i<routeNode->nChildren(); ++i) {
1464 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
1465 WayptRef wpt = Waypt::createFromProperties(NULL, wpNode);
1466 wpts.push_back(wpt);
1467 } // of route iteration
1472 void FGRouteMgr::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
1474 loadXMLRouteHeader(routeData);
1478 SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);
1479 for (int i=0; i<routeNode->nChildren(); ++i) {
1480 SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
1481 WayptRef wpt = parseVersion1XMLWaypt(wpNode);
1482 wpts.push_back(wpt);
1483 } // of route iteration
1488 WayptRef FGRouteMgr::parseVersion1XMLWaypt(SGPropertyNode* aWP)
1491 if (!_route.empty()) {
1492 lastPos = _route.back()->position();
1493 } else if (_departure) {
1494 lastPos = _departure->geod();
1498 string ident(aWP->getStringValue("ident"));
1499 if (aWP->hasChild("longitude-deg")) {
1500 // explicit longitude/latitude
1501 w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"),
1502 aWP->getDoubleValue("latitude-deg")), ident, NULL);
1505 string nid = aWP->getStringValue("navid", ident.c_str());
1506 FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
1508 throw sg_io_exception("bad route file, unknown navid:" + nid);
1511 SGGeod pos(p->geod());
1512 if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
1513 double radialDeg = aWP->getDoubleValue("offset-radial");
1514 // convert magnetic radial to a true radial!
1515 radialDeg += magvar->getDoubleValue();
1516 double offsetNm = aWP->getDoubleValue("offset-nm");
1518 SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
1521 w = new BasicWaypt(pos, ident, NULL);
1524 double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
1525 if (altFt > -9990.0) {
1526 w->setAltitude(altFt, RESTRICT_AT);
1532 bool FGRouteMgr::loadPlainTextRoute(const SGPath& path)
1535 sg_gzifstream in(path.str().c_str());
1536 if (!in.is_open()) {
1537 throw sg_io_exception("Cannot open file for reading.");
1543 getline(in, line, '\n');
1544 // trim CR from end of line, if found
1545 if (line[line.size() - 1] == '\r') {
1546 line.erase(line.size() - 1, 1);
1549 line = simgear::strutils::strip(line);
1550 if (line.empty() || (line[0] == '#')) {
1551 continue; // ignore empty/comment lines
1554 WayptRef w = waypointFromString(line);
1556 throw sg_io_exception("Failed to create waypoint from line '" + line + "'.");
1560 } // of line iteration
1564 } catch (sg_exception& e) {
1565 SG_LOG(SG_IO, SG_ALERT, "Failed to load route from: '" << path.str() << "'. " << e.getMessage());
1570 const char* FGRouteMgr::getDepartureICAO() const
1576 return _departure->ident().c_str();
1579 const char* FGRouteMgr::getDepartureName() const
1585 return _departure->name().c_str();
1588 void FGRouteMgr::setDepartureICAO(const char* aIdent)
1590 if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
1593 _departure = FGAirport::findByIdent(aIdent);
1599 const char* FGRouteMgr::getDestinationICAO() const
1601 if (!_destination) {
1605 return _destination->ident().c_str();
1608 const char* FGRouteMgr::getDestinationName() const
1610 if (!_destination) {
1614 return _destination->name().c_str();
1617 void FGRouteMgr::setDestinationICAO(const char* aIdent)
1619 if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
1620 _destination = NULL;
1622 _destination = FGAirport::findByIdent(aIdent);
1628 FGAirportRef FGRouteMgr::departureAirport() const
1633 FGAirportRef FGRouteMgr::destinationAirport() const
1635 return _destination;
1638 FGRunway* FGRouteMgr::departureRunway() const
1644 string runwayId(departure->getStringValue("runway"));
1645 if (!_departure->hasRunwayWithIdent(runwayId)) {
1649 return _departure->getRunwayByIdent(runwayId);
1652 FGRunway* FGRouteMgr::destinationRunway() const
1654 if (!_destination) {
1658 string runwayId(destination->getStringValue("runway"));
1659 if (!_destination->hasRunwayWithIdent(runwayId)) {
1663 return _destination->getRunwayByIdent(runwayId);