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