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