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