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