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