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