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