]> git.mxchange.org Git - flightgear.git/blob - src/Autopilot/route_mgr.cxx
Give the FGAirport class a sane filename.
[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 <cstdio>
35
36 #include <simgear/compiler.h>
37
38 #include "route_mgr.hxx"
39
40 #include <boost/algorithm/string/case_conv.hpp>
41 #include <boost/tuple/tuple.hpp>
42 #include <boost/foreach.hpp>
43
44 #include <simgear/misc/strutils.hxx>
45 #include <simgear/structure/exception.hxx>
46 #include <simgear/structure/commands.hxx>
47 #include <simgear/misc/sg_path.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/procedure.hxx>
54 #include "Airports/airport.hxx"
55 #include "Airports/runways.hxx"
56 #include <GUI/new_gui.hxx>
57 #include <GUI/dialog.hxx>
58
59 #define RM "/autopilot/route-manager/"
60
61 using namespace flightgear;
62 using std::string;
63
64 static bool commandLoadFlightPlan(const SGPropertyNode* arg)
65 {
66   FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
67   SGPath path(arg->getStringValue("path"));
68   return self->loadRoute(path);
69 }
70
71 static bool commandSaveFlightPlan(const SGPropertyNode* arg)
72 {
73   FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
74   SGPath path(arg->getStringValue("path"));
75   return self->saveRoute(path);
76 }
77
78 static bool commandActivateFlightPlan(const SGPropertyNode* arg)
79 {
80   FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
81   bool activate = arg->getBoolValue("activate", true);
82   if (activate) {
83     self->activate();
84   } else {
85     self->deactivate();
86   }
87   
88   return true;
89 }
90
91 static bool commandClearFlightPlan(const SGPropertyNode*)
92 {
93   FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
94   self->clearRoute();
95   return true;
96 }
97
98 static bool commandSetActiveWaypt(const SGPropertyNode* arg)
99 {
100   FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
101   int index = arg->getIntValue("index");
102   if ((index < 0) || (index >= self->numLegs())) {
103     return false;
104   }
105   
106   self->jumpToIndex(index);
107   return true;
108 }
109
110 static bool commandInsertWaypt(const SGPropertyNode* arg)
111 {
112   FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
113   int index = arg->getIntValue("index");
114   std::string ident(arg->getStringValue("id"));
115   int alt = arg->getIntValue("altitude-ft", -999);
116   int ias = arg->getIntValue("speed-knots", -999);
117   
118   WayptRef wp;
119 // lat/lon may be supplied to narrow down navaid search, or to specify
120 // a raw waypoint
121   SGGeod pos;
122   if (arg->hasChild("longitude-deg")) {
123     pos = SGGeod::fromDeg(arg->getDoubleValue("longitude-deg"),
124                                arg->getDoubleValue("latitude-deg"));
125   }
126   
127   if (arg->hasChild("navaid")) {
128     FGPositionedRef p = FGPositioned::findClosestWithIdent(arg->getStringValue("navaid"), pos);
129     
130     if (arg->hasChild("navaid", 1)) {
131       // intersection of two radials
132       FGPositionedRef p2 = FGPositioned::findClosestWithIdent(arg->getStringValue("navaid[1]"), pos);
133       if (!p2) {
134         SG_LOG( SG_AUTOPILOT, SG_INFO, "Unable to find FGPositioned with ident:" << arg->getStringValue("navaid[1]"));
135         return false;
136       }
137       
138       double r1 = arg->getDoubleValue("radial"),
139         r2 = arg->getDoubleValue("radial[1]");
140       
141       SGGeod intersection;
142       bool ok = SGGeodesy::radialIntersection(p->geod(), r1, p2->geod(), r2, intersection);
143       if (!ok) {
144         SG_LOG(SG_AUTOPILOT, SG_INFO, "no valid intersection for:" << p->ident() 
145                 << "," << p2->ident());
146         return false;
147       }
148       
149       std::string name = p->ident() + "-" + p2->ident();
150       wp = new BasicWaypt(intersection, name, NULL);
151     } else if (arg->hasChild("offset-nm") && arg->hasChild("radial")) {
152       // offset radial from navaid
153       double radial = arg->getDoubleValue("radial");
154       double distanceNm = arg->getDoubleValue("offset-nm");
155       //radial += magvar->getDoubleValue(); // convert to true bearing
156       wp = new OffsetNavaidWaypoint(p, NULL, radial, distanceNm);
157     } else {
158       wp = new NavaidWaypoint(p, NULL);
159     }
160   } else if (arg->hasChild("airport")) {
161     const FGAirport* apt = fgFindAirportID(arg->getStringValue("airport"));
162     if (!apt) {
163       SG_LOG(SG_AUTOPILOT, SG_INFO, "no such airport" << arg->getStringValue("airport"));
164       return false;
165     }
166     
167     if (arg->hasChild("runway")) {
168       if (!apt->hasRunwayWithIdent(arg->getStringValue("runway"))) {
169         SG_LOG(SG_AUTOPILOT, SG_INFO, "No runway: " << arg->getStringValue("runway") << " at " << apt->ident());
170         return false;
171       }
172       
173       FGRunway* runway = apt->getRunwayByIdent(arg->getStringValue("runway"));
174       wp = new RunwayWaypt(runway, NULL);
175     } else {
176       wp = new NavaidWaypoint((FGAirport*) apt, NULL);
177     }
178   } else if (arg->hasChild("text")) {
179     wp = self->waypointFromString(arg->getStringValue("text"));
180   } else if (!(pos == SGGeod())) {
181     // just a raw lat/lon
182     wp = new BasicWaypt(pos, ident, NULL);
183   } else {
184     return false; // failed to build waypoint
185   }
186
187   FlightPlan::Leg* leg = self->flightPlan()->insertWayptAtIndex(wp, index);
188   if (alt >= 0) {
189     leg->setAltitude(RESTRICT_AT, alt);
190   }
191   
192   if (ias > 0) {
193     leg->setSpeed(RESTRICT_AT, ias);
194   }
195   
196   return true;
197 }
198
199 static bool commandDeleteWaypt(const SGPropertyNode* arg)
200 {
201   FGRouteMgr* self = (FGRouteMgr*) globals->get_subsystem("route-manager");
202   int index = arg->getIntValue("index");
203   self->removeLegAtIndex(index);
204   return true;
205 }
206
207 /////////////////////////////////////////////////////////////////////////////
208
209 FGRouteMgr::FGRouteMgr() :
210   _plan(NULL),
211   input(fgGetNode( RM "input", true )),
212   mirror(fgGetNode( RM "route", true ))
213 {
214   listener = new InputListener(this);
215   input->setStringValue("");
216   input->addChangeListener(listener);
217   
218   SGCommandMgr::instance()->addCommand("load-flightplan", commandLoadFlightPlan);
219   SGCommandMgr::instance()->addCommand("save-flightplan", commandSaveFlightPlan);
220   SGCommandMgr::instance()->addCommand("activate-flightplan", commandActivateFlightPlan);
221   SGCommandMgr::instance()->addCommand("clear-flightplan", commandClearFlightPlan);
222   SGCommandMgr::instance()->addCommand("set-active-waypt", commandSetActiveWaypt);
223   SGCommandMgr::instance()->addCommand("insert-waypt", commandInsertWaypt);
224   SGCommandMgr::instance()->addCommand("delete-waypt", commandDeleteWaypt);
225 }
226
227
228 FGRouteMgr::~FGRouteMgr()
229 {
230   input->removeChangeListener(listener);
231   delete listener;
232 }
233
234
235 void FGRouteMgr::init() {
236   SGPropertyNode_ptr rm(fgGetNode(RM));
237   
238   magvar = fgGetNode("/environment/magnetic-variation-deg", true);
239      
240   departure = fgGetNode(RM "departure", true);
241   departure->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
242     &FGRouteMgr::getDepartureICAO, &FGRouteMgr::setDepartureICAO));
243   departure->tie("runway", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
244                                                                        &FGRouteMgr::getDepartureRunway, 
245                                                                       &FGRouteMgr::setDepartureRunway));
246   departure->tie("sid", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
247                                                                       &FGRouteMgr::getSID, 
248                                                                       &FGRouteMgr::setSID));
249   
250   departure->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
251     &FGRouteMgr::getDepartureName, NULL));
252   departure->tie("field-elevation-ft", SGRawValueMethods<FGRouteMgr, double>(*this, 
253                                                                                &FGRouteMgr::getDestinationFieldElevation, NULL));
254   departure->getChild("etd", 0, true);
255   departure->getChild("takeoff-time", 0, true);
256
257   destination = fgGetNode(RM "destination", true);
258   destination->getChild("airport", 0, true);
259   
260   destination->tie("airport", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
261     &FGRouteMgr::getDestinationICAO, &FGRouteMgr::setDestinationICAO));
262   destination->tie("runway", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
263                              &FGRouteMgr::getDestinationRunway, 
264                             &FGRouteMgr::setDestinationRunway));
265   destination->tie("star", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
266                                                                         &FGRouteMgr::getSTAR, 
267                                                                         &FGRouteMgr::setSTAR));
268   destination->tie("approach", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
269                                                                         &FGRouteMgr::getApproach, 
270                                                                         &FGRouteMgr::setApproach));
271   
272   destination->tie("name", SGRawValueMethods<FGRouteMgr, const char*>(*this, 
273     &FGRouteMgr::getDestinationName, NULL));
274   destination->tie("field-elevation-ft", SGRawValueMethods<FGRouteMgr, double>(*this, 
275                                                                       &FGRouteMgr::getDestinationFieldElevation, NULL));
276   
277   destination->getChild("eta", 0, true);
278   destination->getChild("touchdown-time", 0, true);
279
280   alternate = fgGetNode(RM "alternate", true);
281   alternate->getChild("airport", 0, true);
282   alternate->getChild("runway", 0, true);
283   
284   cruise = fgGetNode(RM "cruise", true);
285   cruise->getChild("altitude-ft", 0, true);
286   cruise->setDoubleValue("altitude-ft", 10000.0);
287   cruise->getChild("flight-level", 0, true);
288   cruise->getChild("speed-kts", 0, true);
289   cruise->setDoubleValue("speed-kts", 160.0);
290   
291   totalDistance = fgGetNode(RM "total-distance", true);
292   totalDistance->setDoubleValue(0.0);
293   distanceToGo = fgGetNode(RM "distance-remaining-nm", true);
294   distanceToGo->setDoubleValue(0.0);
295   
296   ete = fgGetNode(RM "ete", true);
297   ete->setDoubleValue(0.0);
298   
299   elapsedFlightTime = fgGetNode(RM "flight-time", true);
300   elapsedFlightTime->setDoubleValue(0.0);
301   
302   active = fgGetNode(RM "active", true);
303   active->setBoolValue(false);
304   
305   airborne = fgGetNode(RM "airborne", true);
306   airborne->setBoolValue(false);
307     
308   _edited = fgGetNode(RM "signals/edited", true);
309   _finished = fgGetNode(RM "signals/finished", true);
310   _flightplanChanged = fgGetNode(RM "signals/flightplan-changed", true);
311   
312   _currentWpt = fgGetNode(RM "current-wp", true);
313   _currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
314     (*this, &FGRouteMgr::currentIndex, &FGRouteMgr::jumpToIndex));
315       
316   // temporary distance / eta calculations, for backward-compatability
317   wp0 = fgGetNode(RM "wp", 0, true);
318   wp0->getChild("id", 0, true);
319   wp0->getChild("dist", 0, true);
320   wp0->getChild("eta", 0, true);
321   wp0->getChild("bearing-deg", 0, true);
322   
323   wp1 = fgGetNode(RM "wp", 1, true);
324   wp1->getChild("id", 0, true);
325   wp1->getChild("dist", 0, true);
326   wp1->getChild("eta", 0, true);
327   
328   wpn = fgGetNode(RM "wp-last", 0, true);
329   wpn->getChild("dist", 0, true);
330   wpn->getChild("eta", 0, true);
331   
332   _pathNode = fgGetNode(RM "file-path", 0, true);
333 }
334
335
336 void FGRouteMgr::postinit()
337 {
338   setFlightPlan(new FlightPlan());
339   
340   SGPath path(_pathNode->getStringValue());
341   if (!path.isNull()) {
342     SG_LOG(SG_AUTOPILOT, SG_INFO, "loading flight-plan from: " << path.str());
343     loadRoute(path);
344   }
345   
346 // this code only matters for the --wp option now - perhaps the option
347 // should be deprecated in favour of an explicit flight-plan file?
348 // then the global initial waypoint list could die.
349   string_list *waypoints = globals->get_initial_waypoints();
350   if (waypoints) {
351     string_list::iterator it;
352     for (it = waypoints->begin(); it != waypoints->end(); ++it) {
353       WayptRef w = waypointFromString(*it);
354       if (w) {
355         _plan->insertWayptAtIndex(w, -1);
356       }
357     }
358     
359     SG_LOG(SG_AUTOPILOT, SG_INFO, "loaded initial waypoints:" << numLegs());
360     update_mirror();
361   }
362
363   weightOnWheels = fgGetNode("/gear/gear[0]/wow", true);
364   groundSpeed = fgGetNode("/velocities/groundspeed-kt", true);
365   
366   // check airbone flag agrees with presets
367 }
368
369 void FGRouteMgr::bind() { }
370 void FGRouteMgr::unbind() { }
371
372 bool FGRouteMgr::isRouteActive() const
373 {
374   return active->getBoolValue();
375 }
376
377 bool FGRouteMgr::saveRoute(const SGPath& p)
378 {
379   if (!_plan) {
380     return false;
381   }
382   
383   return _plan->save(p);
384 }
385
386 bool FGRouteMgr::loadRoute(const SGPath& p)
387 {
388   FlightPlan* fp = new FlightPlan;
389   if (!fp->load(p)) {
390     delete fp;
391     return false;
392   }
393   
394   setFlightPlan(fp);
395   return true;
396 }
397
398 FlightPlan* FGRouteMgr::flightPlan() const
399 {
400   return _plan;
401 }
402
403 void FGRouteMgr::setFlightPlan(FlightPlan* plan)
404 {
405   if (plan == _plan) {
406     return;
407   }
408   
409   if (_plan) {
410     _plan->removeDelegate(this);
411     delete _plan;
412     active->setBoolValue(false);
413   }
414   
415   _plan = plan;
416   _plan->addDelegate(this);
417   
418   _flightplanChanged->fireValueChanged();
419   
420 // fire all the callbacks!
421   departureChanged();
422   arrivalChanged();
423   waypointsChanged();
424   currentWaypointChanged();
425 }
426
427 void FGRouteMgr::update( double dt )
428 {
429   if (dt <= 0.0) {
430     return; // paused, nothing to do here
431   }
432   
433   double gs = groundSpeed->getDoubleValue();
434   if (airborne->getBoolValue()) {
435     time_t now = time(NULL);
436     elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
437     
438     if (weightOnWheels->getBoolValue()) {
439       // touch down
440       destination->setIntValue("touchdown-time", time(NULL));
441       airborne->setBoolValue(false);
442     }
443   } else { // not airborne
444     if (weightOnWheels->getBoolValue() || (gs < 40)) {
445       // either taking-off or rolling-out after touchdown
446     } else {
447       airborne->setBoolValue(true);
448       _takeoffTime = time(NULL); // start the clock
449       departure->setIntValue("takeoff-time", _takeoffTime);
450     }
451   }
452   
453   if (!active->getBoolValue()) {
454     return;
455   }
456
457   if (checkFinished()) {
458     endOfRoute();
459   }
460   
461 // basic course/distance information
462   SGGeod currentPos = globals->get_aircraft_position();
463
464   FlightPlan::Leg* leg = _plan ? _plan->currentLeg() : NULL;
465   if (!leg) {
466     return;
467   }
468   
469   double courseDeg;
470   double distanceM;
471   boost::tie(courseDeg, distanceM) = leg->waypoint()->courseAndDistanceFrom(currentPos);
472   
473 // update wp0 / wp1 / wp-last
474   wp0->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
475   wp0->setDoubleValue("true-bearing-deg", courseDeg);
476   courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
477   wp0->setDoubleValue("bearing-deg", courseDeg);
478   setETAPropertyFromDistance(wp0->getChild("eta"), distanceM);
479   
480   double totalPathDistanceNm = _plan->totalDistanceNm();
481   double totalDistanceRemaining = distanceM * SG_METER_TO_NM; // distance to current waypoint
482   
483 // total distance to go, is direct distance to wp0, plus the remaining
484 // path distance from wp0
485   totalDistanceRemaining += (totalPathDistanceNm - leg->distanceAlongRoute());
486   
487   wp0->setDoubleValue("distance-along-route-nm", 
488                       leg->distanceAlongRoute());
489   wp0->setDoubleValue("remaining-distance-nm", 
490                       totalPathDistanceNm - leg->distanceAlongRoute());
491   
492   FlightPlan::Leg* nextLeg = _plan->nextLeg();
493   if (nextLeg) {
494     boost::tie(courseDeg, distanceM) = nextLeg->waypoint()->courseAndDistanceFrom(currentPos);
495      
496     wp1->setDoubleValue("dist", distanceM * SG_METER_TO_NM);
497     wp1->setDoubleValue("true-bearing-deg", courseDeg);
498     courseDeg -= magvar->getDoubleValue(); // expose magnetic bearing
499     wp1->setDoubleValue("bearing-deg", courseDeg);
500     setETAPropertyFromDistance(wp1->getChild("eta"), distanceM);    
501     wp1->setDoubleValue("distance-along-route-nm", 
502                         nextLeg->distanceAlongRoute());
503     wp1->setDoubleValue("remaining-distance-nm", 
504                         totalPathDistanceNm - nextLeg->distanceAlongRoute());
505   }
506   
507   distanceToGo->setDoubleValue(totalDistanceRemaining);
508   wpn->setDoubleValue("dist", totalDistanceRemaining);
509   ete->setDoubleValue(totalDistanceRemaining / gs * 3600.0);
510   setETAPropertyFromDistance(wpn->getChild("eta"), totalDistanceRemaining);
511 }
512
513 void FGRouteMgr::clearRoute()
514 {
515   if (_plan) {
516     _plan->clear();
517   }
518 }
519
520 Waypt* FGRouteMgr::currentWaypt() const
521 {
522   if (_plan && _plan->currentLeg()) {
523     return _plan->currentLeg()->waypoint();
524   }
525   
526   return NULL;
527 }
528
529 int FGRouteMgr::currentIndex() const
530 {
531   if (!_plan) {
532     return 0;
533   }
534   
535   return _plan->currentIndex();
536 }
537
538 Waypt* FGRouteMgr::wayptAtIndex(int index) const
539 {
540   if (!_plan) {
541     throw sg_range_exception("wayptAtindex: no flightplan");
542   }
543   
544   return _plan->legAtIndex(index)->waypoint();
545 }
546
547 int FGRouteMgr::numLegs() const
548 {
549   if (_plan) {
550     return _plan->numLegs();
551   }
552   
553   return 0;
554 }
555
556 void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDistance)
557 {
558   double speed = groundSpeed->getDoubleValue();
559   if (speed < 1.0) {
560     aProp->setStringValue("--:--");
561     return;
562   }
563
564   char eta_str[64];
565   double eta = aDistance * SG_METER_TO_NM / speed;
566   if ( eta >= 100.0 ) { 
567       eta = 99.999; // clamp
568   }
569   
570   if ( eta < (1.0/6.0) ) {
571     eta *= 60.0; // within 10 minutes, bump up to min/secs
572   }
573   
574   int major = (int)eta, 
575       minor = (int)((eta - (int)eta) * 60.0);
576   snprintf( eta_str, 64, "%d:%02d", major, minor );
577   aProp->setStringValue( eta_str );
578 }
579
580 void FGRouteMgr::removeLegAtIndex(int aIndex)
581 {
582   if (!_plan) {
583     return;
584   }
585   
586   _plan->deleteIndex(aIndex);
587 }
588   
589 void FGRouteMgr::waypointsChanged()
590 {
591   update_mirror();
592   _edited->fireValueChanged();
593   
594 // removing waypoints, deactivate if we hit the end.
595   if (currentIndex() >= numLegs()) {
596     endOfRoute();
597   }
598 }
599
600 // mirror internal route to the property system for inspection by other subsystems
601 void FGRouteMgr::update_mirror()
602 {
603   mirror->removeChildren("wp");
604   NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
605   FGDialog* rmDlg = gui ? gui->getDialog("route-manager") : NULL;
606
607   if (!_plan) {
608     mirror->setIntValue("num", 0);
609     if (rmDlg) {
610       rmDlg->updateValues();
611     }
612     return;
613   }
614   
615   int num = _plan->numLegs();
616     
617   for (int i = 0; i < num; i++) {
618     FlightPlan::Leg* leg = _plan->legAtIndex(i);
619     WayptRef wp = leg->waypoint();
620     SGPropertyNode *prop = mirror->getChild("wp", i, 1);
621
622     const SGGeod& pos(wp->position());
623     prop->setStringValue("id", wp->ident().c_str());
624     prop->setDoubleValue("longitude-deg", pos.getLongitudeDeg());
625     prop->setDoubleValue("latitude-deg",pos.getLatitudeDeg());
626    
627     // leg course+distance
628
629     prop->setDoubleValue("leg-bearing-true-deg", leg->courseDeg());
630     prop->setDoubleValue("leg-distance-nm", leg->distanceNm());
631     prop->setDoubleValue("distance-along-route-nm", leg->distanceAlongRoute());
632     
633     if (leg->altitudeRestriction() != RESTRICT_NONE) {
634       double ft = leg->altitudeFt();
635       prop->setDoubleValue("altitude-m", ft * SG_FEET_TO_METER);
636       prop->setDoubleValue("altitude-ft", ft);
637       prop->setIntValue("flight-level", static_cast<int>(ft / 1000) * 10);
638     } else {
639       prop->setDoubleValue("altitude-m", -9999.9);
640       prop->setDoubleValue("altitude-ft", -9999.9);
641     }
642     
643     if (leg->speedRestriction() == SPEED_RESTRICT_MACH) {
644       prop->setDoubleValue("speed-mach", leg->speedMach());
645     } else if (leg->speedRestriction() != RESTRICT_NONE) {
646       prop->setDoubleValue("speed-kts", leg->speedKts());
647     }
648     
649     if (wp->flag(WPT_ARRIVAL)) {
650       prop->setBoolValue("arrival", true);
651     }
652     
653     if (wp->flag(WPT_DEPARTURE)) {
654       prop->setBoolValue("departure", true);
655     }
656     
657     if (wp->flag(WPT_MISS)) {
658       prop->setBoolValue("missed-approach", true);
659     }
660     
661     prop->setBoolValue("generated", wp->flag(WPT_GENERATED));
662   } // of waypoint iteration
663   
664   // set number as listener attachment point
665   mirror->setIntValue("num", _plan->numLegs());
666     
667   if (rmDlg) {
668     rmDlg->updateValues();
669   }
670   
671   totalDistance->setDoubleValue(_plan->totalDistanceNm());
672 }
673
674 // command interface /autopilot/route-manager/input:
675 //
676 //   @CLEAR             ... clear route
677 //   @POP               ... remove first entry
678 //   @DELETE3           ... delete 4th entry
679 //   @INSERT2:KSFO@900  ... insert "KSFO@900" as 3rd entry
680 //   KSFO@900           ... append "KSFO@900"
681 //
682 void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
683 {
684     const char *s = prop->getStringValue();
685     if (strlen(s) == 0) {
686       return;
687     }
688     
689     if (!strcmp(s, "@CLEAR"))
690         mgr->clearRoute();
691     else if (!strcmp(s, "@ACTIVATE"))
692         mgr->activate();
693     else if (!strcmp(s, "@LOAD")) {
694       SGPath path(mgr->_pathNode->getStringValue());
695       mgr->loadRoute(path);
696     } else if (!strcmp(s, "@SAVE")) {
697       SGPath path(mgr->_pathNode->getStringValue());
698       mgr->saveRoute(path);
699     } else if (!strcmp(s, "@NEXT")) {
700       mgr->jumpToIndex(mgr->currentIndex() + 1);
701     } else if (!strcmp(s, "@PREVIOUS")) {
702       mgr->jumpToIndex(mgr->currentIndex() - 1);
703     } else if (!strncmp(s, "@JUMP", 5)) {
704       mgr->jumpToIndex(atoi(s + 5));
705     } else if (!strncmp(s, "@DELETE", 7))
706         mgr->removeLegAtIndex(atoi(s + 7));
707     else if (!strncmp(s, "@INSERT", 7)) {
708         char *r;
709         int pos = strtol(s + 7, &r, 10);
710         if (*r++ != ':')
711             return;
712         while (isspace(*r))
713             r++;
714         if (*r)
715             mgr->flightPlan()->insertWayptAtIndex(mgr->waypointFromString(r), pos);
716     } else if (!strcmp(s, "@POSINIT")) {
717       mgr->initAtPosition();
718     } else
719       mgr->flightPlan()->insertWayptAtIndex(mgr->waypointFromString(s), -1);
720 }
721
722 void FGRouteMgr::initAtPosition()
723 {
724   if (isRouteActive()) {
725     return; // don't mess with the active route
726   }
727   
728   if (haveUserWaypoints()) {
729     // user has already defined, loaded or entered a route, again
730     // don't interfere with it
731     return; 
732   }
733   
734   if (airborne->getBoolValue()) {
735     SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: airborne, clearing departure info");
736     _plan->setDeparture((FGAirport*) NULL);
737     return;
738   }
739   
740 // on the ground
741   SGGeod pos = globals->get_aircraft_position();
742   if (!_plan->departureAirport()) {
743     _plan->setDeparture(FGAirport::findClosest(pos, 20.0));
744     if (!_plan->departureAirport()) {
745       SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: couldn't find an airport within 20nm");
746       return;
747     }
748   }
749   
750   std::string rwy = departure->getStringValue("runway");
751   FGRunway* r = NULL;
752   if (!rwy.empty()) {
753     r = _plan->departureAirport()->getRunwayByIdent(rwy);
754   } else {
755     r = _plan->departureAirport()->findBestRunwayForPos(pos);
756   }
757   
758   if (!r) {
759     return;
760   }
761   
762   _plan->setDeparture(r);
763   SG_LOG(SG_AUTOPILOT, SG_INFO, "initAtPosition: starting at " 
764     << _plan->departureAirport()->ident() << " on runway " << r->ident());
765 }
766
767 bool FGRouteMgr::haveUserWaypoints() const
768 {
769   // FIXME
770   return false;
771 }
772
773 bool FGRouteMgr::activate()
774 {
775   if (!_plan) {
776     SG_LOG(SG_AUTOPILOT, SG_WARN, "::activate, no flight plan defined");
777     return false;
778   }
779   
780   if (isRouteActive()) {
781     SG_LOG(SG_AUTOPILOT, SG_WARN, "duplicate route-activation, no-op");
782     return false;
783   }
784  
785   _plan->setCurrentIndex(0);
786   active->setBoolValue(true);
787   SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
788   return true;
789 }
790
791 void FGRouteMgr::deactivate()
792 {
793   if (!isRouteActive()) {
794     return;
795   }
796   
797   SG_LOG(SG_AUTOPILOT, SG_INFO, "deactivating flight plan");
798   active->setBoolValue(false);
799 }
800
801 void FGRouteMgr::sequence()
802 {
803   if (!_plan || !active->getBoolValue()) {
804     SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
805     return;
806   }
807   
808   int nextIndex = _plan->currentIndex() + 1;
809   if (nextIndex >= _plan->numLegs()) {
810     SG_LOG(SG_AUTOPILOT, SG_INFO, "sequenced on final leg, deactivating route");
811     endOfRoute();
812     return;
813   }
814
815   _plan->setCurrentIndex(nextIndex);
816 }
817
818 void FGRouteMgr::endOfRoute()
819 {
820   SG_LOG(SG_AUTOPILOT, SG_INFO, "reached end of active route");
821   _finished->fireValueChanged();
822   active->setBoolValue(false);
823 }
824
825 bool FGRouteMgr::checkFinished()
826 {
827   if (!_plan) {
828     return true;
829   }
830   
831   bool done = false;
832 // done if we're stopped on the destination runway 
833   if (_plan->currentLeg() && 
834       (_plan->currentLeg()->waypoint()->source() == _plan->destinationRunway())) 
835   {
836     double gs = groundSpeed->getDoubleValue();
837     done = weightOnWheels->getBoolValue() && (gs < 25);
838   }
839   
840   if (done) {
841     SG_LOG(SG_AUTOPILOT, SG_INFO, "checkFinished: on the ground on destination runway, we're done");
842   }
843   
844   return done;
845 }
846
847 void FGRouteMgr::jumpToIndex(int index)
848 {
849   if (!_plan) {
850     return;
851   }
852   
853   _plan->setCurrentIndex(index);
854 }
855
856 void FGRouteMgr::currentWaypointChanged()
857 {
858   Waypt* cur = currentWaypt();
859   FlightPlan::Leg* next = _plan ? _plan->nextLeg() : NULL;
860
861   wp0->getChild("id")->setStringValue(cur ? cur->ident() : "");
862   wp1->getChild("id")->setStringValue(next ? next->waypoint()->ident() : "");
863   
864   _currentWpt->fireValueChanged();
865   SG_LOG(SG_AUTOPILOT, SG_INFO, "route manager, current-wp is now " << currentIndex());
866 }
867
868 const char* FGRouteMgr::getDepartureICAO() const
869 {
870   if (!_plan || !_plan->departureAirport()) {
871     return "";
872   }
873   
874   return _plan->departureAirport()->ident().c_str();
875 }
876
877 const char* FGRouteMgr::getDepartureName() const
878 {
879   if (!_plan || !_plan->departureAirport()) {
880     return "";
881   }
882   
883   return _plan->departureAirport()->name().c_str();
884 }
885
886 const char* FGRouteMgr::getDepartureRunway() const
887 {
888   if (_plan && _plan->departureRunway()) {
889     return _plan->departureRunway()->ident().c_str();
890   }
891   
892   return "";
893 }
894
895 void FGRouteMgr::setDepartureRunway(const char* aIdent)
896 {
897   FGAirport* apt = _plan->departureAirport();
898   if (!apt || (aIdent == NULL)) {
899     _plan->setDeparture(apt);
900   } else if (apt->hasRunwayWithIdent(aIdent)) {
901     _plan->setDeparture(apt->getRunwayByIdent(aIdent));
902   }
903 }
904
905 void FGRouteMgr::setDepartureICAO(const char* aIdent)
906 {
907   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
908     _plan->setDeparture((FGAirport*) NULL);
909   } else {
910     _plan->setDeparture(FGAirport::findByIdent(aIdent));
911   }
912 }
913
914 const char* FGRouteMgr::getSID() const
915 {
916   if (_plan && _plan->sid()) {
917     return _plan->sid()->ident().c_str();
918   }
919   
920   return "";
921 }
922
923 static double headingDiffDeg(double a, double b)
924 {
925   double rawDiff = b - a;
926   SG_NORMALIZE_RANGE(rawDiff, -180.0, 180.0);
927   return rawDiff;
928 }
929
930 flightgear::SID* createDefaultSID(FGRunway* aRunway, double enrouteCourse)
931 {
932   if (!aRunway) {
933     return NULL;
934   }
935   
936   double runwayElevFt = aRunway->end().getElevationFt();
937   WayptVec wpts;
938   std::ostringstream ss;
939   ss << aRunway->ident() << "-3";
940   
941   SGGeod p = aRunway->pointOnCenterline(aRunway->lengthM() + (3.0 * SG_NM_TO_METER));
942   WayptRef w = new BasicWaypt(p, ss.str(), NULL);
943   w->setAltitude(runwayElevFt + 3000.0, RESTRICT_AT);
944   wpts.push_back(w);
945   
946   ss.str("");
947   ss << aRunway->ident() << "-6";
948   p = aRunway->pointOnCenterline(aRunway->lengthM() + (6.0 * SG_NM_TO_METER));
949   w = new BasicWaypt(p, ss.str(), NULL);
950   w->setAltitude(runwayElevFt + 6000.0, RESTRICT_AT);
951   wpts.push_back(w);
952
953   if (enrouteCourse >= 0.0) {
954     // valid enroute course
955     int index = 3;
956     double course = aRunway->headingDeg();
957     double diff;
958     while (fabs(diff = headingDiffDeg(course, enrouteCourse)) > 45.0) {
959       // turn in the sign of the heading change 45 degrees
960       course += copysign(45.0, diff);
961       ss.str("");
962       ss << "DEP-" << index++;
963       SGGeod pos = wpts.back()->position();
964       pos = SGGeodesy::direct(pos, course, 3.0 * SG_NM_TO_METER);
965       w = new BasicWaypt(pos, ss.str(), NULL);
966       wpts.push_back(w);
967     }
968   } else {
969     // no enroute course, just keep runway heading
970     ss.str("");
971     ss << aRunway->ident() << "-9";
972     p = aRunway->pointOnCenterline(aRunway->lengthM() + (9.0 * SG_NM_TO_METER));
973     w = new BasicWaypt(p, ss.str(), NULL);
974     w->setAltitude(runwayElevFt + 9000.0, RESTRICT_AT);
975     wpts.push_back(w);
976   }
977   
978   BOOST_FOREACH(Waypt* w, wpts) {
979     w->setFlag(WPT_DEPARTURE);
980     w->setFlag(WPT_GENERATED);
981   }
982   
983   return flightgear::SID::createTempSID("DEFAULT", aRunway, wpts);
984 }
985
986 void FGRouteMgr::setSID(const char* aIdent)
987 {
988   FGAirport* apt = _plan->departureAirport();
989   if (!apt || (aIdent == NULL)) {
990     _plan->setSID((flightgear::SID*) NULL);
991     return;
992   } 
993   
994   if (!strcmp(aIdent, "DEFAULT")) {
995     double enrouteCourse = -1.0;
996     if (_plan->destinationAirport()) {
997       enrouteCourse = SGGeodesy::courseDeg(apt->geod(), _plan->destinationAirport()->geod());
998     }
999     
1000     _plan->setSID(createDefaultSID(_plan->departureRunway(), enrouteCourse));
1001     return;
1002   }
1003   
1004   string ident(aIdent);
1005   size_t hyphenPos = ident.find('-');
1006   if (hyphenPos != string::npos) {
1007     string sidIdent = ident.substr(0, hyphenPos);
1008     string transIdent = ident.substr(hyphenPos + 1);
1009     
1010     flightgear::SID* sid = apt->findSIDWithIdent(sidIdent);
1011     Transition* trans = sid ? sid->findTransitionByName(transIdent) : NULL;
1012     _plan->setSID(trans);
1013   } else {
1014     _plan->setSID(apt->findSIDWithIdent(aIdent));
1015   }
1016 }
1017
1018 const char* FGRouteMgr::getDestinationICAO() const
1019 {
1020   if (!_plan || !_plan->destinationAirport()) {
1021     return "";
1022   }
1023   
1024   return _plan->destinationAirport()->ident().c_str();
1025 }
1026
1027 const char* FGRouteMgr::getDestinationName() const
1028 {
1029   if (!_plan || !_plan->destinationAirport()) {
1030     return "";
1031   }
1032   
1033   return _plan->destinationAirport()->name().c_str();
1034 }
1035
1036 void FGRouteMgr::setDestinationICAO(const char* aIdent)
1037 {
1038   if ((aIdent == NULL) || (strlen(aIdent) < 4)) {
1039     _plan->setDestination((FGAirport*) NULL);
1040   } else {
1041     _plan->setDestination(FGAirport::findByIdent(aIdent));
1042   }
1043 }
1044
1045 const char* FGRouteMgr::getDestinationRunway() const
1046 {
1047   if (_plan && _plan->destinationRunway()) {
1048     return _plan->destinationRunway()->ident().c_str();
1049   }
1050   
1051   return "";
1052 }
1053
1054 void FGRouteMgr::setDestinationRunway(const char* aIdent)
1055 {
1056   FGAirport* apt = _plan->destinationAirport();
1057   if (!apt || (aIdent == NULL)) {
1058     _plan->setDestination(apt);
1059   } else if (apt->hasRunwayWithIdent(aIdent)) {
1060     _plan->setDestination(apt->getRunwayByIdent(aIdent));
1061   }
1062 }
1063
1064 const char* FGRouteMgr::getApproach() const
1065 {
1066   if (_plan && _plan->approach()) {
1067     return _plan->approach()->ident().c_str();
1068   }
1069   
1070   return "";
1071 }
1072
1073 flightgear::Approach* createDefaultApproach(FGRunway* aRunway, double aEnrouteCourse)
1074 {
1075   if (!aRunway) {
1076     return NULL;
1077   }
1078
1079   double thresholdElevFt = aRunway->threshold().getElevationFt();
1080   const double approachHeightFt = 2000.0;
1081   double glideslopeDistanceM = (approachHeightFt * SG_FEET_TO_METER) /
1082     tan(3.0 * SG_DEGREES_TO_RADIANS);
1083   
1084   std::ostringstream ss;
1085   ss << aRunway->ident() << "-12";
1086   WayptVec wpts;
1087   SGGeod p = aRunway->pointOnCenterline(-12.0 * SG_NM_TO_METER);
1088   WayptRef w = new BasicWaypt(p, ss.str(), NULL);
1089   w->setAltitude(thresholdElevFt + 4000, RESTRICT_AT);
1090   wpts.push_back(w);
1091
1092 // work back form the first point on the centerline
1093   
1094   if (aEnrouteCourse >= 0.0) {
1095     // valid enroute course
1096     int index = 4;
1097     double course = aRunway->headingDeg();
1098     double diff;
1099     while (fabs(diff = headingDiffDeg(aEnrouteCourse, course)) > 45.0) {
1100       // turn in the sign of the heading change 45 degrees
1101       course -= copysign(45.0, diff);
1102       ss.str("");
1103       ss << "APP-" << index++;
1104       SGGeod pos = wpts.front()->position();
1105       pos = SGGeodesy::direct(pos, course + 180.0, 3.0 * SG_NM_TO_METER);
1106       w = new BasicWaypt(pos, ss.str(), NULL);
1107       wpts.insert(wpts.begin(), w);
1108     }
1109   }
1110     
1111   p = aRunway->pointOnCenterline(-8.0 * SG_NM_TO_METER);
1112   ss.str("");
1113   ss << aRunway->ident() << "-8";
1114   w = new BasicWaypt(p, ss.str(), NULL);
1115   w->setAltitude(thresholdElevFt + approachHeightFt, RESTRICT_AT);
1116   wpts.push_back(w);
1117   
1118   p = aRunway->pointOnCenterline(-glideslopeDistanceM);    
1119   ss.str("");
1120   ss << aRunway->ident() << "-GS";
1121   w = new BasicWaypt(p, ss.str(), NULL);
1122   w->setAltitude(thresholdElevFt + approachHeightFt, RESTRICT_AT);
1123   wpts.push_back(w);
1124     
1125   BOOST_FOREACH(Waypt* w, wpts) {
1126     w->setFlag(WPT_APPROACH);
1127     w->setFlag(WPT_GENERATED);
1128   }
1129   
1130   return Approach::createTempApproach("DEFAULT", aRunway, wpts);
1131 }
1132
1133 void FGRouteMgr::setApproach(const char* aIdent)
1134 {
1135   FGAirport* apt = _plan->destinationAirport();
1136   if (!strcmp(aIdent, "DEFAULT")) {
1137     double enrouteCourse = -1.0;
1138     if (_plan->departureAirport()) {
1139       enrouteCourse = SGGeodesy::courseDeg(_plan->departureAirport()->geod(), apt->geod());
1140     }
1141     
1142     _plan->setApproach(createDefaultApproach(_plan->destinationRunway(), enrouteCourse));
1143     return;
1144   }
1145   
1146   if (!apt || (aIdent == NULL)) {
1147     _plan->setApproach(NULL);
1148   } else {
1149     _plan->setApproach(apt->findApproachWithIdent(aIdent));
1150   }
1151 }
1152
1153 const char* FGRouteMgr::getSTAR() const
1154 {
1155   if (_plan && _plan->star()) {
1156     return _plan->star()->ident().c_str();
1157   }
1158   
1159   return "";
1160 }
1161
1162 void FGRouteMgr::setSTAR(const char* aIdent)
1163 {
1164   FGAirport* apt = _plan->destinationAirport();
1165   if (!apt || (aIdent == NULL)) {
1166     _plan->setSTAR((STAR*) NULL);
1167     return;
1168   } 
1169   
1170   string ident(aIdent);
1171   size_t hyphenPos = ident.find('-');
1172   if (hyphenPos != string::npos) {
1173     string starIdent = ident.substr(0, hyphenPos);
1174     string transIdent = ident.substr(hyphenPos + 1);
1175     
1176     STAR* star = apt->findSTARWithIdent(starIdent);
1177     Transition* trans = star ? star->findTransitionByName(transIdent) : NULL;
1178     _plan->setSTAR(trans);
1179   } else {
1180     _plan->setSTAR(apt->findSTARWithIdent(aIdent));
1181   }
1182 }
1183
1184 WayptRef FGRouteMgr::waypointFromString(const std::string& target)
1185 {
1186   return _plan->waypointFromString(target);
1187 }
1188
1189 double FGRouteMgr::getDepartureFieldElevation() const
1190 {
1191   if (!_plan || !_plan->departureAirport()) {
1192     return 0.0;
1193   }
1194   
1195   return _plan->departureAirport()->elevation();
1196 }
1197
1198 double FGRouteMgr::getDestinationFieldElevation() const
1199 {
1200   if (!_plan || !_plan->destinationAirport()) {
1201     return 0.0;
1202   }
1203   
1204   return _plan->destinationAirport()->elevation();
1205 }
1206
1207 SGPropertyNode_ptr FGRouteMgr::wayptNodeAtIndex(int index) const
1208 {
1209   if ((index < 0) || (index >= numWaypts())) {
1210     throw sg_range_exception("waypt index out of range", "FGRouteMgr::wayptAtIndex");
1211   }
1212   
1213   return mirror->getChild("wp", index);
1214 }