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