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