]> git.mxchange.org Git - flightgear.git/blob - src/Autopilot/route_mgr.cxx
Fix route manager exception and following crash (may fix #177)
[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 #include <boost/tuple/tuple.hpp>
40
41 #include <simgear/misc/strutils.hxx>
42 #include <simgear/structure/exception.hxx>
43 #include <simgear/misc/sgstream.hxx>
44
45 #include <simgear/props/props_io.hxx>
46 #include <simgear/misc/sg_path.hxx>
47 #include <simgear/route/route.hxx>
48 #include <simgear/sg_inlines.h>
49
50 #include "Main/fg_props.hxx"
51 #include "Navaids/positioned.hxx"
52 #include <Navaids/waypoint.hxx>
53 #include <Navaids/airways.hxx>
54 #include <Navaids/procedure.hxx>
55 #include "Airports/simple.hxx"
56 #include "Airports/runways.hxx"
57
58 #define RM "/autopilot/route-manager/"
59
60 #include <GUI/new_gui.hxx>
61 #include <GUI/dialog.hxx>
62
63 using namespace flightgear;
64
65 class PropertyWatcher : public SGPropertyChangeListener
66 {
67 public:
68   void watch(SGPropertyNode* p)
69   {
70     p->addChangeListener(this, false);
71   }
72
73   virtual void valueChanged(SGPropertyNode*)
74   {
75     fire();
76   }
77 protected:
78   virtual void fire() = 0;
79 };
80
81 /**
82  * Template adapter, created by convenience helper below
83  */
84 template <class T>
85 class MethodPropertyWatcher : public PropertyWatcher
86 {
87 public:
88   typedef void (T::*fire_method)();
89
90   MethodPropertyWatcher(T* obj, fire_method m) :
91     _object(obj),
92     _method(m)
93   { ; }
94   
95 protected:
96   virtual void fire()
97   { // dispatch to the object method we're helping
98     (_object->*_method)();
99   }
100   
101 private:
102   T* _object;
103   fire_method _method;
104 };
105
106 template <class T>
107 PropertyWatcher* createWatcher(T* obj, void (T::*m)())
108 {
109   return new MethodPropertyWatcher<T>(obj, m);
110 }
111
112 FGRouteMgr::FGRouteMgr() :
113   _currentIndex(0),
114   input(fgGetNode( RM "input", true )),
115   mirror(fgGetNode( RM "route", true ))
116 {
117   listener = new InputListener(this);
118   input->setStringValue("");
119   input->addChangeListener(listener);
120 }
121
122
123 FGRouteMgr::~FGRouteMgr()
124 {
125   input->removeChangeListener(listener);
126   delete listener;
127 }
128
129
130 void FGRouteMgr::init() {
131   SGPropertyNode_ptr rm(fgGetNode(RM));
132   
133   lon = fgGetNode( "/position/longitude-deg", true );
134   lat = fgGetNode( "/position/latitude-deg", true );
135   alt = fgGetNode( "/position/altitude-ft", true );
136   magvar = fgGetNode("/environment/magnetic-variation-deg", true);
137      
138   departure = fgGetNode(RM "departure", true);
139   departure->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
140     &FGRouteMgr::getDepartureICAO, &FGRouteMgr::setDepartureICAO));
141   departure->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
142     &FGRouteMgr::getDepartureName, NULL));
143   departure->setStringValue("runway", "");
144   
145   _departureWatcher = createWatcher(this, &FGRouteMgr::departureChanged);
146   _departureWatcher->watch(departure->getChild("runway"));
147   
148   departure->getChild("etd", 0, true);
149   _departureWatcher->watch(departure->getChild("sid", 0, true));
150   departure->getChild("takeoff-time", 0, true);
151
152   destination = fgGetNode(RM "destination", true);
153   destination->getChild("airport", 0, true);
154   
155   destination->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
156     &FGRouteMgr::getDestinationICAO, &FGRouteMgr::setDestinationICAO));
157   destination->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
158     &FGRouteMgr::getDestinationName, NULL));
159   
160   _arrivalWatcher = createWatcher(this, &FGRouteMgr::arrivalChanged);
161   _arrivalWatcher->watch(destination->getChild("runway", 0, true));
162   
163   destination->getChild("eta", 0, true);
164   _arrivalWatcher->watch(destination->getChild("star", 0, true));
165   _arrivalWatcher->watch(destination->getChild("transition", 0, true));
166   destination->getChild("touchdown-time", 0, true);
167
168   alternate = fgGetNode(RM "alternate", true);
169   alternate->getChild("airport", 0, true);
170   alternate->getChild("runway", 0, true);
171   
172   cruise = fgGetNode(RM "cruise", true);
173   cruise->getChild("altitude-ft", 0, true);
174   cruise->setDoubleValue("altitude-ft", 10000.0);
175   cruise->getChild("flight-level", 0, true);
176   cruise->getChild("speed-kts", 0, true);
177   cruise->setDoubleValue("speed-kts", 160.0);
178   
179   _routingType = cruise->getChild("routing", 0, true);
180   _routingType->setIntValue(ROUTE_HIGH_AIRWAYS);
181   
182   totalDistance = fgGetNode(RM "total-distance", true);
183   totalDistance->setDoubleValue(0.0);
184   
185   ete = fgGetNode(RM "ete", true);
186   ete->setDoubleValue(0.0);
187   
188   elapsedFlightTime = fgGetNode(RM "flight-time", true);
189   elapsedFlightTime->setDoubleValue(0.0);
190   
191   active = fgGetNode(RM "active", true);
192   active->setBoolValue(false);
193   
194   airborne = fgGetNode(RM "airborne", true);
195   airborne->setBoolValue(false);
196     
197   _edited = fgGetNode(RM "signals/edited", true);
198   _finished = fgGetNode(RM "signals/finished", true);
199   
200   _currentWpt = fgGetNode(RM "current-wp", true);
201   _currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
202     (*this, &FGRouteMgr::currentIndex, &FGRouteMgr::jumpToIndex));
203       
204   // temporary distance / eta calculations, for backward-compatability
205   wp0 = fgGetNode(RM "wp", 0, true);
206   wp0->getChild("id", 0, true);
207   wp0->getChild("dist", 0, true);
208   wp0->getChild("eta", 0, true);
209   wp0->getChild("bearing-deg", 0, true);
210   
211   wp1 = fgGetNode(RM "wp", 1, true);
212   wp1->getChild("id", 0, true);
213   wp1->getChild("dist", 0, true);
214   wp1->getChild("eta", 0, true);
215   
216   wpn = fgGetNode(RM "wp-last", 0, true);
217   wpn->getChild("dist", 0, true);
218   wpn->getChild("eta", 0, true);
219   
220   update_mirror();
221   _pathNode = fgGetNode(RM "file-path", 0, true);
222 }
223
224
225 void FGRouteMgr::postinit()
226 {
227   string_list *waypoints = globals->get_initial_waypoints();
228   if (waypoints) {
229     string_list::iterator it;
230     for (it = waypoints->begin(); it != waypoints->end(); ++it) {
231       WayptRef w = waypointFromString(*it);
232       if (w) {
233         _route.push_back(w);
234       }
235     }
236     
237     SG_LOG(SG_AUTOPILOT, SG_INFO, "loaded initial waypoints:" << _route.size());
238   }
239
240   weightOnWheels = fgGetNode("/gear/gear[0]/wow", true);
241   // check airbone flag agrees with presets
242 }
243
244 void FGRouteMgr::bind() { }
245 void FGRouteMgr::unbind() { }
246
247 bool FGRouteMgr::isRouteActive() const
248 {
249   return active->getBoolValue();
250 }
251
252 void FGRouteMgr::update( double dt )
253 {
254   if (dt <= 0.0) {
255     return; // paused, nothing to do here
256   }
257   
258   double groundSpeed = fgGetDouble("/velocities/groundspeed-kt", 0.0);
259   if (airborne->getBoolValue()) {
260     time_t now = time(NULL);
261     elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
262   } else { // not airborne
263     if (weightOnWheels->getBoolValue() || (groundSpeed < 40)) {
264       return;
265     }
266     
267     airborne->setBoolValue(true);
268     _takeoffTime = time(NULL); // start the clock
269     departure->setIntValue("takeoff-time", _takeoffTime);
270   }
271   
272   if (!active->getBoolValue()) {
273     return;
274   }
275
276 // basic course/distance information
277   SGGeod currentPos = SGGeod::fromDegFt(lon->getDoubleValue(), 
278     lat->getDoubleValue(),alt->getDoubleValue());
279
280   Waypt* curWpt = currentWaypt();
281   if (!curWpt) {
282     return;
283   }
284   
285   double courseDeg;
286   double distanceM;
287   boost::tie(courseDeg, distanceM) = curWpt->courseAndDistanceFrom(currentPos);
288   
289 // update wp0 / wp1 / wp-last for legacy users
290   wp0->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
291   courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
292   wp0->setDoubleValue("bearing-deg", courseDeg);
293   setETAPropertyFromDistance(wp0->getChild("eta"), distanceM);
294   
295   double totalDistanceRemaining = distanceM; // distance to current waypoint
296   
297   Waypt* nextWpt = nextWaypt();
298   if (nextWpt) {
299     boost::tie(courseDeg, distanceM) = nextWpt->courseAndDistanceFrom(currentPos);
300      
301     wp1->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
302     courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
303     wp1->setDoubleValue("bearing-deg", courseDeg);
304     setETAPropertyFromDistance(wp1->getChild("eta"), distanceM);
305   }
306   
307   Waypt* prev = curWpt;
308   for (unsigned int i=_currentIndex + 1; i<_route.size(); ++i) {
309     Waypt* w = _route[i];
310     if (w->flag(WPT_DYNAMIC)) continue;
311     totalDistanceRemaining += SGGeodesy::distanceM(prev->position(), w->position());
312     prev = w;
313   }
314   
315   wpn->setDoubleValue("dist", totalDistanceRemaining * SG_METER_TO_NM);
316   ete->setDoubleValue(totalDistanceRemaining * SG_METER_TO_NM / groundSpeed * 3600.0);
317   setETAPropertyFromDistance(wpn->getChild("eta"), totalDistanceRemaining);
318 }
319
320 void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDistance)
321 {
322   double speed = fgGetDouble("/velocities/groundspeed-kt", 0.0);
323   if (speed < 1.0) {
324     aProp->setStringValue("--:--");
325     return;
326   }
327
328   char eta_str[64];
329   double eta = aDistance * SG_METER_TO_NM / speed;
330   if ( eta >= 100.0 ) { 
331       eta = 99.999; // clamp
332   }
333   
334   if ( eta < (1.0/6.0) ) {
335     eta *= 60.0; // within 10 minutes, bump up to min/secs
336   }
337   
338   int major = (int)eta, 
339       minor = (int)((eta - (int)eta) * 60.0);
340   snprintf( eta_str, 64, "%d:%02d", major, minor );
341   aProp->setStringValue( eta_str );
342 }
343
344 flightgear::WayptRef FGRouteMgr::removeWayptAtIndex(int aIndex)
345 {
346   int index = aIndex;
347   if (aIndex < 0) { // negative indices count the the end
348     index = _route.size() -  index;
349   }
350   
351   if ((index < 0) || (index >= numWaypts())) {
352     SG_LOG(SG_AUTOPILOT, SG_WARN, "removeWayptAtIndex with invalid index:" << aIndex);
353     return NULL;
354   }
355   
356   if (_currentIndex > index) {
357     --_currentIndex; // shift current index down if necessary
358   }
359
360   WayptVec::iterator it = _route.begin();
361   it += index;
362   
363   WayptRef w = *it; // hold a ref now, in case _route is the only other owner
364   _route.erase(it);
365   
366   update_mirror();
367   _edited->fireValueChanged();
368   checkFinished();
369   
370   return w;
371 }
372   
373 void FGRouteMgr::clearRoute()
374 {
375   _route.clear();
376   _currentIndex = -1;
377   
378   update_mirror();
379   active->setBoolValue(false);
380   _edited->fireValueChanged();
381 }
382
383 /**
384  * route between index-1 and index, using airways.
385  */
386 bool FGRouteMgr::routeToIndex(int index, RouteType aRouteType)
387 {
388   WayptRef wp1;
389   WayptRef wp2;
390   
391   if (index == -1) {
392     index = _route.size(); // can still be zero, of course
393   }
394   
395   if (index == 0) {
396     if (!_departure) {
397       SG_LOG(SG_AUTOPILOT, SG_WARN, "routeToIndex: no departure set");
398       return false;
399     }
400     
401     wp1 = new NavaidWaypoint(_departure.get(), NULL);
402   } else {
403     wp1 = wayptAtIndex(index - 1);
404   }
405   
406   if (index >= numWaypts()) {
407     if (!_destination) {
408       SG_LOG(SG_AUTOPILOT, SG_WARN, "routeToIndex: no destination set");
409       return false;
410     }
411     
412     wp2 = new NavaidWaypoint(_destination.get(), NULL);
413   } else {
414     wp2 = wayptAtIndex(index);
415   }
416   
417   double distNm = SGGeodesy::distanceNm(wp1->position(), wp2->position());
418   if (distNm < 100.0) {
419     SG_LOG(SG_AUTOPILOT, SG_INFO, "routeToIndex: existing waypoints are nearby, direct route");
420     return true;
421   }
422   
423   WayptVec r;
424   switch (aRouteType) {
425   case ROUTE_HIGH_AIRWAYS:
426     Airway::highLevel()->route(wp1, wp2, r);
427     break;
428     
429   case ROUTE_LOW_AIRWAYS:
430     Airway::lowLevel()->route(wp1, wp2, r);
431     break;
432     
433   case ROUTE_VOR:
434     throw sg_exception("VOR routing not supported yet");
435   }
436   
437   if (r.empty()) {
438     SG_LOG(SG_AUTOPILOT, SG_INFO, "routeToIndex: no route found");
439     return false;
440   }
441
442   WayptVec::iterator it = _route.begin();
443   it += index;
444   _route.insert(it, r.begin(), r.end());
445
446   update_mirror();
447   _edited->fireValueChanged();
448   return true;
449 }
450
451 void FGRouteMgr::autoRoute()
452 {
453   if (!_departure || !_destination) {
454     return;
455   }
456   
457   string runwayId(departure->getStringValue("runway"));
458   FGRunway* runway = NULL;
459   if (_departure->hasRunwayWithIdent(runwayId)) {
460     runway = _departure->getRunwayByIdent(runwayId);
461   }
462   
463   FGRunway* dstRunway = NULL;
464   runwayId = destination->getStringValue("runway");
465   if (_destination->hasRunwayWithIdent(runwayId)) {
466     dstRunway = _destination->getRunwayByIdent(runwayId);
467   }
468     
469   _route.clear(); // clear out the existing, first
470 // SID
471   flightgear::SID* sid;
472   WayptRef sidTrans;
473   
474   boost::tie(sid, sidTrans) = _departure->selectSID(_destination->geod(), runway);
475   if (sid) { 
476     SG_LOG(SG_AUTOPILOT, SG_INFO, "selected SID " << sid->ident());
477     if (sidTrans) {
478       SG_LOG(SG_AUTOPILOT, SG_INFO, "\tvia " << sidTrans->ident() << " transition");
479     }
480     
481     sid->route(runway, sidTrans, _route);
482     departure->setStringValue("sid", sid->ident());
483   } else {
484     // use airport location for airway search
485     sidTrans = new NavaidWaypoint(_departure.get(), NULL);
486     departure->setStringValue("sid", "");
487   }
488   
489 // STAR
490   destination->setStringValue("transition", "");
491   destination->setStringValue("star", "");
492   
493   STAR* star;
494   WayptRef starTrans;
495   boost::tie(star, starTrans) = _destination->selectSTAR(_departure->geod(), dstRunway);
496   if (star) {
497     SG_LOG(SG_AUTOPILOT, SG_INFO, "selected STAR " << star->ident());
498     if (starTrans) {
499       SG_LOG(SG_AUTOPILOT, SG_INFO, "\tvia " << starTrans->ident() << " transition");
500       destination->setStringValue("transition", starTrans->ident());
501     }    
502     destination->setStringValue("star", star->ident());
503   } else {
504     // use airport location for search
505     starTrans = new NavaidWaypoint(_destination.get(), NULL);
506   }
507   
508 // route between them
509   WayptVec airwayRoute;
510   if (Airway::highLevel()->route(sidTrans, starTrans, airwayRoute)) {
511     _route.insert(_route.end(), airwayRoute.begin(), airwayRoute.end());
512   }
513   
514 // add the STAR if we have one
515   if (star) {
516     _destination->buildApproach(starTrans, star, dstRunway, _route);
517   }
518
519   update_mirror();
520   _edited->fireValueChanged();
521 }
522
523 void FGRouteMgr::departureChanged()
524 {
525 // remove existing departure waypoints
526   WayptVec::iterator it = _route.begin();
527   for (; it != _route.end(); ++it) {
528     if (!(*it)->flag(WPT_DEPARTURE)) {
529       break;
530     }
531   }
532   
533   // erase() invalidates iterators, so grab now
534   WayptRef enroute;
535   if (it == _route.end()) {
536     if (_destination) {
537       enroute = new NavaidWaypoint(_destination.get(), NULL);
538     }
539   } else {
540     enroute = *it;
541   }
542
543   _route.erase(_route.begin(), it);
544   if (!_departure) {
545     waypointsChanged();
546     return;
547   }
548   
549   WayptVec wps;
550   buildDeparture(enroute, wps);
551   for (it = wps.begin(); it != wps.end(); ++it) {
552     (*it)->setFlag(WPT_DEPARTURE);
553     (*it)->setFlag(WPT_GENERATED);
554   }
555   _route.insert(_route.begin(), wps.begin(), wps.end());
556   
557   update_mirror();
558   waypointsChanged();
559 }
560
561 void FGRouteMgr::buildDeparture(WayptRef enroute, WayptVec& wps)
562 {
563   string runwayId(departure->getStringValue("runway"));
564   if (!_departure->hasRunwayWithIdent(runwayId)) {
565 // valid airport, but no runway selected, so just the airport noide itself
566     wps.push_back(new NavaidWaypoint(_departure.get(), NULL));
567     return;
568   }
569   
570   FGRunway* r = _departure->getRunwayByIdent(runwayId);
571   string sidId = departure->getStringValue("sid");
572   flightgear::SID* sid = _departure->findSIDWithIdent(sidId);
573   if (!sid) {
574 // valid runway, but no SID selected/found, so just the runway node for now
575     if (!sidId.empty() && (sidId != "(none)")) {
576       SG_LOG(SG_AUTOPILOT, SG_INFO, "SID not found:" << sidId);
577     }
578     
579     wps.push_back(new RunwayWaypt(r, NULL));
580     return;
581   }
582   
583 // we have a valid SID, awesome
584   string trans(departure->getStringValue("transition"));
585   WayptRef t = sid->findTransitionByName(trans);
586   if (!t && enroute) {
587     t = sid->findBestTransition(enroute->position());
588   }
589
590   sid->route(r, t, wps);
591   if (!wps.empty() && wps.front()->flag(WPT_DYNAMIC)) {
592     // ensure first waypoint is static, to simplify other computations
593     wps.insert(wps.begin(), new RunwayWaypt(r, NULL));
594   }
595 }
596
597 void FGRouteMgr::arrivalChanged()
598 {  
599   // remove existing arrival waypoints
600   WayptVec::reverse_iterator rit = _route.rbegin();
601   for (; rit != _route.rend(); ++rit) {
602     if (!(*rit)->flag(WPT_ARRIVAL)) {
603       break;
604     }
605   }
606   
607   // erase() invalidates iterators, so grab now
608   WayptRef enroute;
609   WayptVec::iterator it;
610   
611   if (rit != _route.rend()) {
612     enroute = *rit;
613     it = rit.base(); // convert to fwd iterator
614   } else {
615     it = _route.begin();
616   }
617
618   _route.erase(it, _route.end());
619   
620   WayptVec wps;
621   buildArrival(enroute, wps);
622   for (it = wps.begin(); it != wps.end(); ++it) {
623     (*it)->setFlag(WPT_ARRIVAL);
624     (*it)->setFlag(WPT_GENERATED);
625   }
626   _route.insert(_route.end(), wps.begin(), wps.end());
627   
628   update_mirror();
629   waypointsChanged();
630 }
631
632 void FGRouteMgr::buildArrival(WayptRef enroute, WayptVec& wps)
633 {
634   if (!_destination) {
635     return;
636   }
637   
638   string runwayId(destination->getStringValue("runway"));
639   if (!_destination->hasRunwayWithIdent(runwayId)) {
640 // valid airport, but no runway selected, so just the airport node itself
641     wps.push_back(new NavaidWaypoint(_destination.get(), NULL));
642     return;
643   }
644   
645   FGRunway* r = _destination->getRunwayByIdent(runwayId);
646   string starId = destination->getStringValue("star");
647   STAR* star = _destination->findSTARWithIdent(starId);
648   if (!star) {
649 // valid runway, but no STAR selected/found, so just the runway node for now
650     wps.push_back(new RunwayWaypt(r, NULL));
651     return;
652   }
653   
654 // we have a valid STAR
655   string trans(destination->getStringValue("transition"));
656   WayptRef t = star->findTransitionByName(trans);
657   if (!t && enroute) {
658     t = star->findBestTransition(enroute->position());
659   }
660   
661   _destination->buildApproach(t, star, r, wps);
662 }
663
664 void FGRouteMgr::waypointsChanged()
665 {
666
667 }
668
669 void FGRouteMgr::insertWayptAtIndex(Waypt* aWpt, int aIndex)
670 {
671   if (!aWpt) {
672     return;
673   }
674   
675   int index = aIndex;
676   if ((aIndex == -1) || (aIndex > (int) _route.size())) {
677     index = _route.size();
678   }
679   
680   WayptVec::iterator it = _route.begin();
681   it += index;
682       
683   if (_currentIndex > index) {
684     ++_currentIndex;
685   }
686   
687   _route.insert(it, aWpt);
688   
689   update_mirror();
690   _edited->fireValueChanged();
691 }
692
693 WayptRef FGRouteMgr::waypointFromString(const string& tgt )
694 {
695   string target(boost::to_upper_copy(tgt));
696   WayptRef wpt;
697   
698 // extract altitude
699   double altFt = cruise->getDoubleValue("altitude-ft");
700   RouteRestriction altSetting = RESTRICT_NONE;
701     
702   size_t pos = target.find( '@' );
703   if ( pos != string::npos ) {
704     altFt = atof( target.c_str() + pos + 1 );
705     target = target.substr( 0, pos );
706     if ( !strcmp(fgGetString("/sim/startup/units"), "meter") )
707       altFt *= SG_METER_TO_FEET;
708     altSetting = RESTRICT_AT;
709   }
710
711 // check for lon,lat
712   pos = target.find( ',' );
713   if ( pos != string::npos ) {
714     double lon = atof( target.substr(0, pos).c_str());
715     double lat = atof( target.c_str() + pos + 1);
716     char buf[32];
717     char ew = (lon < 0.0) ? 'W' : 'E';
718     char ns = (lat < 0.0) ? 'S' : 'N';
719     snprintf(buf, 32, "%c%03d%c%03d", ew, (int) fabs(lon), ns, (int)fabs(lat));
720     
721     wpt = new BasicWaypt(SGGeod::fromDeg(lon, lat), buf, NULL);
722     if (altSetting != RESTRICT_NONE) {
723       wpt->setAltitude(altFt, altSetting);
724     }
725     return wpt;
726   }
727
728   SGGeod basePosition;
729   if (_route.empty()) {
730     // route is empty, use current position
731     basePosition = SGGeod::fromDeg(lon->getDoubleValue(), lat->getDoubleValue());
732   } else {
733     basePosition = _route.back()->position();
734   }
735     
736   string_list pieces(simgear::strutils::split(target, "/"));
737   FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), basePosition);
738   if (!p) {
739     SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
740     return NULL;
741   }
742
743   if (pieces.size() == 1) {
744     wpt = new NavaidWaypoint(p, NULL);
745   } else if (pieces.size() == 3) {
746     // navaid/radial/distance-nm notation
747     double radial = atof(pieces[1].c_str()),
748       distanceNm = atof(pieces[2].c_str());
749     radial += magvar->getDoubleValue(); // convert to true bearing
750     wpt = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
751   } else if (pieces.size() == 2) {
752     FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
753     if (!apt) {
754       SG_LOG(SG_AUTOPILOT, SG_INFO, "Waypoint is not an airport:" << pieces.front());
755       return NULL;
756     }
757     
758     if (!apt->hasRunwayWithIdent(pieces[1])) {
759       SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
760       return NULL;
761     }
762       
763     FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
764     wpt = new NavaidWaypoint(runway, NULL);
765   } else if (pieces.size() == 4) {
766     // navid/radial/navid/radial notation     
767     FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
768     if (!p2) {
769       SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
770       return NULL;
771     }
772
773     double r1 = atof(pieces[1].c_str()),
774       r2 = atof(pieces[3].c_str());
775     r1 += magvar->getDoubleValue();
776     r2 += magvar->getDoubleValue();
777     
778     SGGeod intersection;
779     bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
780     if (!ok) {
781       SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << target);
782       return NULL;
783     }
784     
785     std::string name = p->ident() + "-" + p2->ident();
786     wpt = new BasicWaypt(intersection, name, NULL);
787   }
788   
789   if (!wpt) {
790     SG_LOG(SG_AUTOPILOT, SG_INFO, "Unable to parse waypoint:" << target);
791     return NULL;
792   }
793   
794   if (altSetting != RESTRICT_NONE) {
795     wpt->setAltitude(altFt, altSetting);
796   }
797   return wpt;
798 }
799
800 // mirror internal route to the property system for inspection by other subsystems
801 void FGRouteMgr::update_mirror()
802 {
803   mirror->removeChildren("wp");
804   for (int i = 0; i < numWaypts(); i++) {
805     Waypt* wp = _route[i];
806     SGPropertyNode *prop = mirror->getChild("wp", i, 1);
807
808     const SGGeod& pos(wp->position());
809     prop->setStringValue("id", wp->ident().c_str());
810     //prop->setStringValue("name", wp.get_name().c_str());
811     prop->setDoubleValue("longitude-deg", pos.getLongitudeDeg());
812     prop->setDoubleValue("latitude-deg",pos.getLatitudeDeg());
813    
814     if (wp->altitudeRestriction() != RESTRICT_NONE) {
815       double ft = wp->altitudeFt();
816       prop->setDoubleValue("altitude-m", ft * SG_FEET_TO_METER);
817       prop->setDoubleValue("altitude-ft", ft);
818     } else {
819       prop->setDoubleValue("altitude-m", -9999.9);
820       prop->setDoubleValue("altitude-ft", -9999.9);
821     }
822     
823     if (wp->speedRestriction() != RESTRICT_NONE) {
824       prop->setDoubleValue("speed-kts", wp->speedKts());
825     }
826     
827     if (wp->flag(WPT_ARRIVAL)) {
828       prop->setBoolValue("arrival", true);
829     }
830     
831     if (wp->flag(WPT_DEPARTURE)) {
832       prop->setBoolValue("departure", true);
833     }
834     
835     if (wp->flag(WPT_MISS)) {
836       prop->setBoolValue("missed-approach", true);
837     }
838     
839     prop->setBoolValue("generated", wp->flag(WPT_GENERATED));
840   } // of waypoint iteration
841   
842   // set number as listener attachment point
843   mirror->setIntValue("num", _route.size());
844     
845   NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
846   FGDialog* rmDlg = gui->getDialog("route-manager");
847   if (rmDlg) {
848     rmDlg->updateValues();
849   }
850 }
851
852 // command interface /autopilot/route-manager/input:
853 //
854 //   @CLEAR             ... clear route
855 //   @POP               ... remove first entry
856 //   @DELETE3           ... delete 4th entry
857 //   @INSERT2:KSFO@900  ... insert "KSFO@900" as 3rd entry
858 //   KSFO@900           ... append "KSFO@900"
859 //
860 void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
861 {
862     const char *s = prop->getStringValue();
863     if (strlen(s) == 0) {
864       return;
865     }
866     
867     if (!strcmp(s, "@CLEAR"))
868         mgr->clearRoute();
869     else if (!strcmp(s, "@ACTIVATE"))
870         mgr->activate();
871     else if (!strcmp(s, "@LOAD")) {
872       mgr->loadRoute();
873     } else if (!strcmp(s, "@SAVE")) {
874       mgr->saveRoute();
875     } else if (!strcmp(s, "@POP")) {
876       SG_LOG(SG_AUTOPILOT, SG_WARN, "route-manager @POP command is deprecated");
877     } else if (!strcmp(s, "@NEXT")) {
878       mgr->jumpToIndex(mgr->_currentIndex + 1);
879     } else if (!strcmp(s, "@PREVIOUS")) {
880       mgr->jumpToIndex(mgr->_currentIndex - 1);
881     } else if (!strncmp(s, "@JUMP", 5)) {
882       mgr->jumpToIndex(atoi(s + 5));
883     } else if (!strncmp(s, "@DELETE", 7))
884         mgr->removeWayptAtIndex(atoi(s + 7));
885     else if (!strncmp(s, "@INSERT", 7)) {
886         char *r;
887         int pos = strtol(s + 7, &r, 10);
888         if (*r++ != ':')
889             return;
890         while (isspace(*r))
891             r++;
892         if (*r)
893             mgr->insertWayptAtIndex(mgr->waypointFromString(r), pos);
894     } else if (!strncmp(s, "@ROUTE", 6)) {
895       char* r;
896       int endIndex = strtol(s + 6, &r, 10);
897       RouteType rt = (RouteType) mgr->_routingType->getIntValue();
898       mgr->routeToIndex(endIndex, rt);
899     } else if (!strcmp(s, "@AUTOROUTE")) {
900       mgr->autoRoute();
901     } else if (!strcmp(s, "@POSINIT")) {
902       mgr->initAtPosition();
903     } else
904       mgr->insertWayptAtIndex(mgr->waypointFromString(s), -1);
905 }
906
907 void FGRouteMgr::initAtPosition()
908 {
909   if (isRouteActive()) {
910     return; // don't mess with the active route
911   }
912   
913   if (haveUserWaypoints()) {
914     // user has already defined, loaded or entered a route, again
915     // don't interfere with it
916     return; 
917   }
918   
919   if (airborne->getBoolValue()) {
920     SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: airborne, clearing departure info");
921     _departure = NULL;
922     departure->setStringValue("runway", "");
923     return;
924   }
925   
926 // on the ground
927   SGGeod pos = SGGeod::fromDegFt(lon->getDoubleValue(), lat->getDoubleValue(), alt->getDoubleValue());
928   _departure = FGAirport::findClosest(pos, 20.0);
929   if (!_departure) {
930     SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: couldn't find an airport within 20nm");
931     departure->setStringValue("runway", "");
932     return;
933   }
934   
935   FGRunway* r = _departure->findBestRunwayForPos(pos);
936   if (!r) {
937     return;
938   }
939   
940   departure->setStringValue("runway", r->ident().c_str());
941   SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: starting at " 
942     << _departure->ident() << " on runway " << r->ident());
943 }
944
945 bool FGRouteMgr::haveUserWaypoints() const
946 {
947   for (int i = 0; i < numWaypts(); i++) {
948     if (!_route[i]->flag(WPT_GENERATED)) {
949       // have a non-generated waypoint, we're done
950       return true;
951     }
952   }
953   
954   // all waypoints are generated
955   return false;
956 }
957
958 bool FGRouteMgr::activate()
959 {
960   if (isRouteActive()) {
961     SG_LOG(SG_AUTOPILOT, SG_WARN, "duplicate route-activation, no-op");
962     return false;
963   }
964  
965   _currentIndex = 0;
966   currentWaypointChanged();
967   
968  /* double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
969   totalDistance->setDoubleValue(routeDistanceNm);
970   double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
971   if (cruiseSpeedKts > 1.0) {
972     // very very crude approximation, doesn't allow for climb / descent
973     // performance or anything else at all
974     ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
975   }
976   */
977   active->setBoolValue(true);
978   SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
979   return true;
980 }
981
982
983 void FGRouteMgr::sequence()
984 {
985   if (!active->getBoolValue()) {
986     SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
987     return;
988   }
989   
990   if (checkFinished()) {
991     return;
992   }
993   
994   _currentIndex++;
995   currentWaypointChanged();
996 }
997
998 bool FGRouteMgr::checkFinished()
999 {
1000   if (_currentIndex < (int) _route.size()) {
1001     return false;
1002   }
1003   
1004   SG_LOG(SG_AUTOPILOT, SG_INFO, "reached end of active route");
1005   _finished->fireValueChanged();
1006   active->setBoolValue(false);
1007   return true;
1008 }
1009
1010 void FGRouteMgr::jumpToIndex(int index)
1011 {
1012   if ((index < 0) || (index >= (int) _route.size())) {
1013     SG_LOG(SG_AUTOPILOT, SG_ALERT, "passed invalid index (" << 
1014       index << ") to FGRouteMgr::jumpToIndex");
1015     return;
1016   }
1017
1018   if (_currentIndex == index) {
1019     return; // no-op
1020   }
1021   
1022 // all the checks out the way, go ahead and update state
1023   _currentIndex = index;
1024   currentWaypointChanged();
1025   _currentWpt->fireValueChanged();
1026 }
1027
1028 void FGRouteMgr::currentWaypointChanged()
1029 {
1030   Waypt* cur = (_currentIndex<numWaypts()) ? currentWaypt() : NULL;
1031   Waypt* next = nextWaypt();
1032
1033   wp0->getChild("id")->setStringValue(cur ? cur->ident() : "");
1034   wp1->getChild("id")->setStringValue(next ? next->ident() : "");
1035   
1036   _currentWpt->fireValueChanged();
1037   SG_LOG(SG_AUTOPILOT, SG_INFO, "route manager, current-wp is now " << _currentIndex);
1038 }
1039
1040 int FGRouteMgr::findWayptIndex(const SGGeod& aPos) const
1041 {  
1042   for (int i=0; i<numWaypts(); ++i) {
1043     if (_route[i]->matches(aPos)) {
1044       return i;
1045     }
1046   }
1047   
1048   return -1;
1049 }
1050
1051 Waypt* FGRouteMgr::currentWaypt() const
1052 {
1053   return wayptAtIndex(_currentIndex);
1054 }
1055
1056 Waypt* FGRouteMgr::previousWaypt() const
1057 {
1058   if (_currentIndex == 0) {
1059     return NULL;
1060   }
1061   
1062   return wayptAtIndex(_currentIndex - 1);
1063 }
1064
1065 Waypt* FGRouteMgr::nextWaypt() const
1066 {
1067   if ((_currentIndex + 1) >= numWaypts()) {
1068     return NULL;
1069   }
1070   
1071   return wayptAtIndex(_currentIndex + 1);
1072 }
1073
1074 Waypt* FGRouteMgr::wayptAtIndex(int index) const
1075 {
1076   if ((index < 0) || (index >= numWaypts())) {
1077     throw sg_range_exception("waypt index out of range", "FGRouteMgr::wayptAtIndex");
1078   }
1079   
1080   return _route[index];
1081 }
1082
1083 void FGRouteMgr::saveRoute()
1084 {
1085   SGPath path(_pathNode->getStringValue());
1086   SG_LOG(SG_IO, SG_INFO, "Saving route to " << path.str());
1087   try {
1088     SGPropertyNode_ptr d(new SGPropertyNode);
1089     SGPath path(_pathNode->getStringValue());
1090     d->setIntValue("version", 2);
1091     
1092     if (_departure) {
1093       d->setStringValue("departure/airport", _departure->ident());
1094       d->setStringValue("departure/sid", departure->getStringValue("sid"));
1095       d->setStringValue("departure/runway", departure->getStringValue("runway"));
1096     }
1097     
1098     if (_destination) {
1099       d->setStringValue("destination/airport", _destination->ident());
1100       d->setStringValue("destination/star", destination->getStringValue("star"));
1101       d->setStringValue("destination/transition", destination->getStringValue("transition"));
1102       d->setStringValue("destination/runway", destination->getStringValue("runway"));
1103     }
1104     
1105   // route nodes
1106     SGPropertyNode* routeNode = d->getChild("route", 0, true);
1107     for (unsigned int i=0; i<_route.size(); ++i) {
1108       Waypt* wpt = _route[i];
1109       wpt->saveAsNode(routeNode->getChild("wp", i, true));
1110     } // of waypoint iteration
1111     writeProperties(path.str(), d, true /* write-all */);
1112   } catch (sg_exception& e) {
1113     SG_LOG(SG_IO, SG_WARN, "failed to save flight-plan:" << e.getMessage());
1114   }
1115 }
1116
1117 void FGRouteMgr::loadRoute()
1118 {
1119   // deactivate route first
1120   active->setBoolValue(false);
1121   
1122   SGPropertyNode_ptr routeData(new SGPropertyNode);
1123   SGPath path(_pathNode->getStringValue());
1124   
1125   SG_LOG(SG_IO, SG_INFO, "going to read flight-plan from:" << path.str());
1126     
1127   try {
1128     readProperties(path.str(), routeData);
1129   } catch (sg_exception& e) {
1130     // if XML parsing fails, the file might be simple textual list of waypoints
1131     loadPlainTextRoute(path);
1132     return;
1133   }
1134   
1135   try {
1136     int version = routeData->getIntValue("version", 1);
1137     if (version == 1) {
1138       loadVersion1XMLRoute(routeData);
1139     } else if (version == 2) {
1140       loadVersion2XMLRoute(routeData);
1141     } else {
1142       throw sg_io_exception("unsupported XML route version");
1143     }
1144   } catch (sg_exception& e) {
1145     SG_LOG(SG_IO, SG_WARN, "failed to load flight-plan (from '" << e.getOrigin()
1146       << "'):" << e.getMessage());
1147   }
1148 }
1149
1150 void FGRouteMgr::loadXMLRouteHeader(SGPropertyNode_ptr routeData)
1151 {
1152   // departure nodes
1153   SGPropertyNode* dep = routeData->getChild("departure");
1154   if (dep) {
1155     string depIdent = dep->getStringValue("airport");
1156     _departure = (FGAirport*) fgFindAirportID(depIdent);
1157     departure->setStringValue("runway", dep->getStringValue("runway"));
1158     departure->setStringValue("sid", dep->getStringValue("sid"));
1159     departure->setStringValue("transition", dep->getStringValue("transition"));
1160   }
1161   
1162 // destination
1163   SGPropertyNode* dst = routeData->getChild("destination");
1164   if (dst) {
1165     _destination = (FGAirport*) fgFindAirportID(dst->getStringValue("airport"));
1166     destination->setStringValue("runway", dst->getStringValue("runway"));
1167     destination->setStringValue("star", dst->getStringValue("star"));
1168     destination->setStringValue("transition", dst->getStringValue("transition"));
1169   }
1170
1171 // alternate
1172   SGPropertyNode* alt = routeData->getChild("alternate");
1173   if (alt) {
1174     alternate->setStringValue(alt->getStringValue("airport"));
1175   } // of cruise data loading
1176   
1177 // cruise
1178   SGPropertyNode* crs = routeData->getChild("cruise");
1179   if (crs) {
1180     cruise->setDoubleValue("speed-kts", crs->getDoubleValue("speed-kts"));
1181     cruise->setDoubleValue("mach", crs->getDoubleValue("mach"));
1182     cruise->setDoubleValue("altitude-ft", crs->getDoubleValue("altitude-ft"));
1183   } // of cruise data loading
1184
1185 }
1186
1187 void FGRouteMgr::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
1188 {
1189   loadXMLRouteHeader(routeData);
1190   
1191 // route nodes
1192   WayptVec wpts;
1193   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
1194   for (int i=0; i<routeNode->nChildren(); ++i) {
1195     SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
1196     WayptRef wpt = Waypt::createFromProperties(NULL, wpNode);
1197     wpts.push_back(wpt);
1198   } // of route iteration
1199   
1200   _route = wpts;
1201 }
1202
1203 void FGRouteMgr::loadVersion1XMLRoute(SGPropertyNode_ptr routeData)
1204 {
1205   loadXMLRouteHeader(routeData);
1206
1207 // route nodes
1208   WayptVec wpts;
1209   SGPropertyNode_ptr routeNode = routeData->getChild("route", 0);    
1210   for (int i=0; i<routeNode->nChildren(); ++i) {
1211     SGPropertyNode_ptr wpNode = routeNode->getChild("wp", i);
1212     WayptRef wpt = parseVersion1XMLWaypt(wpNode);
1213     wpts.push_back(wpt);
1214   } // of route iteration
1215   
1216   _route = wpts;
1217 }
1218
1219 WayptRef FGRouteMgr::parseVersion1XMLWaypt(SGPropertyNode* aWP)
1220 {
1221   SGGeod lastPos;
1222   if (!_route.empty()) {
1223     lastPos = _route.back()->position();
1224   } else if (_departure) {
1225     lastPos = _departure->geod();
1226   }
1227
1228   WayptRef w;
1229   string ident(aWP->getStringValue("ident"));
1230   if (aWP->hasChild("longitude-deg")) {
1231     // explicit longitude/latitude
1232     w = new BasicWaypt(SGGeod::fromDeg(aWP->getDoubleValue("longitude-deg"), 
1233       aWP->getDoubleValue("latitude-deg")), ident, NULL);
1234     
1235   } else {
1236     string nid = aWP->getStringValue("navid", ident.c_str());
1237     FGPositionedRef p = FGPositioned::findClosestWithIdent(nid, lastPos);
1238     if (!p) {
1239       throw sg_io_exception("bad route file, unknown navid:" + nid);
1240     }
1241       
1242     SGGeod pos(p->geod());
1243     if (aWP->hasChild("offset-nm") && aWP->hasChild("offset-radial")) {
1244       double radialDeg = aWP->getDoubleValue("offset-radial");
1245       // convert magnetic radial to a true radial!
1246       radialDeg += magvar->getDoubleValue();
1247       double offsetNm = aWP->getDoubleValue("offset-nm");
1248       double az2;
1249       SGGeodesy::direct(p->geod(), radialDeg, offsetNm * SG_NM_TO_METER, pos, az2);
1250     }
1251
1252     w = new BasicWaypt(pos, ident, NULL);
1253   }
1254   
1255   double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
1256   if (altFt > -9990.0) {
1257     w->setAltitude(altFt, RESTRICT_AT);
1258   }
1259
1260   return w;
1261 }
1262
1263 void FGRouteMgr::loadPlainTextRoute(const SGPath& path)
1264 {
1265   sg_gzifstream in(path.str().c_str());
1266   if (!in.is_open()) {
1267     return;
1268   }
1269   
1270   try {
1271     WayptVec wpts;
1272     while (!in.eof()) {
1273       string line;
1274       getline(in, line, '\n');
1275     // trim CR from end of line, if found
1276       if (line[line.size() - 1] == '\r') {
1277         line.erase(line.size() - 1, 1);
1278       }
1279       
1280       line = simgear::strutils::strip(line);
1281       if (line.empty() || (line[0] == '#')) {
1282         continue; // ignore empty/comment lines
1283       }
1284       
1285       WayptRef w = waypointFromString(line);
1286       if (!w) {
1287         throw sg_io_exception("failed to create waypoint from line:" + line);
1288       }
1289       
1290       wpts.push_back(w);
1291     } // of line iteration
1292   
1293     _route = wpts;
1294   } catch (sg_exception& e) {
1295     SG_LOG(SG_IO, SG_WARN, "failed to load route from:" << path.str() << ":" << e.getMessage());
1296   }
1297 }
1298
1299 const char* FGRouteMgr::getDepartureICAO() const
1300 {
1301   if (!_departure) {
1302     return "";
1303   }
1304   
1305   return _departure->ident().c_str();
1306 }
1307
1308 const char* FGRouteMgr::getDepartureName() const
1309 {
1310   if (!_departure) {
1311     return "";
1312   }
1313   
1314   return _departure->name().c_str();
1315 }
1316
1317 void FGRouteMgr::setDepartureICAO(const char* aIdent)
1318 {
1319   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
1320     _departure = NULL;
1321   } else {
1322     _departure = FGAirport::findByIdent(aIdent);
1323   }
1324   
1325   departureChanged();
1326 }
1327
1328 const char* FGRouteMgr::getDestinationICAO() const
1329 {
1330   if (!_destination) {
1331     return "";
1332   }
1333   
1334   return _destination->ident().c_str();
1335 }
1336
1337 const char* FGRouteMgr::getDestinationName() const
1338 {
1339   if (!_destination) {
1340     return "";
1341   }
1342   
1343   return _destination->name().c_str();
1344 }
1345
1346 void FGRouteMgr::setDestinationICAO(const char* aIdent)
1347 {
1348   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
1349     _destination = NULL;
1350   } else {
1351     _destination = FGAirport::findByIdent(aIdent);
1352   }
1353   
1354   arrivalChanged();
1355 }
1356