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