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