]> git.mxchange.org Git - flightgear.git/blob - src/Autopilot/route_mgr.cxx
Expand route-manager waypoint info, to support smarter FMS functions.
[flightgear.git] / src / Autopilot / route_mgr.cxx
1 // route_mgr.cxx - manage a route (i.e. a collection of waypoints)
2 //
3 // Written by Curtis Olson, started January 2004.
4 //            Norman Vine
5 //            Melchior FRANZ
6 //
7 // Copyright (C) 2004  Curtis L. Olson  - http://www.flightgear.org/~curt
8 //
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.
13 //
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.
18 //
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.
22 //
23 // $Id$
24
25
26 #ifdef HAVE_CONFIG_H
27 #  include <config.h>
28 #endif
29
30 #ifdef HAVE_WINDOWS_H
31 #include <time.h>
32 #endif
33
34 #include <simgear/compiler.h>
35
36 #include "route_mgr.hxx"
37
38 #include <boost/algorithm/string/case_conv.hpp>
39 #include <boost/tuple/tuple.hpp>
40
41 #include <simgear/misc/strutils.hxx>
42 #include <simgear/structure/exception.hxx>
43 #include <simgear/misc/sgstream.hxx>
44
45 #include <simgear/props/props_io.hxx>
46 #include <simgear/misc/sg_path.hxx>
47 #include <simgear/route/route.hxx>
48 #include <simgear/sg_inlines.h>
49
50 #include "Main/fg_props.hxx"
51 #include "Navaids/positioned.hxx"
52 #include <Navaids/waypoint.hxx>
53 #include <Navaids/airways.hxx>
54 #include <Navaids/procedure.hxx>
55 #include "Airports/simple.hxx"
56 #include "Airports/runways.hxx"
57
58 #define RM "/autopilot/route-manager/"
59
60 #include <GUI/new_gui.hxx>
61 #include <GUI/dialog.hxx>
62
63 using namespace flightgear;
64
65 class PropertyWatcher : public SGPropertyChangeListener
66 {
67 public:
68   void watch(SGPropertyNode* p)
69   {
70     p->addChangeListener(this, false);
71   }
72
73   virtual void valueChanged(SGPropertyNode*)
74   {
75     fire();
76   }
77 protected:
78   virtual void fire() = 0;
79 };
80
81 /**
82  * Template adapter, created by convenience helper below
83  */
84 template <class T>
85 class MethodPropertyWatcher : public PropertyWatcher
86 {
87 public:
88   typedef void (T::*fire_method)();
89
90   MethodPropertyWatcher(T* obj, fire_method m) :
91     _object(obj),
92     _method(m)
93   { ; }
94   
95 protected:
96   virtual void fire()
97   { // dispatch to the object method we're helping
98     (_object->*_method)();
99   }
100   
101 private:
102   T* _object;
103   fire_method _method;
104 };
105
106 template <class T>
107 PropertyWatcher* createWatcher(T* obj, void (T::*m)())
108 {
109   return new MethodPropertyWatcher<T>(obj, m);
110 }
111
112 FGRouteMgr::FGRouteMgr() :
113   _currentIndex(0),
114   input(fgGetNode( RM "input", true )),
115   mirror(fgGetNode( RM "route", true ))
116 {
117   listener = new InputListener(this);
118   input->setStringValue("");
119   input->addChangeListener(listener);
120 }
121
122
123 FGRouteMgr::~FGRouteMgr()
124 {
125   input->removeChangeListener(listener);
126   delete listener;
127 }
128
129
130 void FGRouteMgr::init() {
131   SGPropertyNode_ptr rm(fgGetNode(RM));
132   
133   lon = fgGetNode( "/position/longitude-deg", true );
134   lat = fgGetNode( "/position/latitude-deg", true );
135   alt = fgGetNode( "/position/altitude-ft", true );
136   magvar = fgGetNode("/environment/magnetic-variation-deg", true);
137      
138   departure = fgGetNode(RM "departure", true);
139   departure->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
140     &FGRouteMgr::getDepartureICAO, &FGRouteMgr::setDepartureICAO));
141   departure->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
142     &FGRouteMgr::getDepartureName, NULL));
143   departure->setStringValue("runway", "");
144   
145   _departureWatcher = createWatcher(this, &FGRouteMgr::departureChanged);
146   _departureWatcher->watch(departure->getChild("runway"));
147   
148   departure->getChild("etd", 0, true);
149   _departureWatcher->watch(departure->getChild("sid", 0, true));
150   departure->getChild("takeoff-time", 0, true);
151
152   destination = fgGetNode(RM "destination", true);
153   destination->getChild("airport", 0, true);
154   
155   destination->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
156     &FGRouteMgr::getDestinationICAO, &FGRouteMgr::setDestinationICAO));
157   destination->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
158     &FGRouteMgr::getDestinationName, NULL));
159   
160   _arrivalWatcher = createWatcher(this, &FGRouteMgr::arrivalChanged);
161   _arrivalWatcher->watch(destination->getChild("runway", 0, true));
162   
163   destination->getChild("eta", 0, true);
164   _arrivalWatcher->watch(destination->getChild("star", 0, true));
165   _arrivalWatcher->watch(destination->getChild("transition", 0, true));
166   destination->getChild("touchdown-time", 0, true);
167
168   alternate = fgGetNode(RM "alternate", true);
169   alternate->getChild("airport", 0, true);
170   alternate->getChild("runway", 0, true);
171   
172   cruise = fgGetNode(RM "cruise", true);
173   cruise->getChild("altitude-ft", 0, true);
174   cruise->setDoubleValue("altitude-ft", 10000.0);
175   cruise->getChild("flight-level", 0, true);
176   cruise->getChild("speed-kts", 0, true);
177   cruise->setDoubleValue("speed-kts", 160.0);
178   
179   _routingType = cruise->getChild("routing", 0, true);
180   _routingType->setIntValue(ROUTE_HIGH_AIRWAYS);
181   
182   totalDistance = fgGetNode(RM "total-distance", true);
183   totalDistance->setDoubleValue(0.0);
184   
185   ete = fgGetNode(RM "ete", true);
186   ete->setDoubleValue(0.0);
187   
188   elapsedFlightTime = fgGetNode(RM "flight-time", true);
189   elapsedFlightTime->setDoubleValue(0.0);
190   
191   active = fgGetNode(RM "active", true);
192   active->setBoolValue(false);
193   
194   airborne = fgGetNode(RM "airborne", true);
195   airborne->setBoolValue(false);
196     
197   _edited = fgGetNode(RM "signals/edited", true);
198   _finished = fgGetNode(RM "signals/finished", true);
199   
200   _currentWpt = fgGetNode(RM "current-wp", true);
201   _currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
202     (*this, &FGRouteMgr::currentIndex, &FGRouteMgr::jumpToIndex));
203       
204   // temporary distance / eta calculations, for backward-compatability
205   wp0 = fgGetNode(RM "wp", 0, true);
206   wp0->getChild("id", 0, true);
207   wp0->getChild("dist", 0, true);
208   wp0->getChild("eta", 0, true);
209   wp0->getChild("bearing-deg", 0, true);
210   
211   wp1 = fgGetNode(RM "wp", 1, true);
212   wp1->getChild("id", 0, true);
213   wp1->getChild("dist", 0, true);
214   wp1->getChild("eta", 0, true);
215   
216   wpn = fgGetNode(RM "wp-last", 0, true);
217   wpn->getChild("dist", 0, true);
218   wpn->getChild("eta", 0, true);
219   
220   update_mirror();
221   _pathNode = fgGetNode(RM "file-path", 0, true);
222 }
223
224
225 void FGRouteMgr::postinit()
226 {
227   string_list *waypoints = globals->get_initial_waypoints();
228   if (waypoints) {
229     string_list::iterator it;
230     for (it = waypoints->begin(); it != waypoints->end(); ++it) {
231       WayptRef w = waypointFromString(*it);
232       if (w) {
233         _route.push_back(w);
234       }
235     }
236     
237     SG_LOG(SG_AUTOPILOT, SG_INFO, "loaded initial waypoints:" << _route.size());
238   }
239
240   weightOnWheels = fgGetNode("/gear/gear[0]/wow", true);
241   // check airbone flag agrees with presets
242 }
243
244 void FGRouteMgr::bind() { }
245 void FGRouteMgr::unbind() { }
246
247 bool FGRouteMgr::isRouteActive() const
248 {
249   return active->getBoolValue();
250 }
251
252 void FGRouteMgr::update( double dt )
253 {
254   if (dt <= 0.0) {
255     return; // paused, nothing to do here
256   }
257   
258   double groundSpeed = fgGetDouble("/velocities/groundspeed-kt", 0.0);
259   if (airborne->getBoolValue()) {
260     time_t now = time(NULL);
261     elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
262   } else { // not airborne
263     if (weightOnWheels->getBoolValue() || (groundSpeed < 40)) {
264       return;
265     }
266     
267     airborne->setBoolValue(true);
268     _takeoffTime = time(NULL); // start the clock
269     departure->setIntValue("takeoff-time", _takeoffTime);
270   }
271   
272   if (!active->getBoolValue()) {
273     return;
274   }
275
276 // basic course/distance information
277   SGGeod currentPos = SGGeod::fromDegFt(lon->getDoubleValue(), 
278     lat->getDoubleValue(),alt->getDoubleValue());
279
280   Waypt* curWpt = currentWaypt();
281   if (!curWpt) {
282     return;
283   }
284   
285   double courseDeg;
286   double distanceM;
287   boost::tie(courseDeg, distanceM) = curWpt->courseAndDistanceFrom(currentPos);
288   
289 // update wp0 / wp1 / wp-last for legacy users
290   wp0->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
291   courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
292   wp0->setDoubleValue("bearing-deg", courseDeg);
293   setETAPropertyFromDistance(wp0->getChild("eta"), distanceM);
294   
295   double totalDistanceRemaining = distanceM; // distance to current waypoint
296   
297   Waypt* nextWpt = nextWaypt();
298   if (nextWpt) {
299     boost::tie(courseDeg, distanceM) = nextWpt->courseAndDistanceFrom(currentPos);
300      
301     wp1->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
302     courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
303     wp1->setDoubleValue("bearing-deg", courseDeg);
304     setETAPropertyFromDistance(wp1->getChild("eta"), distanceM);
305   }
306   
307   Waypt* prev = curWpt;
308   for (unsigned int i=_currentIndex + 1; i<_route.size(); ++i) {
309     Waypt* w = _route[i];
310     if (w->flag(WPT_DYNAMIC)) continue;
311     totalDistanceRemaining += SGGeodesy::distanceM(prev->position(), w->position());
312     prev = w;
313     
314     
315   }
316   
317   wpn->setDoubleValue("dist", totalDistanceRemaining * SG_METER_TO_NM);
318   ete->setDoubleValue(totalDistanceRemaining * SG_METER_TO_NM / groundSpeed * 3600.0);
319   setETAPropertyFromDistance(wpn->getChild("eta"), totalDistanceRemaining);
320 }
321
322 void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDistance)
323 {
324   double speed = fgGetDouble("/velocities/groundspeed-kt", 0.0);
325   if (speed < 1.0) {
326     aProp->setStringValue("--:--");
327     return;
328   }
329
330   char eta_str[64];
331   double eta = aDistance * SG_METER_TO_NM / speed;
332   if ( eta >= 100.0 ) { 
333       eta = 99.999; // clamp
334   }
335   
336   if ( eta < (1.0/6.0) ) {
337     eta *= 60.0; // within 10 minutes, bump up to min/secs
338   }
339   
340   int major = (int)eta, 
341       minor = (int)((eta - (int)eta) * 60.0);
342   snprintf( eta_str, 64, "%d:%02d", major, minor );
343   aProp->setStringValue( eta_str );
344 }
345
346 flightgear::WayptRef FGRouteMgr::removeWayptAtIndex(int aIndex)
347 {
348   int index = aIndex;
349   if (aIndex < 0) { // negative indices count the the end
350     index = _route.size() + index;
351   }
352   
353   if ((index < 0) || (index >= numWaypts())) {
354     SG_LOG(SG_AUTOPILOT, SG_WARN, "removeWayptAtIndex with invalid index:" << aIndex);
355     return NULL;
356   }
357   
358   if (_currentIndex > index) {
359     --_currentIndex; // shift current index down if necessary
360   }
361
362   WayptVec::iterator it = _route.begin();
363   it += index;
364   
365   WayptRef w = *it; // hold a ref now, in case _route is the only other owner
366   _route.erase(it);
367   
368   update_mirror();
369   _edited->fireValueChanged();
370   checkFinished();
371   
372   return w;
373 }
374   
375 void FGRouteMgr::clearRoute()
376 {
377   _route.clear();
378   _currentIndex = -1;
379   
380   update_mirror();
381   active->setBoolValue(false);
382   _edited->fireValueChanged();
383 }
384
385 /**
386  * route between index-1 and index, using airways.
387  */
388 bool FGRouteMgr::routeToIndex(int index, RouteType aRouteType)
389 {
390   WayptRef wp1;
391   WayptRef wp2;
392   
393   if (index == -1) {
394     index = _route.size(); // can still be zero, of course
395   }
396   
397   if (index == 0) {
398     if (!_departure) {
399       SG_LOG(SG_AUTOPILOT, SG_WARN, "routeToIndex: no departure set");
400       return false;
401     }
402     
403     wp1 = new NavaidWaypoint(_departure.get(), NULL);
404   } else {
405     wp1 = wayptAtIndex(index - 1);
406   }
407   
408   if (index >= numWaypts()) {
409     if (!_destination) {
410       SG_LOG(SG_AUTOPILOT, SG_WARN, "routeToIndex: no destination set");
411       return false;
412     }
413     
414     wp2 = new NavaidWaypoint(_destination.get(), NULL);
415   } else {
416     wp2 = wayptAtIndex(index);
417   }
418   
419   double distNm = SGGeodesy::distanceNm(wp1->position(), wp2->position());
420   if (distNm < 100.0) {
421     SG_LOG(SG_AUTOPILOT, SG_INFO, "routeToIndex: existing waypoints are nearby, direct route");
422     return true;
423   }
424   
425   WayptVec r;
426   switch (aRouteType) {
427   case ROUTE_HIGH_AIRWAYS:
428     Airway::highLevel()->route(wp1, wp2, r);
429     break;
430     
431   case ROUTE_LOW_AIRWAYS:
432     Airway::lowLevel()->route(wp1, wp2, r);
433     break;
434     
435   case ROUTE_VOR:
436     throw sg_exception("VOR routing not supported yet");
437   }
438   
439   if (r.empty()) {
440     SG_LOG(SG_AUTOPILOT, SG_INFO, "routeToIndex: no route found");
441     return false;
442   }
443
444   WayptVec::iterator it = _route.begin();
445   it += index;
446   _route.insert(it, r.begin(), r.end());
447
448   update_mirror();
449   _edited->fireValueChanged();
450   return true;
451 }
452
453 void FGRouteMgr::autoRoute()
454 {
455   if (!_departure || !_destination) {
456     return;
457   }
458   
459   string runwayId(departure->getStringValue("runway"));
460   FGRunway* runway = NULL;
461   if (_departure->hasRunwayWithIdent(runwayId)) {
462     runway = _departure->getRunwayByIdent(runwayId);
463   }
464   
465   FGRunway* dstRunway = NULL;
466   runwayId = destination->getStringValue("runway");
467   if (_destination->hasRunwayWithIdent(runwayId)) {
468     dstRunway = _destination->getRunwayByIdent(runwayId);
469   }
470     
471   _route.clear(); // clear out the existing, first
472 // SID
473   flightgear::SID* sid;
474   WayptRef sidTrans;
475   
476   boost::tie(sid, sidTrans) = _departure->selectSID(_destination->geod(), runway);
477   if (sid) { 
478     SG_LOG(SG_AUTOPILOT, SG_INFO, "selected SID " << sid->ident());
479     if (sidTrans) {
480       SG_LOG(SG_AUTOPILOT, SG_INFO, "\tvia " << sidTrans->ident() << " transition");
481     }
482     
483     sid->route(runway, sidTrans, _route);
484     departure->setStringValue("sid", sid->ident());
485   } else {
486     // use airport location for airway search
487     sidTrans = new NavaidWaypoint(_departure.get(), NULL);
488     departure->setStringValue("sid", "");
489   }
490   
491 // STAR
492   destination->setStringValue("transition", "");
493   destination->setStringValue("star", "");
494   
495   STAR* star;
496   WayptRef starTrans;
497   boost::tie(star, starTrans) = _destination->selectSTAR(_departure->geod(), dstRunway);
498   if (star) {
499     SG_LOG(SG_AUTOPILOT, SG_INFO, "selected STAR " << star->ident());
500     if (starTrans) {
501       SG_LOG(SG_AUTOPILOT, SG_INFO, "\tvia " << starTrans->ident() << " transition");
502       destination->setStringValue("transition", starTrans->ident());
503     }    
504     destination->setStringValue("star", star->ident());
505   } else {
506     // use airport location for search
507     starTrans = new NavaidWaypoint(_destination.get(), NULL);
508   }
509   
510 // route between them
511   WayptVec airwayRoute;
512   if (Airway::highLevel()->route(sidTrans, starTrans, airwayRoute)) {
513     _route.insert(_route.end(), airwayRoute.begin(), airwayRoute.end());
514   }
515   
516 // add the STAR if we have one
517   if (star) {
518     _destination->buildApproach(starTrans, star, dstRunway, _route);
519   }
520
521   update_mirror();
522   _edited->fireValueChanged();
523 }
524
525 void FGRouteMgr::departureChanged()
526 {
527 // remove existing departure waypoints
528   WayptVec::iterator it = _route.begin();
529   for (; it != _route.end(); ++it) {
530     if (!(*it)->flag(WPT_DEPARTURE)) {
531       break;
532     }
533   }
534   
535   // erase() invalidates iterators, so grab now
536   WayptRef enroute;
537   if (it == _route.end()) {
538     if (_destination) {
539       enroute = new NavaidWaypoint(_destination.get(), NULL);
540     }
541   } else {
542     enroute = *it;
543   }
544
545   _route.erase(_route.begin(), it);
546   if (!_departure) {
547     waypointsChanged();
548     return;
549   }
550   
551   WayptVec wps;
552   buildDeparture(enroute, wps);
553   for (it = wps.begin(); it != wps.end(); ++it) {
554     (*it)->setFlag(WPT_DEPARTURE);
555     (*it)->setFlag(WPT_GENERATED);
556   }
557   _route.insert(_route.begin(), wps.begin(), wps.end());
558   
559   update_mirror();
560   waypointsChanged();
561 }
562
563 void FGRouteMgr::buildDeparture(WayptRef enroute, WayptVec& wps)
564 {
565   string runwayId(departure->getStringValue("runway"));
566   if (!_departure->hasRunwayWithIdent(runwayId)) {
567 // valid airport, but no runway selected, so just the airport noide itself
568     wps.push_back(new NavaidWaypoint(_departure.get(), NULL));
569     return;
570   }
571   
572   FGRunway* r = _departure->getRunwayByIdent(runwayId);
573   string sidId = departure->getStringValue("sid");
574   flightgear::SID* sid = _departure->findSIDWithIdent(sidId);
575   if (!sid) {
576 // valid runway, but no SID selected/found, so just the runway node for now
577     if (!sidId.empty() && (sidId != "(none)")) {
578       SG_LOG(SG_AUTOPILOT, SG_INFO, "SID not found:" << sidId);
579     }
580     
581     wps.push_back(new RunwayWaypt(r, NULL));
582     return;
583   }
584   
585 // we have a valid SID, awesome
586   string trans(departure->getStringValue("transition"));
587   WayptRef t = sid->findTransitionByName(trans);
588   if (!t && enroute) {
589     t = sid->findBestTransition(enroute->position());
590   }
591
592   sid->route(r, t, wps);
593   if (!wps.empty() && wps.front()->flag(WPT_DYNAMIC)) {
594     // ensure first waypoint is static, to simplify other computations
595     wps.insert(wps.begin(), new RunwayWaypt(r, NULL));
596   }
597 }
598
599 void FGRouteMgr::arrivalChanged()
600 {  
601   // remove existing arrival waypoints
602   WayptVec::reverse_iterator rit = _route.rbegin();
603   for (; rit != _route.rend(); ++rit) {
604     if (!(*rit)->flag(WPT_ARRIVAL)) {
605       break;
606     }
607   }
608   
609   // erase() invalidates iterators, so grab now
610   WayptRef enroute;
611   WayptVec::iterator it;
612   
613   if (rit != _route.rend()) {
614     enroute = *rit;
615     it = rit.base(); // convert to fwd iterator
616   } else {
617     it = _route.begin();
618   }
619
620   _route.erase(it, _route.end());
621   
622   WayptVec wps;
623   buildArrival(enroute, wps);
624   for (it = wps.begin(); it != wps.end(); ++it) {
625     (*it)->setFlag(WPT_ARRIVAL);
626     (*it)->setFlag(WPT_GENERATED);
627   }
628   _route.insert(_route.end(), wps.begin(), wps.end());
629   
630   update_mirror();
631   waypointsChanged();
632 }
633
634 void FGRouteMgr::buildArrival(WayptRef enroute, WayptVec& wps)
635 {
636   if (!_destination) {
637     return;
638   }
639   
640   string runwayId(destination->getStringValue("runway"));
641   if (!_destination->hasRunwayWithIdent(runwayId)) {
642 // valid airport, but no runway selected, so just the airport node itself
643     wps.push_back(new NavaidWaypoint(_destination.get(), NULL));
644     return;
645   }
646   
647   FGRunway* r = _destination->getRunwayByIdent(runwayId);
648   string starId = destination->getStringValue("star");
649   STAR* star = _destination->findSTARWithIdent(starId);
650   if (!star) {
651 // valid runway, but no STAR selected/found, so just the runway node for now
652     wps.push_back(new RunwayWaypt(r, NULL));
653     return;
654   }
655   
656 // we have a valid STAR
657   string trans(destination->getStringValue("transition"));
658   WayptRef t = star->findTransitionByName(trans);
659   if (!t && enroute) {
660     t = star->findBestTransition(enroute->position());
661   }
662   
663   _destination->buildApproach(t, star, r, wps);
664 }
665
666 void FGRouteMgr::waypointsChanged()
667 {
668
669 }
670
671 void FGRouteMgr::insertWayptAtIndex(Waypt* aWpt, int aIndex)
672 {
673   if (!aWpt) {
674     return;
675   }
676   
677   int index = aIndex;
678   if ((aIndex == -1) || (aIndex > (int) _route.size())) {
679     index = _route.size();
680   }
681   
682   WayptVec::iterator it = _route.begin();
683   it += index;
684       
685   if (_currentIndex > index) {
686     ++_currentIndex;
687   }
688   
689   _route.insert(it, aWpt);
690   
691   update_mirror();
692   _edited->fireValueChanged();
693 }
694
695 WayptRef FGRouteMgr::waypointFromString(const string& tgt )
696 {
697   string target(boost::to_upper_copy(tgt));
698   WayptRef wpt;
699   
700 // extract altitude
701   double altFt = cruise->getDoubleValue("altitude-ft");
702   RouteRestriction altSetting = RESTRICT_NONE;
703     
704   size_t pos = target.find( '@' );
705   if ( pos != string::npos ) {
706     altFt = atof( target.c_str() + pos + 1 );
707     target = target.substr( 0, pos );
708     if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
709       altFt *= SG_METER_TO_FEET;
710     altSetting = RESTRICT_AT;
711   }
712
713 // check for lon,lat
714   pos = target.find( ',' );
715   if ( pos != string::npos ) {
716     double lon = atof( target.substr(0, pos).c_str());
717     double lat = atof( target.c_str() + pos + 1);
718     char buf[32];
719     char ew = (lon < 0.0) ? 'W' : 'E';
720     char ns = (lat < 0.0) ? 'S' : 'N';
721     snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
722     
723     wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
724     if (altSetting != RESTRICT_NONE) {
725       wpt->setAltitude(altFt, altSetting);
726     }
727     return wpt;
728   }
729
730   SGGeod basePosition;
731   if (_route.empty()) {
732     // route is empty, use current position
733     basePosition = SGGeod::fromDeg(lon->getDoubleValue(), lat->getDoubleValue());
734   } else {
735     basePosition = _route.back()->position();
736   }
737     
738   string_list pieces(simgear::strutils::split(target, "/"));
739   FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
740   if (!p) {
741     SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
742     return NULL;
743   }
744
745   if (pieces.size() == 1) {
746     wpt = new NavaidWaypoint(p, NULL);
747   } else if (pieces.size() == 3) {
748     // navaid/radial/distance-nm notation
749     double radial = atof(pieces[1].c_str()),
750       distanceNm = atof(pieces[2].c_str());
751     radial += magvar->getDoubleValue(); // convert to true bearing
752     wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
753   } else if (pieces.size() == 2) {
754     FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
755     if (!apt) {
756       SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
757       return NULL;
758     }
759     
760     if (!apt->hasRunwayWithIdent(pieces[1])) {
761       SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
762       return NULL;
763     }
764       
765     FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
766     wpt = new NavaidWaypoint(runway, NULL);
767   } else if (pieces.size() == 4) {
768     // navid/radial/navid/radial notation     
769     FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
770     if (!p2) {
771       SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
772       return NULL;
773     }
774
775     double r1 = atof(pieces[1].c_str()),
776       r2 = atof(pieces[3].c_str());
777     r1 += magvar->getDoubleValue();
778     r2 += magvar->getDoubleValue();
779     
780     SGGeod intersection;
781     bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
782     if (!ok) {
783       SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target);
784       return NULL;
785     }
786     
787     std::string name = p->ident() + "-" + p2->ident();
788     wpt = new BasicWaypt(intersection, name, NULL);
789   }
790   
791   if (!wpt) {
792     SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
793     return NULL;
794   }
795   
796   if (altSetting != RESTRICT_NONE) {
797     wpt->setAltitude(altFt, altSetting);
798   }
799   return wpt;
800 }
801
802 // mirror internal route to the property system for inspection by other subsystems
803 void FGRouteMgr::update_mirror()
804 {
805   mirror->removeChildren("wp");
806   
807   int num = numWaypts();
808   for (int i = 0; i < num; i++) {
809     Waypt* wp = _route[i];
810     SGPropertyNode *prop = mirror->getChild("wp", i, 1);
811
812     const SGGeod& pos(wp->position());
813     prop->setStringValue("id", wp->ident().c_str());
814     //prop->setStringValue("name", wp.get_name().c_str());
815     prop->setDoubleValue("longitude-deg", pos.getLongitudeDeg());
816     prop->setDoubleValue("latitude-deg",pos.getLatitudeDeg());
817    
818     // leg course+distance
819     if (i < (num - 1)) {
820       Waypt* next = _route[i+1];
821       std::pair<double, double> crsDist =
822         next->courseAndDistanceFrom(pos);
823       prop->setDoubleValue("leg-bearing-true-deg", crsDist.first);
824       prop->setDoubleValue("leg-distance-nm", crsDist.second * SG_METER_TO_NM);
825     }
826     
827     if (wp->altitudeRestriction() != RESTRICT_NONE) {
828       double ft = wp->altitudeFt();
829       prop->setDoubleValue("altitude-m", ft * SG_FEET_TO_METER);
830       prop->setDoubleValue("altitude-ft", ft);
831     } else {
832       prop->setDoubleValue("altitude-m", -9999.9);
833       prop->setDoubleValue("altitude-ft", -9999.9);
834     }
835     
836     if (wp->speedRestriction() == SPEED_RESTRICT_MACH) {
837       prop->setDoubleValue("speed-mach", wp->speedMach());
838     } else if (wp->speedRestriction() != RESTRICT_NONE) {
839       prop->setDoubleValue("speed-kts", wp->speedKts());
840     }
841     
842     if (wp->flag(WPT_ARRIVAL)) {
843       prop->setBoolValue("arrival", true);
844     }
845     
846     if (wp->flag(WPT_DEPARTURE)) {
847       prop->setBoolValue("departure", true);
848     }
849     
850     if (wp->flag(WPT_MISS)) {
851       prop->setBoolValue("missed-approach", true);
852     }
853     
854     prop->setBoolValue("generated", wp->flag(WPT_GENERATED));
855   } // of waypoint iteration
856   
857   // set number as listener attachment point
858   mirror->setIntValue("num", _route.size());
859     
860   NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
861   FGDialog* rmDlg = gui->getDialog("route-manager");
862   if (rmDlg) {
863     rmDlg->updateValues();
864   }
865 }
866
867 // command interface /autopilot/route-manager/input:
868 //
869 //   @CLEAR             ... clear route
870 //   @POP               ... remove first entry
871 //   @DELETE3           ... delete 4th entry
872 //   @INSERT2:KSFO@900  ... insert "KSFO@900" as 3rd entry
873 //   KSFO@900           ... append "KSFO@900"
874 //
875 void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
876 {
877     const char *s = prop->getStringValue();
878     if (strlen(s) == 0) {
879       return;
880     }
881     
882     if (!strcmp(s, "@CLEAR"))
883         mgr->clearRoute();
884     else if (!strcmp(s, "@ACTIVATE"))
885         mgr->activate();
886     else if (!strcmp(s, "@LOAD")) {
887       mgr->loadRoute();
888     } else if (!strcmp(s, "@SAVE")) {
889       mgr->saveRoute();
890     } else if (!strcmp(s, "@POP")) {
891       SG_LOG(SG_AUTOPILOT, SG_WARN, "route-manager @POP command is deprecated");
892     } else if (!strcmp(s, "@NEXT")) {
893       mgr->jumpToIndex(mgr->_currentIndex + 1);
894     } else if (!strcmp(s, "@PREVIOUS")) {
895       mgr->jumpToIndex(mgr->_currentIndex - 1);
896     } else if (!strncmp(s, "@JUMP", 5)) {
897       mgr->jumpToIndex(atoi(s + 5));
898     } else if (!strncmp(s, "@DELETE", 7))
899         mgr->removeWayptAtIndex(atoi(s + 7));
900     else if (!strncmp(s, "@INSERT", 7)) {
901         char *r;
902         int pos = strtol(s + 7, &r, 10);
903         if (*r++ != ':')
904             return;
905         while (isspace(*r))
906             r++;
907         if (*r)
908             mgr->insertWayptAtIndex(mgr->waypointFromString(r), pos);
909     } else if (!strncmp(s, "@ROUTE", 6)) {
910       char* r;
911       int endIndex = strtol(s + 6, &r, 10);
912       RouteType rt = (RouteType) mgr->_routingType->getIntValue();
913       mgr->routeToIndex(endIndex, rt);
914     } else if (!strcmp(s, "@AUTOROUTE")) {
915       mgr->autoRoute();
916     } else if (!strcmp(s, "@POSINIT")) {
917       mgr->initAtPosition();
918     } else
919       mgr->insertWayptAtIndex(mgr->waypointFromString(s), -1);
920 }
921
922 void FGRouteMgr::initAtPosition()
923 {
924   if (isRouteActive()) {
925     return; // don't mess with the active route
926   }
927   
928   if (haveUserWaypoints()) {
929     // user has already defined, loaded or entered a route, again
930     // don't interfere with it
931     return; 
932   }
933   
934   if (airborne->getBoolValue()) {
935     SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: airborne, clearing departure info");
936     _departure = NULL;
937     departure->setStringValue("runway", "");
938     return;
939   }
940   
941 // on the ground
942   SGGeod pos = SGGeod::fromDegFt(lon->getDoubleValue(), 
943     lat->getDoubleValue(), alt->getDoubleValue());
944   if (!_departure) {
945     _departure = FGAirport::findClosest(pos, 20.0);
946     if (!_departure) {
947       SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: couldn't find an airport within 20nm");
948       departure->setStringValue("runway", "");
949       return;
950     }
951   }
952   
953   std::string rwy = departure->getStringValue("runway");
954   if (!rwy.empty()) {
955     // runway already set, fine
956     return;
957   }
958   
959   FGRunway* r = _departure->findBestRunwayForPos(pos);
960   if (!r) {
961     return;
962   }
963   
964   departure->setStringValue("runway", r->ident().c_str());
965   SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: starting at " 
966     << _departure->ident() << " on runway " << r->ident());
967 }
968
969 bool FGRouteMgr::haveUserWaypoints() const
970 {
971   for (int i = 0; i < numWaypts(); i++) {
972     if (!_route[i]->flag(WPT_GENERATED)) {
973       // have a non-generated waypoint, we're done
974       return true;
975     }
976   }
977   
978   // all waypoints are generated
979   return false;
980 }
981
982 bool FGRouteMgr::activate()
983 {
984   if (isRouteActive()) {
985     SG_LOG(SG_AUTOPILOT, SG_WARN, "duplicate route-activation, no-op");
986     return false;
987   }
988  
989   _currentIndex = 0;
990   currentWaypointChanged();
991   
992  /* double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
993   totalDistance->setDoubleValue(routeDistanceNm);
994   double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
995   if (cruiseSpeedKts > 1.0) {
996     // very very crude approximation, doesn't allow for climb / descent
997     // performance or anything else at all
998     ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
999   }
1000   */
1001   active->setBoolValue(true);
1002   SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
1003   return true;
1004 }
1005
1006
1007 void FGRouteMgr::sequence()
1008 {
1009   if (!active->getBoolValue()) {
1010     SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
1011     return;
1012   }
1013   
1014   if (checkFinished()) {
1015     return;
1016   }
1017   
1018   _currentIndex++;
1019   currentWaypointChanged();
1020 }
1021
1022 bool FGRouteMgr::checkFinished()
1023 {
1024   if (_currentIndex < (int) _route.size()) {
1025     return false;
1026   }
1027   
1028   SG_LOG(SG_AUTOPILOT, SG_INFO, "reached end of active route");
1029   _finished->fireValueChanged();
1030   active->setBoolValue(false);
1031   return true;
1032 }
1033
1034 void FGRouteMgr::jumpToIndex(int index)
1035 {
1036   if ((index < 0) || (index >= (int) _route.size())) {
1037     SG_LOG(SG_AUTOPILOT, SG_ALERT, "passed invalid index (" << 
1038       index << ") to FGRouteMgr::jumpToIndex");
1039     return;
1040   }
1041
1042   if (_currentIndex == index) {
1043     return; // no-op
1044   }
1045   
1046 // all the checks out the way, go ahead and update state
1047   _currentIndex = index;
1048   currentWaypointChanged();
1049   _currentWpt->fireValueChanged();
1050 }
1051
1052 void FGRouteMgr::currentWaypointChanged()
1053 {
1054   Waypt* cur = currentWaypt();
1055   Waypt* next = nextWaypt();
1056
1057   wp0->getChild("id")->setStringValue(cur ? cur->ident() : "");
1058   wp1->getChild("id")->setStringValue(next ? next->ident() : "");
1059   
1060   _currentWpt->fireValueChanged();
1061   SG_LOG(SG_AUTOPILOT, SG_INFO, "route manager, current-wp is now " << _currentIndex);
1062 }
1063
1064 int FGRouteMgr::findWayptIndex(const SGGeod& aPos) const
1065 {  
1066   for (int i=0; i<numWaypts(); ++i) {
1067     if (_route[i]->matches(aPos)) {
1068       return i;
1069     }
1070   }
1071   
1072   return -1;
1073 }
1074
1075 Waypt* FGRouteMgr::currentWaypt() const
1076 {
1077   if ((_currentIndex < 0) || (_currentIndex >= numWaypts()))
1078       return NULL;
1079   return wayptAtIndex(_currentIndex);
1080 }
1081
1082 Waypt* FGRouteMgr::previousWaypt() const
1083 {
1084   if (_currentIndex == 0) {
1085     return NULL;
1086   }
1087   
1088   return wayptAtIndex(_currentIndex - 1);
1089 }
1090
1091 Waypt* FGRouteMgr::nextWaypt() const
1092 {
1093   if ((_currentIndex < 0) || ((_currentIndex + 1) >= numWaypts())) {
1094     return NULL;
1095   }
1096   
1097   return wayptAtIndex(_currentIndex + 1);
1098 }
1099
1100 Waypt* FGRouteMgr::wayptAtIndex(int index) const
1101 {
1102   if ((index < 0) || (index >= numWaypts())) {
1103     throw sg_range_exception("waypt index out of range", "FGRouteMgr::wayptAtIndex");
1104   }
1105   
1106   return _route[index];
1107 }
1108
1109 void FGRouteMgr::saveRoute()
1110 {
1111   SGPath path(_pathNode->getStringValue());
1112   SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
1113   try {
1114     SGPropertyNode_ptr d(new SGPropertyNode);
1115     SGPath path(_pathNode->getStringValue());
1116     d->setIntValue("version", 2);
1117     
1118     if (_departure) {
1119       d->setStringValue("departure/airport", _departure->ident());
1120       d->setStringValue("departure/sid", departure->getStringValue("sid"));
1121       d->setStringValue("departure/runway", departure->getStringValue("runway"));
1122     }
1123     
1124     if (_destination) {
1125       d->setStringValue("destination/airport", _destination->ident());
1126       d->setStringValue("destination/star", destination->getStringValue("star"));
1127       d->setStringValue("destination/transition", destination->getStringValue("transition"));
1128       d->setStringValue("destination/runway", destination->getStringValue("runway"));
1129     }
1130     
1131   // route nodes
1132     SGPropertyNode* routeNode = d->getChild("route", 0, true);
1133     for (unsigned int i=0; i<_route.size(); ++i) {
1134       Waypt* wpt = _route[i];
1135       wpt->saveAsNode(routeNode->getChild("wp", i, true));
1136     } // of waypoint iteration
1137     writeProperties(path.str(), d, true /* write-all */);
1138   } catch (sg_exception& e) {
1139     SG_LOG(SG_IO, SG_WARN, "failed to save flight-plan:" << e.getMessage());
1140   }
1141 }
1142
1143 void FGRouteMgr::loadRoute()
1144 {
1145   // deactivate route first
1146   active->setBoolValue(false);
1147   
1148   SGPropertyNode_ptr routeData(new SGPropertyNode);
1149   SGPath path(_pathNode->getStringValue());
1150   
1151   SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
1152     
1153   try {
1154     readProperties(path.str(), routeData);
1155   } catch (sg_exception& ) {
1156     // if XML parsing fails, the file might be simple textual list of waypoints
1157     loadPlainTextRoute(path);
1158     return;
1159   }
1160   
1161   try {
1162     int version = routeData->getIntValue("version", 1);
1163     if (version == 1) {
1164       loadVersion1XMLRoute(routeData);
1165     } else if (version == 2) {
1166       loadVersion2XMLRoute(routeData);
1167     } else {
1168       throw sg_io_exception("unsupported XML route version");
1169     }
1170   } catch (sg_exception& e) {
1171     SG_LOG(SG_IO, SG_WARN, "failed to load flight-plan (from '" << e.getOrigin()
1172       << "'):" << e.getMessage());
1173   }
1174 }
1175
1176 void FGRouteMgr::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
1177 {
1178   // departure nodes
1179   SGPropertyNode* dep = routeData->getChild("departure");
1180   if (dep) {
1181     string depIdent = dep->getStringValue("airport");
1182     _departure = (FGAirport*) fgFindAirportID(depIdent);
1183     departure->setStringValue("runway", dep->getStringValue("runway"));
1184     departure->setStringValue("sid", dep->getStringValue("sid"));
1185     departure->setStringValue("transition", dep->getStringValue("transition"));
1186   }
1187   
1188 // destination
1189   SGPropertyNode* dst = routeData->getChild("destination");
1190   if (dst) {
1191     _destination = (FGAirport*) fgFindAirportID(dst->getStringValue("airport"));
1192     destination->setStringValue("runway", dst->getStringValue("runway"));
1193     destination->setStringValue("star", dst->getStringValue("star"));
1194     destination->setStringValue("transition", dst->getStringValue("transition"));
1195   }
1196
1197 // alternate
1198   SGPropertyNode* alt = routeData->getChild("alternate");
1199   if (alt) {
1200     alternate->setStringValue(alt->getStringValue("airport"));
1201   } // of cruise data loading
1202   
1203 // cruise
1204   SGPropertyNode* crs = routeData->getChild("cruise");
1205   if (crs) {
1206     cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
1207     cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
1208     cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
1209   } // of cruise data loading
1210
1211 }
1212
1213 void FGRouteMgr::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
1214 {
1215   loadXMLRouteHeader(routeData);
1216   
1217 // route nodes
1218   WayptVec wpts;
1219   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
1220   for (int i=0; i<routeNode->nChildren(); ++i) {
1221     SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
1222     WayptRef wpt = Waypt::createFromProperties(NULL, wpNode);
1223     wpts.push_back(wpt);
1224   } // of route iteration
1225   
1226   _route = wpts;
1227 }
1228
1229 void FGRouteMgr::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
1230 {
1231   loadXMLRouteHeader(routeData);
1232
1233 // route nodes
1234   WayptVec wpts;
1235   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
1236   for (int i=0; i<routeNode->nChildren(); ++i) {
1237     SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
1238     WayptRef wpt = parseVersion1XMLWaypt(wpNode);
1239     wpts.push_back(wpt);
1240   } // of route iteration
1241   
1242   _route = wpts;
1243 }
1244
1245 WayptRef FGRouteMgr::parseVersion1XMLWaypt(SGPropertyNode* aWP)
1246 {
1247   SGGeod lastPos;
1248   if (!_route.empty()) {
1249     lastPos = _route.back()->position();
1250   } else if (_departure) {
1251     lastPos = _departure->geod();
1252   }
1253
1254   WayptRef w;
1255   string ident(aWP->getStringValue("ident"));
1256   if (aWP->hasChild("longitude-deg")) {
1257     // explicit longitude/latitude
1258     w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"), 
1259       aWP->getDoubleValue("latitude-deg")), ident, NULL);
1260     
1261   } else {
1262     string nid = aWP->getStringValue("navid", ident.c_str());
1263     FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
1264     if (!p) {
1265       throw sg_io_exception("bad route file, unknown navid:" + nid);
1266     }
1267       
1268     SGGeod pos(p->geod());
1269     if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
1270       double radialDeg = aWP->getDoubleValue("offset-radial");
1271       // convert magnetic radial to a true radial!
1272       radialDeg += magvar->getDoubleValue();
1273       double offsetNm = aWP->getDoubleValue("offset-nm");
1274       double az2;
1275       SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
1276     }
1277
1278     w = new BasicWaypt(pos, ident, NULL);
1279   }
1280   
1281   double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
1282   if (altFt > -9990.0) {
1283     w->setAltitude(altFt, RESTRICT_AT);
1284   }
1285
1286   return w;
1287 }
1288
1289 void FGRouteMgr::loadPlainTextRoute(const SGPath& path)
1290 {
1291   sg_gzifstream in(path.str().c_str());
1292   if (!in.is_open()) {
1293     return;
1294   }
1295   
1296   try {
1297     WayptVec wpts;
1298     while (!in.eof()) {
1299       string line;
1300       getline(in, line, '\n');
1301     // trim CR from end of line, if found
1302       if (line[line.size() - 1] == '\r') {
1303         line.erase(line.size() - 1, 1);
1304       }
1305       
1306       line = simgear::strutils::strip(line);
1307       if (line.empty() || (line[0] == '#')) {
1308         continue; // ignore empty/comment lines
1309       }
1310       
1311       WayptRef w = waypointFromString(line);
1312       if (!w) {
1313         throw sg_io_exception("failed to create waypoint from line:" + line);
1314       }
1315       
1316       wpts.push_back(w);
1317     } // of line iteration
1318   
1319     _route = wpts;
1320   } catch (sg_exception& e) {
1321     SG_LOG(SG_IO, SG_WARN, "failed to load route from:" << path.str() << ":" << e.getMessage());
1322   }
1323 }
1324
1325 const char* FGRouteMgr::getDepartureICAO() const
1326 {
1327   if (!_departure) {
1328     return "";
1329   }
1330   
1331   return _departure->ident().c_str();
1332 }
1333
1334 const char* FGRouteMgr::getDepartureName() const
1335 {
1336   if (!_departure) {
1337     return "";
1338   }
1339   
1340   return _departure->name().c_str();
1341 }
1342
1343 void FGRouteMgr::setDepartureICAO(const char* aIdent)
1344 {
1345   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
1346     _departure = NULL;
1347   } else {
1348     _departure = FGAirport::findByIdent(aIdent);
1349   }
1350   
1351   departureChanged();
1352 }
1353
1354 const char* FGRouteMgr::getDestinationICAO() const
1355 {
1356   if (!_destination) {
1357     return "";
1358   }
1359   
1360   return _destination->ident().c_str();
1361 }
1362
1363 const char* FGRouteMgr::getDestinationName() const
1364 {
1365   if (!_destination) {
1366     return "";
1367   }
1368   
1369   return _destination->name().c_str();
1370 }
1371
1372 void FGRouteMgr::setDestinationICAO(const char* aIdent)
1373 {
1374   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
1375     _destination = NULL;
1376   } else {
1377     _destination = FGAirport::findByIdent(aIdent);
1378   }
1379   
1380   arrivalChanged();
1381 }