]> git.mxchange.org Git - flightgear.git/blob - src/Autopilot/route_mgr.cxx
Merge branch 'jmt/runway-fix' into next
[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
40 #include <simgear/misc/strutils.hxx>
41 #include <simgear/structure/exception.hxx>
42
43 #include <simgear/props/props_io.hxx>
44 #include <simgear/misc/sg_path.hxx>
45 #include <simgear/route/route.hxx>
46 #include <simgear/sg_inlines.h>
47
48 #include "Main/fg_props.hxx"
49 #include "Navaids/positioned.hxx"
50 #include "Airports/simple.hxx"
51 #include "Airports/runways.hxx"
52
53 #include "FDM/flight.hxx" // for getting ground speed
54
55 #define RM "/autopilot/route-manager/"
56
57 static double get_ground_speed() {
58   // starts in ft/s so we convert to kts
59   static const SGPropertyNode * speedup_node = fgGetNode("/sim/speed-up");
60
61   double ft_s = cur_fdm_state->get_V_ground_speed()
62       * speedup_node->getIntValue();
63   double kts = ft_s * SG_FEET_TO_METER * 3600 * SG_METER_TO_NM;
64   return kts;
65 }
66
67 FGRouteMgr::FGRouteMgr() :
68     _route( new SGRoute ),
69     input(fgGetNode( RM "input", true )),
70     mirror(fgGetNode( RM "route", true ))
71 {
72     listener = new InputListener(this);
73     input->setStringValue("");
74     input->addChangeListener(listener);
75 }
76
77
78 FGRouteMgr::~FGRouteMgr() {
79     input->removeChangeListener(listener);
80     
81     delete listener;
82     delete _route;
83 }
84
85
86 void FGRouteMgr::init() {
87   SGPropertyNode_ptr rm(fgGetNode(RM));
88   
89   lon = fgGetNode( "/position/longitude-deg", true );
90   lat = fgGetNode( "/position/latitude-deg", true );
91   alt = fgGetNode( "/position/altitude-ft", true );
92   magvar = fgGetNode("/environment/magnetic-variation-deg", true);
93      
94   departure = fgGetNode(RM "departure", true);
95   departure->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
96     &FGRouteMgr::getDepartureICAO, &FGRouteMgr::setDepartureICAO));
97   departure->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
98     &FGRouteMgr::getDepartureName, NULL));
99     
100 // init departure information from current location
101   SGGeod pos = SGGeod::fromDegFt(lon->getDoubleValue(), lat->getDoubleValue(), alt->getDoubleValue());
102   _departure = FGAirport::findClosest(pos, 20.0);
103   if (_departure) {
104     FGRunway* active = _departure->getActiveRunwayForUsage();
105     departure->setStringValue("runway", active->ident().c_str());
106   } else {
107     departure->setStringValue("runway", "");
108   }
109   
110   departure->getChild("etd", 0, true);
111   departure->getChild("takeoff-time", 0, true);
112
113   destination = fgGetNode(RM "destination", true);
114   destination->getChild("airport", 0, true);
115   
116   destination->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
117     &FGRouteMgr::getDestinationICAO, &FGRouteMgr::setDestinationICAO));
118   destination->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
119     &FGRouteMgr::getDestinationName, NULL));
120     
121   destination->getChild("runway", 0, true);
122   destination->getChild("eta", 0, true);
123   destination->getChild("touchdown-time", 0, true);
124   
125   alternate = fgGetNode(RM "alternate", true);
126   alternate->getChild("airport", 0, true);
127   alternate->getChild("runway", 0, true);
128   
129   cruise = fgGetNode(RM "cruise", true);
130   cruise->getChild("altitude-ft", 0, true);
131   cruise->setDoubleValue("altitude-ft", 10000.0);
132   cruise->getChild("flight-level", 0, true);
133   cruise->getChild("speed-kts", 0, true);
134   cruise->setDoubleValue("speed-kts", 160.0);
135   
136   totalDistance = fgGetNode(RM "total-distance", true);
137   totalDistance->setDoubleValue(0.0);
138   
139   ete = fgGetNode(RM "ete", true);
140   ete->setDoubleValue(0.0);
141   
142   elapsedFlightTime = fgGetNode(RM "flight-time", true);
143   elapsedFlightTime->setDoubleValue(0.0);
144   
145   active = fgGetNode(RM "active", true);
146   active->setBoolValue(false);
147   
148   airborne = fgGetNode(RM "airborne", true);
149   airborne->setBoolValue(false);
150     
151   _edited = fgGetNode(RM "signals/edited", true);
152   _finished = fgGetNode(RM "signals/finished", true);
153   
154   _currentWpt = fgGetNode(RM "current-wp", true);
155   _currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
156     (*this, &FGRouteMgr::currentWaypoint, &FGRouteMgr::jumpToIndex));
157       
158   // temporary distance / eta calculations, for backward-compatability
159   wp0 = fgGetNode(RM "wp", 0, true);
160   wp0->getChild("id", 0, true);
161   wp0->getChild("dist", 0, true);
162   wp0->getChild("eta", 0, true);
163   wp0->getChild("bearing-deg", 0, true);
164   
165   wp1 = fgGetNode(RM "wp", 1, true);
166   wp1->getChild("id", 0, true);
167   wp1->getChild("dist", 0, true);
168   wp1->getChild("eta", 0, true);
169   
170   wpn = fgGetNode(RM "wp-last", 0, true);
171   wpn->getChild("dist", 0, true);
172   wpn->getChild("eta", 0, true);
173   
174   _route->clear();
175   _route->set_current(0);
176   update_mirror();
177   
178   _pathNode = fgGetNode(RM "file-path", 0, true);
179 }
180
181
182 void FGRouteMgr::postinit() {
183     string_list *waypoints = globals->get_initial_waypoints();
184     if (waypoints) {
185       vector<string>::iterator it;
186       for (it = waypoints->begin(); it != waypoints->end(); ++it)
187         new_waypoint(*it);
188     }
189
190     weightOnWheels = fgGetNode("/gear/gear[0]/wow", false);
191     // check airbone flag agrees with presets
192     
193 }
194
195
196 void FGRouteMgr::bind() { }
197 void FGRouteMgr::unbind() { }
198
199 bool FGRouteMgr::isRouteActive() const
200 {
201   return active->getBoolValue();
202 }
203
204 void FGRouteMgr::update( double dt ) {
205     if (dt <= 0.0) {
206       // paused, nothing to do here
207       return;
208     }
209   
210     if (!active->getBoolValue()) {
211       return;
212     }
213     
214     double groundSpeed = get_ground_speed();
215     if (airborne->getBoolValue()) {
216       time_t now = time(NULL);
217       elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
218     } else { // not airborne
219       if (weightOnWheels->getBoolValue() || (groundSpeed < 40)) {
220         return;
221       }
222       
223       airborne->setBoolValue(true);
224       _takeoffTime = time(NULL); // start the clock
225       departure->setIntValue("takeoff-time", _takeoffTime);
226     }
227     
228   // basic course/distance information
229     double wp_course, wp_distance;
230     SGWayPoint wp = _route->get_current();
231     wp.CourseAndDistance( lon->getDoubleValue(), lat->getDoubleValue(),
232                           alt->getDoubleValue(), &wp_course, &wp_distance );
233
234   // update wp0 / wp1 / wp-last for legacy users
235     wp0->setDoubleValue("dist", wp_distance * SG_METER_TO_NM);
236     wp_course -= magvar->getDoubleValue(); // expose magnetic bearing
237     wp0->setDoubleValue("bearing-deg", wp_course);
238     setETAPropertyFromDistance(wp0->getChild("eta"), wp_distance);
239     
240     if ((_route->current_index() + 1) < _route->size()) {
241       wp = _route->get_waypoint(_route->current_index() + 1);
242       double wp1_course, wp1_distance;
243       wp.CourseAndDistance(lon->getDoubleValue(), lat->getDoubleValue(),
244                           alt->getDoubleValue(), &wp1_course, &wp1_distance);
245     
246       wp1->setDoubleValue("dist", wp1_distance * SG_METER_TO_NM);
247       setETAPropertyFromDistance(wp1->getChild("eta"), wp1_distance);
248     }
249     
250     double totalDistanceRemaining = wp_distance; // distance to current waypoint
251     for (int i=_route->current_index() + 1; i<_route->size(); ++i) {
252       totalDistanceRemaining += _route->get_waypoint(i).get_distance();
253     }
254     
255     wpn->setDoubleValue("dist", totalDistanceRemaining * SG_METER_TO_NM);
256     ete->setDoubleValue(totalDistanceRemaining * SG_METER_TO_NM / groundSpeed * 3600.0);
257     setETAPropertyFromDistance(wpn->getChild("eta"), totalDistanceRemaining);
258     
259     // get time now at destination tz as tm struct
260     // add ete seconds
261     // convert to string ... and stash in property
262     //destination->setDoubleValue("eta", eta);
263 }
264
265
266 void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDistance) {
267     double speed = get_ground_speed();
268     if (speed < 1.0) {
269       aProp->setStringValue("--:--");
270       return;
271     }
272   
273     char eta_str[64];
274     double eta = aDistance * SG_METER_TO_NM / get_ground_speed();
275     if ( eta >= 100.0 ) { 
276         eta = 99.999; // clamp
277     }
278     
279     if ( eta < (1.0/6.0) ) {
280       eta *= 60.0; // within 10 minutes, bump up to min/secs
281     }
282     
283     int major = (int)eta, 
284         minor = (int)((eta - (int)eta) * 60.0);
285     snprintf( eta_str, 64, "%d:%02d", major, minor );
286     aProp->setStringValue( eta_str );
287 }
288
289 void FGRouteMgr::add_waypoint( const SGWayPoint& wp, int n )
290 {
291   _route->add_waypoint( wp, n );
292     
293   if ((n >= 0) && (_route->current_index() > n)) {
294     _route->set_current(_route->current_index() + 1);
295   }
296   
297   waypointsChanged();
298 }
299
300 void FGRouteMgr::waypointsChanged()
301 {
302   double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
303   totalDistance->setDoubleValue(routeDistanceNm);
304   double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
305   if (cruiseSpeedKts > 1.0) {
306     // very very crude approximation, doesn't allow for climb / descent
307     // performance or anything else at all
308     ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
309   }
310
311   update_mirror();
312   _edited->fireValueChanged();
313   checkFinished();
314 }
315
316 SGWayPoint FGRouteMgr::pop_waypoint( int n ) {
317   if ( _route->size() <= 0 ) {
318     return SGWayPoint();
319   }
320   
321   if ( n < 0 ) {
322     n = _route->size() - 1;
323   }
324   
325   if (_route->current_index() > n) {
326     _route->set_current(_route->current_index() - 1);
327   }
328
329   SGWayPoint wp = _route->get_waypoint(n);
330   _route->delete_waypoint(n);
331     
332   waypointsChanged();
333   return wp;
334 }
335
336
337 bool FGRouteMgr::build() {
338     return true;
339 }
340
341
342 void FGRouteMgr::new_waypoint( const string& target, int n ) {
343     SGWayPoint* wp = make_waypoint( target );
344     if (!wp) {
345         return;
346     }
347     
348     add_waypoint( *wp, n );
349     delete wp;
350 }
351
352
353 SGWayPoint* FGRouteMgr::make_waypoint(const string& tgt ) {
354     string target(boost::to_upper_copy(tgt));
355     
356     
357     double alt = -9999.0;
358     // extract altitude
359     size_t pos = target.find( '@' );
360     if ( pos != string::npos ) {
361         alt = atof( target.c_str() + pos + 1 );
362         target = target.substr( 0, pos );
363         if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
364             alt *= SG_FEET_TO_METER;
365     }
366
367     // check for lon,lat
368     pos = target.find( ',' );
369     if ( pos != string::npos ) {
370         double lon = atof( target.substr(0, pos).c_str());
371         double lat = atof( target.c_str() + pos + 1);
372         char buf[32];
373         char ew = (lon < 0.0) ? 'W' : 'E';
374         char ns = (lat < 0.0) ? 'S' : 'N';
375         snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
376         return new SGWayPoint( lon, lat, alt, SGWayPoint::WGS84, buf);
377     }    
378
379     SGGeod basePosition;
380     if (_route->size() > 0) {
381         SGWayPoint wp = get_waypoint(_route->size()-1);
382         basePosition = wp.get_target();
383     } else {
384         // route is empty, use current position
385         basePosition = SGGeod::fromDeg(
386             fgGetNode("/position/longitude-deg")->getDoubleValue(), 
387             fgGetNode("/position/latitude-deg")->getDoubleValue());
388     }
389     
390     vector<string> pieces(simgear::strutils::split(target, "/"));
391
392
393     FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
394     if (!p) {
395       SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
396       return NULL;
397     }
398     
399     SGGeod geod = SGGeod::fromGeodM(p->geod(), alt);
400     if (pieces.size() == 1) {
401       // simple case
402       return new SGWayPoint(geod, target, p->name());
403     }
404         
405     if (pieces.size() == 3) {
406       // navaid/radial/distance-nm notation
407       double radial = atof(pieces[1].c_str()),
408         distanceNm = atof(pieces[2].c_str()),
409         az2;
410       radial += magvar->getDoubleValue(); // convert to true bearing
411       SGGeod offsetPos;
412       SGGeodesy::direct(geod, radial, distanceNm * SG_NM_TO_METER, offsetPos, az2);
413       offsetPos.setElevationM(alt);
414       
415       SG_LOG(SG_AUTOPILOT, SG_INFO, "final offset radial is " << radial);
416       return new SGWayPoint(offsetPos, p->ident() + pieces[2], target);
417     }
418     
419     if (pieces.size() == 2) {
420       FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
421       if (!apt) {
422         SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
423         return NULL;
424       }
425       
426       if (!apt->hasRunwayWithIdent(pieces[1])) {
427         SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
428         return NULL;
429       }
430       
431       FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
432       SGGeod t = runway->threshold();
433       return new SGWayPoint(t.getLongitudeDeg(), t.getLatitudeDeg(), alt, SGWayPoint::WGS84, pieces[1]);
434     }
435     
436     SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
437     return NULL;
438 }
439
440
441 // mirror internal route to the property system for inspection by other subsystems
442 void FGRouteMgr::update_mirror() {
443     mirror->removeChildren("wp");
444     for (int i = 0; i < _route->size(); i++) {
445         SGWayPoint wp = _route->get_waypoint(i);
446         SGPropertyNode *prop = mirror->getChild("wp", i, 1);
447
448         const SGGeod& pos(wp.get_target());
449         prop->setStringValue("id", wp.get_id().c_str());
450         prop->setStringValue("name", wp.get_name().c_str());
451         prop->setDoubleValue("longitude-deg", pos.getLongitudeDeg());
452         prop->setDoubleValue("latitude-deg",pos.getLatitudeDeg());
453         prop->setDoubleValue("altitude-m", pos.getElevationM());
454         prop->setDoubleValue("altitude-ft", pos.getElevationFt());
455     }
456     // set number as listener attachment point
457     mirror->setIntValue("num", _route->size());
458 }
459
460 // command interface /autopilot/route-manager/input:
461 //
462 //   @CLEAR             ... clear route
463 //   @POP               ... remove first entry
464 //   @DELETE3           ... delete 4th entry
465 //   @INSERT2:KSFO@900  ... insert "KSFO@900" as 3rd entry
466 //   KSFO@900           ... append "KSFO@900"
467 //
468 void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
469 {
470     const char *s = prop->getStringValue();
471     if (strlen(s) == 0) {
472       return;
473     }
474     
475     if (!strcmp(s, "@CLEAR"))
476         mgr->init();
477     else if (!strcmp(s, "@ACTIVATE"))
478         mgr->activate();
479     else if (!strcmp(s, "@LOAD")) {
480       mgr->loadRoute();
481     } else if (!strcmp(s, "@SAVE")) {
482       mgr->saveRoute();
483     } else if (!strcmp(s, "@POP")) {
484       SG_LOG(SG_AUTOPILOT, SG_WARN, "route-manager @POP command is deprecated");
485     } else if (!strcmp(s, "@NEXT")) {
486       mgr->jumpToIndex(mgr->currentWaypoint() + 1);
487     } else if (!strcmp(s, "@PREVIOUS")) {
488       mgr->jumpToIndex(mgr->currentWaypoint() - 1);
489     } else if (!strncmp(s, "@JUMP", 5)) {
490       mgr->jumpToIndex(atoi(s + 5));
491     } else if (!strncmp(s, "@DELETE", 7))
492         mgr->pop_waypoint(atoi(s + 7));
493     else if (!strncmp(s, "@INSERT", 7)) {
494         char *r;
495         int pos = strtol(s + 7, &r, 10);
496         if (*r++ != ':')
497             return;
498         while (isspace(*r))
499             r++;
500         if (*r)
501             mgr->new_waypoint(r, pos);
502     } else
503         mgr->new_waypoint(s);
504 }
505
506 //    SGWayPoint( const double lon = 0.0, const double lat = 0.0,
507 //              const double alt = 0.0, const modetype m = WGS84,
508 //              const string& s = "", const string& n = "" );
509
510 bool FGRouteMgr::activate()
511 {
512   if (isRouteActive()) {
513     SG_LOG(SG_AUTOPILOT, SG_WARN, "duplicate route-activation, no-op");
514     return false;
515   }
516
517   // only add departure waypoint if we're not airborne, so that
518   // in-air route activation doesn't confuse matters.
519   if (weightOnWheels->getBoolValue() && _departure) {
520     string runwayId(departure->getStringValue("runway"));
521     FGRunway* runway = NULL;
522     if (_departure->hasRunwayWithIdent(runwayId)) {
523       runway = _departure->getRunwayByIdent(runwayId);
524     } else {
525       SG_LOG(SG_AUTOPILOT, SG_INFO, 
526         "route-manager, departure runway not found:" << runwayId);
527       runway = _departure->getActiveRunwayForUsage();
528     }
529     
530     SGWayPoint swp(runway->threshold(), 
531       _departure->ident() + "-" + runway->ident(), runway->name());
532     add_waypoint(swp, 0);
533   }
534   
535   if (_destination) {
536     string runwayId = (destination->getStringValue("runway"));
537     if (_destination->hasRunwayWithIdent(runwayId)) {
538       FGRunway* runway = _destination->getRunwayByIdent(runwayId);
539       SGWayPoint swp(runway->end(), 
540         _destination->ident() + "-" + runway->ident(), runway->name());
541       add_waypoint(swp);
542     } else {
543       // quite likely, since destination runway may not be known until enroute
544       // probably want a listener on the 'destination' node to allow an enroute
545       // update
546       add_waypoint(SGWayPoint(_destination->geod(), _destination->ident(), _destination->name()));
547     }
548   }
549
550   _route->set_current(0);
551   active->setBoolValue(true);
552   SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
553   return true;
554 }
555
556
557 void FGRouteMgr::sequence()
558 {
559   if (!active->getBoolValue()) {
560     SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
561     return;
562   }
563   
564   if (checkFinished()) {
565     return;
566   }
567   
568   _route->increment_current();
569   currentWaypointChanged();
570   _currentWpt->fireValueChanged();
571 }
572
573 bool FGRouteMgr::checkFinished()
574 {
575   int lastWayptIndex = _route->size() - 1;
576   if (_route->current_index() < lastWayptIndex) {
577     return false;
578   }
579   
580   SG_LOG(SG_AUTOPILOT, SG_INFO, "reached end of active route");
581   _finished->fireValueChanged();
582   active->setBoolValue(false);
583   return true;
584 }
585
586 void FGRouteMgr::jumpToIndex(int index)
587 {
588   if ((index < 0) || (index >= _route->size())) {
589     SG_LOG(SG_AUTOPILOT, SG_ALERT, "passed invalid index (" << 
590       index << ") to FGRouteMgr::jumpToIndex");
591     return;
592   }
593
594   if (_route->current_index() == index) {
595     return; // no-op
596   }
597   
598   _route->set_current(index);
599   currentWaypointChanged();
600   _currentWpt->fireValueChanged();
601 }
602
603 void FGRouteMgr::currentWaypointChanged()
604 {
605   SGWayPoint previous = _route->get_previous();
606   SGWayPoint cur = _route->get_current();
607   
608   wp0->getChild("id")->setStringValue(cur.get_id());
609   if ((_route->current_index() + 1) < _route->size()) {
610     wp1->getChild("id")->setStringValue(_route->get_next().get_id());
611   } else {
612     wp1->getChild("id")->setStringValue("");
613   }
614   
615   SG_LOG(SG_AUTOPILOT, SG_INFO, "route manager, current-wp is now " << _route->current_index());
616 }
617
618 int FGRouteMgr::findWaypoint(const SGGeod& aPos) const
619 {  
620   for (int i=0; i<_route->size(); ++i) {
621     double d = SGGeodesy::distanceM(aPos, _route->get_waypoint(i).get_target());
622     if (d < 200.0) { // 200 metres seems close enough
623       return i;
624     }
625   }
626   
627   return -1;
628 }
629
630 SGWayPoint FGRouteMgr::get_waypoint( int i ) const
631 {
632   return _route->get_waypoint(i);
633 }
634
635 int FGRouteMgr::size() const
636 {
637   return _route->size();
638 }
639
640 int FGRouteMgr::currentWaypoint() const
641 {
642   return _route->current_index();
643 }
644
645 void FGRouteMgr::setWaypointTargetAltitudeFt(unsigned int index, int altFt)
646 {
647   SGWayPoint wp = _route->get_waypoint(index);
648   wp.setTargetAltFt(altFt);
649   // simplest way to update a waypoint is to remove and re-add it
650   _route->delete_waypoint(index);
651   _route->add_waypoint(wp, index);
652   waypointsChanged();
653 }
654
655 void FGRouteMgr::saveRoute()
656 {
657   SGPath path(_pathNode->getStringValue());
658   SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
659   try {
660     writeProperties(path.str(), mirror, false, SGPropertyNode::ARCHIVE);
661   } catch (const sg_exception &e) {
662     SG_LOG(SG_IO, SG_WARN, "Error saving route:" << e.getMessage());
663     //guiErrorMessage("Error writing autosave.xml: ", e);
664   }
665 }
666
667 void FGRouteMgr::loadRoute()
668 {
669   try {
670     // deactivate route first
671     active->setBoolValue(false);
672     
673     SGPropertyNode_ptr routeData(new SGPropertyNode);
674     SGPath path(_pathNode->getStringValue());
675     
676     SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
677     readProperties(path.str(), routeData);
678     
679   // departure nodes
680     SGPropertyNode* dep = routeData->getChild("departure");
681     if (!dep) {
682       throw sg_io_exception("malformed route file, no departure node");
683     }
684     
685     string depIdent = dep->getStringValue("airport");
686     _departure = (FGAirport*) fgFindAirportID(depIdent);
687
688         
689   // destination
690     SGPropertyNode* dst = routeData->getChild("destination");
691     if (!dst) {
692       throw sg_io_exception("malformed route file, no destination node");
693     }
694     
695     _destination = (FGAirport*) fgFindAirportID(dst->getStringValue("airport"));
696     destination->setStringValue("runway", dst->getStringValue("runway"));
697
698   // alternate
699     SGPropertyNode* alt = routeData->getChild("alternate");
700     if (alt) {
701       alternate->setStringValue(alt->getStringValue("airport"));
702     } // of cruise data loading
703     
704   // cruise
705     SGPropertyNode* crs = routeData->getChild("cruise");
706     if (crs) {
707       cruise->setDoubleValue(crs->getDoubleValue("speed"));
708     } // of cruise data loading
709
710   // route nodes
711     _route->clear();
712     SGPropertyNode_ptr _route = routeData->getChild("route", 0);
713     SGGeod lastPos = (_departure ? _departure->geod() : SGGeod());
714     
715     for (int i=0; i<_route->nChildren(); ++i) {
716       SGPropertyNode_ptr wp = _route->getChild("wp", i);
717       parseRouteWaypoint(wp);
718     } // of route iteration
719   } catch (sg_exception& e) {
720     SG_LOG(SG_IO, SG_WARN, "failed to load flight-plan (from '" << e.getOrigin()
721       << "'):" << e.getMessage());
722   }
723 }
724
725 void FGRouteMgr::parseRouteWaypoint(SGPropertyNode* aWP)
726 {
727   SGGeod lastPos;
728   if (_route->size() > 0) {
729     lastPos = get_waypoint(_route->size()-1).get_target();
730   } else {
731     // route is empty, use departure airport position
732     const FGAirport* apt = fgFindAirportID(departure->getStringValue("airport"));
733     assert(apt); // shouldn't have got this far with an invalid airport
734     lastPos = apt->geod();
735   }
736
737   SGPropertyNode_ptr altProp = aWP->getChild("altitude-ft");
738   double altM = cruise->getDoubleValue("altitude-ft") * SG_FEET_TO_METER;
739   if (altProp) {
740     altM = altProp->getDoubleValue() * SG_FEET_TO_METER;
741   }
742       
743   string ident(aWP->getStringValue("ident"));
744   if (aWP->hasChild("longitude-deg")) {
745     // explicit longitude/latitude
746     SGWayPoint swp(aWP->getDoubleValue("longitude-deg"),
747       aWP->getDoubleValue("latitude-deg"), altM, 
748       SGWayPoint::WGS84, ident, aWP->getStringValue("name"));
749     add_waypoint(swp);
750   } else if (aWP->hasChild("navid")) {
751     // lookup by navid (possibly with offset)
752     string nid(aWP->getStringValue("navid"));
753     FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
754     if (!p) {
755       throw sg_io_exception("bad route file, unknown navid:" + nid);
756     }
757     
758     SGGeod pos(p->geod());
759     if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
760       double radialDeg = aWP->getDoubleValue("offset-radial");
761       // convert magnetic radial to a true radial!
762       radialDeg += magvar->getDoubleValue();
763       double offsetNm = aWP->getDoubleValue("offset-nm");
764       double az2;
765       SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
766     }
767     
768     SGWayPoint swp(pos.getLongitudeDeg(), pos.getLatitudeDeg(), altM, 
769       SGWayPoint::WGS84, ident, "");
770     add_waypoint(swp);
771   } else {
772     // lookup by ident (symbolic waypoint)
773     FGPositionedRef p = FGPositioned::findClosestWithIdent(ident, lastPos);
774     if (!p) {
775       throw sg_io_exception("bad route file, unknown waypoint:" + ident);
776     }
777     
778     SGWayPoint swp(p->longitude(), p->latitude(), altM, 
779       SGWayPoint::WGS84, p->ident(), p->name());
780     add_waypoint(swp);
781   }
782 }
783
784 const char* FGRouteMgr::getDepartureICAO() const
785 {
786   if (!_departure) {
787     return "";
788   }
789   
790   return _departure->ident().c_str();
791 }
792
793 const char* FGRouteMgr::getDepartureName() const
794 {
795   if (!_departure) {
796     return "";
797   }
798   
799   return _departure->name().c_str();
800 }
801
802 void FGRouteMgr::setDepartureICAO(const char* aIdent)
803 {
804   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
805     _departure = NULL;
806   } else {
807     _departure = FGAirport::findByIdent(aIdent);
808   }
809 }
810
811 const char* FGRouteMgr::getDestinationICAO() const
812 {
813   if (!_destination) {
814     return "";
815   }
816   
817   return _destination->ident().c_str();
818 }
819
820 const char* FGRouteMgr::getDestinationName() const
821 {
822   if (!_destination) {
823     return "";
824   }
825   
826   return _destination->name().c_str();
827 }
828
829 void FGRouteMgr::setDestinationICAO(const char* aIdent)
830 {
831   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
832     _destination = NULL;
833   } else {
834     _destination = FGAirport::findByIdent(aIdent);
835   }
836 }
837