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