12#include <simgear/compiler.h>
15#include <simgear/misc/sg_path.hxx>
16#include <simgear/misc/strutils.hxx>
17#include <simgear/structure/commands.hxx>
18#include <simgear/structure/exception.hxx>
20#include <simgear/timing/sg_time.hxx>
21#include <simgear/sg_inlines.h>
36#define RM "/autopilot/route-manager/"
40namespace su = simgear::strutils;
45 SGPath path = SGPath::fromUtf8(arg->getStringValue(
"path"));
46 return self->loadRoute(path);
52 SGPath path = SGPath::fromUtf8(arg->getStringValue(
"path"));
53 const SGPath authorizedPath = SGPath(path).validate(
true );
55 if (!authorizedPath.isNull()) {
56 return self->saveRoute(authorizedPath);
59 "The route manager was asked to write the flightplan to '" +
60 path.utf8Str() +
"', but this path is not authorized for writing. " +
61 "Please choose another location, for instance in the $FG_HOME/Export "
62 "folder (" + (
globals->get_fg_home() /
"Export").utf8Str() +
").";
64 SG_LOG(SG_AUTOPILOT, SG_ALERT, msg);
74 bool activate = arg->getBoolValue(
"activate",
true);
94 int index = arg->getIntValue(
"index");
95 if ((index < 0) || (index >= self->numLegs())) {
99 self->jumpToIndex(index);
106 const bool haveIndex = arg->hasChild(
"index");
107 int index = arg->getIntValue(
"index");
109 std::string ident(arg->getStringValue(
"id"));
110 int alt = arg->getIntValue(
"altitude-ft", -999);
111 int ias = arg->getIntValue(
"speed-knots", -999);
116 SGGeod pos = SGGeod::invalid();
117 if (arg->hasChild(
"longitude-deg")) {
118 pos = SGGeod::fromDeg(arg->getDoubleValue(
"longitude-deg"),
119 arg->getDoubleValue(
"latitude-deg"));
122 if (arg->hasChild(
"navaid")) {
123 if (!pos.isValid()) {
124 pos = self->flightPlan()->vicinityForInsertIndex(haveIndex ? index : -1 );
132 SG_LOG(SG_AUTOPILOT, SG_WARN,
"Unable to find navaid with ident:" << arg->getStringValue(
"navaid"));
136 if (arg->hasChild(
"navaid", 1)) {
140 SG_LOG( SG_AUTOPILOT, SG_INFO,
"Unable to find FGPositioned with ident:" << arg->getStringValue(
"navaid[1]"));
144 double r1 = arg->getDoubleValue(
"radial"),
145 r2 = arg->getDoubleValue(
"radial[1]");
148 bool ok = SGGeodesy::radialIntersection(
p->geod(), r1,
p2->geod(), r2, intersection);
150 SG_LOG(SG_AUTOPILOT, SG_INFO,
"no valid intersection for:" <<
p->ident()
151 <<
"," <<
p2->ident());
155 std::string
name =
p->ident() +
"-" +
p2->ident();
157 }
else if (arg->hasChild(
"offset-nm") && arg->hasChild(
"radial")) {
159 double radial = arg->getDoubleValue(
"radial");
160 double distanceNm = arg->getDoubleValue(
"offset-nm");
166 }
else if (arg->hasChild(
"airport")) {
169 SG_LOG(SG_AUTOPILOT, SG_INFO,
"no such airport" << arg->getStringValue(
"airport"));
173 if (arg->hasChild(
"runway")) {
175 SG_LOG(SG_AUTOPILOT, SG_INFO,
"No runway: " << arg->getStringValue(
"runway") <<
" at " << apt->
ident());
184 }
else if (arg->hasChild(
"text")) {
185 const auto t = su::strip(arg->getStringValue(
"text"));
188 wp = self->flightPlan()->waypointFromString(t, pos);
190 const int searchIndex = haveIndex ? index : -1;
191 wp = self->waypointFromString(t, searchIndex);
195 SG_LOG(SG_AUTOPILOT, SG_WARN,
"insert-waypoint failed: couldn't parse waypoint from '" << t <<
"'");
198 }
else if (pos.isValid()) {
205 FlightPlan::Leg* leg = self->flightPlan()->insertWayptAtIndex(wp, index);
220 int index = arg->getIntValue(
"index");
221 self->removeLegAtIndex(index);
231 listener =
new InputListener(
this);
232 input->setStringValue(
"");
233 input->addChangeListener(listener);
235 SGCommandMgr* cmdMgr =
globals->get_commands();
236 cmdMgr->addCommand(
"define-user-waypoint",
this, &FGRouteMgr::commandDefineUserWaypoint);
237 cmdMgr->addCommand(
"delete-user-waypoint",
this, &FGRouteMgr::commandDeleteUserWaypoint);
251 input->removeChangeListener(listener);
255 _plan->removeDelegate(
this);
258 SGCommandMgr* cmdMgr =
globals->get_commands();
259 cmdMgr->removeCommand(
"define-user-waypoint");
260 cmdMgr->removeCommand(
"delete-user-waypoint");
261 cmdMgr->removeCommand(
"load-flightplan");
262 cmdMgr->removeCommand(
"save-flightplan");
263 cmdMgr->removeCommand(
"activate-flightplan");
264 cmdMgr->removeCommand(
"clear-flightplan");
265 cmdMgr->removeCommand(
"set-active-waypt");
266 cmdMgr->removeCommand(
"insert-waypt");
267 cmdMgr->removeCommand(
"delete-waypt");
274 magvar =
fgGetNode(
"/environment/magnetic-variation-deg",
true);
277 departure->tie(
"airport", SGStringValueMethods<FGRouteMgr>(*
this,
278 &FGRouteMgr::getDepartureICAO, &FGRouteMgr::setDepartureICAO));
279 departure->tie(
"runway", SGStringValueMethods<FGRouteMgr>(*
this,
280 &FGRouteMgr::getDepartureRunway,
281 &FGRouteMgr::setDepartureRunway));
282 departure->tie(
"sid", SGStringValueMethods<FGRouteMgr>(*
this,
284 &FGRouteMgr::setSID));
286 departure->tie(
"name", SGStringValueMethods<FGRouteMgr>(*
this,
287 &FGRouteMgr::getDepartureName,
nullptr));
288 departure->tie(
"field-elevation-ft", SGRawValueMethods<FGRouteMgr, double>(*
this,
289 &FGRouteMgr::getDepartureFieldElevation,
nullptr));
290 departure->getChild(
"etd", 0,
true);
291 departure->getChild(
"takeoff-time", 0,
true);
294 destination->getChild(
"airport", 0,
true);
296 destination->tie(
"airport", SGStringValueMethods<FGRouteMgr>(*
this,
297 &FGRouteMgr::getDestinationICAO, &FGRouteMgr::setDestinationICAO));
298 destination->tie(
"runway", SGStringValueMethods<FGRouteMgr>(*
this,
299 &FGRouteMgr::getDestinationRunway,
300 &FGRouteMgr::setDestinationRunway));
301 destination->tie(
"star", SGStringValueMethods<FGRouteMgr>(*
this,
302 &FGRouteMgr::getSTAR,
303 &FGRouteMgr::setSTAR));
304 destination->tie(
"approach", SGStringValueMethods<FGRouteMgr>(*
this,
305 &FGRouteMgr::getApproach,
306 &FGRouteMgr::setApproach));
308 destination->tie(
"name", SGStringValueMethods<FGRouteMgr>(*
this,
309 &FGRouteMgr::getDestinationName,
nullptr));
310 destination->tie(
"field-elevation-ft", SGRawValueMethods<FGRouteMgr, double>(*
this,
311 &FGRouteMgr::getDestinationFieldElevation,
nullptr));
313 destination->getChild(
"eta", 0,
true);
314 destination->getChild(
"eta-seconds", 0,
true);
315 destination->getChild(
"touchdown-time", 0,
true);
318 alternate->tie(
"airport", SGStringValueMethods<FGRouteMgr>(*
this,
319 &FGRouteMgr::getAlternate,
320 &FGRouteMgr::setAlternate));
321 alternate->tie(
"name", SGStringValueMethods<FGRouteMgr>(*
this,
322 &FGRouteMgr::getAlternateName,
nullptr));
325 cruise->tie(
"altitude-ft", SGRawValueMethods<FGRouteMgr, int>(*
this,
326 &FGRouteMgr::getCruiseAltitudeFt,
327 &FGRouteMgr::setCruiseAltitudeFt));
328 cruise->tie(
"flight-level", SGRawValueMethods<FGRouteMgr, int>(*
this,
329 &FGRouteMgr::getCruiseFlightLevel,
330 &FGRouteMgr::setCruiseFlightLevel));
331 cruise->tie(
"speed-kts", SGRawValueMethods<FGRouteMgr, int>(*
this,
332 &FGRouteMgr::getCruiseSpeedKnots,
333 &FGRouteMgr::setCruiseSpeedKnots));
334 cruise->tie(
"mach", SGRawValueMethods<FGRouteMgr, double>(*
this,
335 &FGRouteMgr::getCruiseSpeedMach,
336 &FGRouteMgr::setCruiseSpeedMach));
338 totalDistance =
fgGetNode(
RM "total-distance",
true);
339 totalDistance->setDoubleValue(0.0);
340 distanceToGo =
fgGetNode(
RM "distance-remaining-nm",
true);
341 distanceToGo->setDoubleValue(0.0);
344 ete->setDoubleValue(0.0);
346 elapsedFlightTime =
fgGetNode(
RM "flight-time",
true);
347 elapsedFlightTime->setDoubleValue(0.0);
350 active->setBoolValue(
false);
353 airborne->setBoolValue(
false);
356 _flightplanChanged =
fgGetNode(
RM "signals/flightplan-changed",
true);
360 _currentWpt->setAttribute(SGPropertyNode::LISTENER_SAFE,
true);
361 _currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
365 wp0->getChild(
"id", 0,
true);
366 wp0->getChild(
"dist", 0,
true);
367 wp0->getChild(
"eta", 0,
true);
368 wp0->getChild(
"eta-seconds", 0,
true);
369 wp0->getChild(
"bearing-deg", 0,
true);
372 wp1->getChild(
"id", 0,
true);
373 wp1->getChild(
"dist", 0,
true);
374 wp1->getChild(
"eta", 0,
true);
375 wp1->getChild(
"eta-seconds", 0,
true);
378 wpn->getChild(
"dist", 0,
true);
379 wpn->getChild(
"eta", 0,
true);
380 wpn->getChild(
"eta-seconds", 0,
true);
389 _plan->setIdent(
"default-flightplan");
391 SGPath path = SGPath::fromUtf8(_pathNode->getStringValue());
392 if (!path.isNull()) {
393 SG_LOG(SG_AUTOPILOT, SG_INFO,
"loading flight-plan from: " << path);
397 _isRoute->setBoolValue(_plan->isRoute());
404 for (
const auto& wpStr : *waypoints) {
407 _plan->insertWayptAtIndex(w, -1);
409 SG_LOG(SG_AUTOPILOT, SG_WARN,
"Failed to create waypoint from '" << wpStr <<
"'");
416 weightOnWheels =
fgGetNode(
"/gear/gear[0]/wow",
true);
417 groundSpeed =
fgGetNode(
"/velocities/groundspeed-kt",
true);
427 return active->getBoolValue();
436 return _plan->save(
p);
463 _plan->removeDelegate(
this);
469 active->setBoolValue(
false);
473 _plan->addDelegate(
this);
474 _isRoute->setBoolValue(_plan->isRoute());
475 _flightplanChanged->fireValueChanged();
481 currentWaypointChanged();
484void FGRouteMgr::departureChanged()
487 FGDialog* rmDlg = gui ? gui->getDialog(
"route-manager") : NULL;
489 rmDlg->runCallback(
"departure-changed");
493void FGRouteMgr::arrivalChanged()
495 auto gui =
globals->get_subsystem<NewGUI>();
496 FGDialog* rmDlg = gui ? gui->getDialog(
"route-manager") : NULL;
498 rmDlg->runCallback(
"arrival-changed");
509 double gs = groundSpeed->getDoubleValue();
510 if (airborne->getBoolValue()) {
511 time_t now =
globals->get_time_params()->get_cur_time();
512 elapsedFlightTime->setDoubleValue(difftime(now, _takeoffTime));
514 if (weightOnWheels->getBoolValue()) {
516 destination->setIntValue(
"touchdown-time", now);
517 airborne->setBoolValue(
false);
520 if (weightOnWheels->getBoolValue() || (gs < 40)) {
523 airborne->setBoolValue(
true);
524 _takeoffTime =
globals->get_time_params()->get_cur_time();
525 departure->setIntValue(
"takeoff-time", _takeoffTime);
529 if (!active->getBoolValue()) {
534 SGGeod currentPos =
globals->get_aircraft_position();
546 SGGeod wpPos = _routePath->positionForIndex(_plan->currentIndex());
547 double courseDeg, az2, distanceM;
548 SGGeodesy::inverse(currentPos, wpPos, courseDeg, az2, distanceM);
551 wp0->setDoubleValue(
"dist", distanceM * SG_METER_TO_NM);
552 wp0->setDoubleValue(
"true-bearing-deg", courseDeg);
553 courseDeg -= magvar->getDoubleValue();
554 wp0->setDoubleValue(
"bearing-deg", courseDeg);
555 setETAPropertyFromDistance(wp0, distanceM);
557 double totalPathDistanceNm = _plan->totalDistanceNm();
558 double totalDistanceRemaining = distanceM * SG_METER_TO_NM;
564 wp0->setDoubleValue(
"distance-along-route-nm",
566 wp0->setDoubleValue(
"remaining-distance-nm",
571 wpPos = _routePath->positionForIndex(_plan->currentIndex() + 1);
572 SGGeodesy::inverse(currentPos, wpPos, courseDeg, az2, distanceM);
574 wp1->setDoubleValue(
"dist", distanceM * SG_METER_TO_NM);
575 wp1->setDoubleValue(
"true-bearing-deg", courseDeg);
576 courseDeg -= magvar->getDoubleValue();
577 wp1->setDoubleValue(
"bearing-deg", courseDeg);
578 setETAPropertyFromDistance(wp1, distanceM);
579 wp1->setDoubleValue(
"distance-along-route-nm",
581 wp1->setDoubleValue(
"remaining-distance-nm",
585 distanceToGo->setDoubleValue(totalDistanceRemaining);
586 wpn->setDoubleValue(
"dist", totalDistanceRemaining);
587 ete->setDoubleValue(totalDistanceRemaining / gs * 3600.0);
588 setETAPropertyFromDistance(wpn, totalDistanceRemaining);
601 if (_plan && _plan->currentLeg()) {
602 return _plan->currentLeg()->waypoint();
614 return _plan->currentIndex();
620 throw sg_range_exception(
"wayptAtindex: no flightplan");
623 return _plan->legAtIndex(index)->waypoint();
629 return _plan->numLegs();
635void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp,
double aDistance)
637 double speed = groundSpeed->getDoubleValue();
639 aProp->setStringValue(
"--:--");
644 double eta = aDistance * SG_METER_TO_NM / speed;
645 aProp->getChild(
"eta-seconds")->setIntValue( eta * 3600 );
646 if ( eta >= 100.0 ) {
650 if ( eta < (1.0/6.0) ) {
654 int major = (int)eta,
655 minor = (
int)((eta - (int)eta) * 60.0);
656 snprintf( eta_str, 64,
"%d:%02d", major, minor );
657 aProp->getChild(
"eta")->setStringValue( eta_str );
666 _plan->deleteIndex(aIndex);
669void FGRouteMgr::waypointsChanged()
672 _edited->fireValueChanged();
676void FGRouteMgr::update_mirror()
679 mirror->removeChildren(
"wp");
680 auto gui =
globals->get_subsystem<NewGUI>();
681 FGDialog* rmDlg = gui ? gui->getDialog(
"route-manager") : NULL;
684 mirror->setIntValue(
"num", 0);
686 rmDlg->updateValues();
691 int num = _plan->numLegs();
693 for (
int i = 0;
i < num;
i++) {
696 SGPropertyNode *prop = mirror->getChild(
"wp",
i, 1);
698 const SGGeod& pos(wp->position());
699 prop->setStringValue(
"id", wp->ident());
700 prop->setDoubleValue(
"longitude-deg", pos.getLongitudeDeg());
701 prop->setDoubleValue(
"latitude-deg",pos.getLatitudeDeg());
705 prop->setDoubleValue(
"leg-bearing-true-deg", leg->
courseDeg());
706 prop->setDoubleValue(
"leg-distance-nm", leg->
distanceNm());
711 prop->setDoubleValue(
"altitude-m", ft * SG_FEET_TO_METER);
712 prop->setDoubleValue(
"altitude-ft", ft);
713 prop->setIntValue(
"flight-level",
static_cast<int>(ft / 1000) * 10);
715 prop->setDoubleValue(
"altitude-m", -9999.9);
716 prop->setDoubleValue(
"altitude-ft", -9999.9);
720 prop->setDoubleValue(
"speed-mach", leg->
speedMach());
722 prop->setDoubleValue(
"speed-kts", leg->
speedKts());
726 prop->setBoolValue(
"arrival",
true);
730 prop->setBoolValue(
"departure",
true);
734 prop->setBoolValue(
"missed-approach",
true);
741 mirror->setIntValue(
"num", _plan->numLegs());
744 rmDlg->updateValues();
747 totalDistance->setDoubleValue(_plan->totalDistanceNm());
758void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
760 const auto input = su::uppercase(su::strip(prop->getStringValue()));
765 if (input ==
"@CLEAR") {
767 }
else if (input ==
"@ACTIVATE") {
769 }
else if (input ==
"@LOAD") {
770 SGPath path = SGPath::fromUtf8(mgr->_pathNode->getStringValue());
771 mgr->loadRoute(path);
772 }
else if (input ==
"@SAVE") {
773 SGPath path = SGPath::fromUtf8(mgr->_pathNode->getStringValue());
774 const SGPath authorizedPath = SGPath(path).validate(
true );
776 if (!authorizedPath.isNull()) {
777 mgr->saveRoute(authorizedPath);
780 "The route manager was asked to write the flightplan to '" +
781 path.utf8Str() +
"', but this path is not authorized for writing. " +
782 "Please choose another location, for instance in the $FG_HOME/Export "
786 SG_LOG(SG_AUTOPILOT, SG_ALERT, msg);
790 }
else if (input ==
"@NEXT") {
791 mgr->jumpToIndex(mgr->currentIndex() + 1);
792 }
else if (input ==
"@PREVIOUS") {
793 mgr->jumpToIndex(mgr->currentIndex() - 1);
794 }
else if (su::starts_with(input,
"@JUMP")) {
795 const int index = stoi(input.substr(5));
796 mgr->jumpToIndex(index);
797 }
else if (su::starts_with(input,
"@DELETE")) {
798 const int index = stoi(input.substr(7));
799 mgr->removeLegAtIndex(index);
800 }
else if (su::starts_with(input,
"@INSERT")) {
802 const auto arg = input.substr(7);
803 const int index = stoi(arg, &pos, 10);
804 if (arg.at(pos) !=
':') {
805 SG_LOG(SG_AUTOPILOT, SG_WARN,
"@INSERT: couldn't parse index from:'" << input <<
"'");
809 const auto wpString = su::strip(arg.substr(pos + 1));
810 const auto newWp = mgr->waypointFromString(wpString, index);
811 mgr->flightPlan()->insertWayptAtIndex(newWp, index);
813 const auto newWp = mgr->waypointFromString(input, -1);
814 mgr->flightPlan()->insertWayptAtIndex(newWp, -1);
821 SG_LOG(SG_AUTOPILOT, SG_WARN,
"::activate, no flight plan defined");
826 SG_LOG(SG_AUTOPILOT, SG_WARN,
"duplicate route-activation, no-op");
831 active->setBoolValue(
true);
832 SG_LOG(SG_AUTOPILOT, SG_INFO,
"route-manager, activate route ok");
842 SG_LOG(SG_AUTOPILOT, SG_INFO,
"deactivating flight plan");
843 active->setBoolValue(
false);
855 if ((index < -1) || (index >= _plan->numLegs())) {
856 SG_LOG(SG_AUTOPILOT, SG_WARN,
"FGRouteMgr::jumpToIndex: ignoring invalid index:" << index);
860 _plan->setCurrentIndex(index);
863void FGRouteMgr::currentWaypointChanged()
868 wp0->getChild(
"id")->setStringValue(cur ? cur->
ident() :
"");
869 wp1->getChild(
"id")->setStringValue(next ? next->
waypoint()->
ident() :
"");
871 _currentWpt->fireValueChanged();
872 SG_LOG(SG_AUTOPILOT, SG_INFO,
"route manager, current-wp is now " <<
currentIndex());
875std::string FGRouteMgr::getDepartureICAO()
const
877 if (!_plan || !_plan->departureAirport()) {
881 return _plan->departureAirport()->ident();
884std::string FGRouteMgr::getDepartureName()
const
886 if (!_plan || !_plan->departureAirport()) {
890 return _plan->departureAirport()->name();
893std::string FGRouteMgr::getDepartureRunway()
const
895 if (_plan && _plan->departureRunway()) {
896 return _plan->departureRunway()->ident();
902void FGRouteMgr::setDepartureRunway(
const std::string& aIdent)
908 FGAirport* apt = _plan->departureAirport();
909 if (!apt || aIdent.empty()) {
910 _plan->setDeparture(apt);
916void FGRouteMgr::setDepartureICAO(
const std::string& aIdent)
922 if (aIdent.length() < 3) {
923 _plan->setDeparture((FGAirport*)
nullptr);
929std::string FGRouteMgr::getSID()
const
931 if (_plan && _plan->sid()) {
932 return _plan->sid()->ident();
940 double rawDiff = b - a;
941 SG_NORMALIZE_RANGE(rawDiff, -180.0, 180.0);
951 double runwayElevFt = aRunway->
end().getElevationFt();
953 std::ostringstream ss;
954 ss << aRunway->
ident() <<
"-3";
958 w->setAltitude(runwayElevFt + 3000.0,
RESTRICT_AT);
962 ss << aRunway->
ident() <<
"-6";
965 w->setAltitude(runwayElevFt + 6000.0,
RESTRICT_AT);
968 if (enrouteCourse >= 0.0) {
973 while (fabs(diff =
headingDiffDeg(course, enrouteCourse)) > 45.0) {
975 course += copysign(45.0, diff);
977 ss <<
"DEP-" << index++;
978 SGGeod pos = wpts.back()->position();
979 pos = SGGeodesy::direct(pos, course, 3.0 * SG_NM_TO_METER);
986 ss << aRunway->
ident() <<
"-9";
989 w->setAltitude(runwayElevFt + 9000.0,
RESTRICT_AT);
993 for (
Waypt* w : wpts) {
1001void FGRouteMgr::setSID(
const std::string& aIdent)
1007 FGAirport* apt = _plan->departureAirport();
1008 if (!apt || aIdent.empty()) {
1009 _plan->setSID((flightgear::SID*) NULL);
1013 if (aIdent ==
"DEFAULT") {
1014 double enrouteCourse = -1.0;
1015 if (_plan->destinationAirport()) {
1016 enrouteCourse = SGGeodesy::courseDeg(apt->
geod(), _plan->destinationAirport()->geod());
1023 size_t hyphenPos = aIdent.find(
'-');
1024 if (hyphenPos != string::npos) {
1025 string sidIdent = aIdent.substr(0, hyphenPos);
1026 string transIdent = aIdent.substr(hyphenPos + 1);
1030 _plan->setSID(trans);
1036std::string FGRouteMgr::getDestinationICAO()
const
1038 if (!_plan || !_plan->destinationAirport()) {
1042 return _plan->destinationAirport()->ident();
1045std::string FGRouteMgr::getDestinationName()
const
1047 if (!_plan || !_plan->destinationAirport()) {
1051 return _plan->destinationAirport()->name();
1054void FGRouteMgr::setDestinationICAO(
const std::string& aIdent)
1060 if (aIdent.length() < 3) {
1061 _plan->setDestination((FGAirport*) NULL);
1067std::string FGRouteMgr::getDestinationRunway()
const
1069 if (_plan && _plan->destinationRunway()) {
1070 return _plan->destinationRunway()->ident();
1076void FGRouteMgr::setDestinationRunway(
const std::string& aIdent)
1082 FGAirport* apt = _plan->destinationAirport();
1083 if (!apt || aIdent.empty()) {
1084 _plan->setDestination(apt);
1090std::string FGRouteMgr::getApproach()
const
1092 if (_plan && _plan->approach()) {
1093 return _plan->approach()->ident();
1105 double thresholdElevFt = aRunway->
threshold().getElevationFt();
1106 const double approachHeightFt = 2000.0;
1107 double glideslopeDistanceM = (approachHeightFt * SG_FEET_TO_METER) /
1108 tan(3.0 * SG_DEGREES_TO_RADIANS);
1110 std::ostringstream ss;
1111 ss << aRunway->
ident() <<
"-12";
1115 w->setAltitude(thresholdElevFt + 4000,
RESTRICT_AT);
1120 if (aEnrouteCourse >= 0.0) {
1125 while (fabs(diff =
headingDiffDeg(aEnrouteCourse, course)) > 45.0) {
1127 course -= copysign(45.0, diff);
1129 ss <<
"APP-" << index++;
1130 SGGeod pos = wpts.front()->position();
1131 pos = SGGeodesy::direct(pos, course + 180.0, 3.0 * SG_NM_TO_METER);
1133 wpts.insert(wpts.begin(), w);
1139 ss << aRunway->
ident() <<
"-8";
1141 w->setAltitude(thresholdElevFt + approachHeightFt,
RESTRICT_AT);
1146 ss << aRunway->
ident() <<
"-GS";
1148 w->setAltitude(thresholdElevFt + approachHeightFt,
RESTRICT_AT);
1151 for (
Waypt* w : wpts) {
1159void FGRouteMgr::setApproach(
const std::string& aIdent)
1165 FGAirport* apt = _plan->destinationAirport();
1166 if (aIdent ==
"DEFAULT") {
1167 double enrouteCourse = -1.0;
1168 if (_plan->departureAirport()) {
1169 enrouteCourse = SGGeodesy::courseDeg(_plan->departureAirport()->geod(), apt->
geod());
1176 if (!apt || aIdent.empty()) {
1177 _plan->setApproach(
static_cast<Approach*
>(
nullptr));
1183std::string FGRouteMgr::getSTAR()
const
1185 if (_plan && _plan->star()) {
1186 return _plan->star()->ident();
1192void FGRouteMgr::setSTAR(
const std::string& aIdent)
1198 FGAirport* apt = _plan->destinationAirport();
1199 if (!apt || aIdent.empty()) {
1200 _plan->setSTAR((STAR*) NULL);
1204 string ident(aIdent);
1205 size_t hyphenPos = ident.find(
'-');
1206 if (hyphenPos != string::npos) {
1207 string starIdent = ident.substr(0, hyphenPos);
1208 string transIdent = ident.substr(hyphenPos + 1);
1212 _plan->setSTAR(trans);
1220 return _plan->waypointFromString(target, _plan->vicinityForInsertIndex(insertPosition));
1223double FGRouteMgr::getDepartureFieldElevation()
const
1225 if (!_plan || !_plan->departureAirport()) {
1229 return _plan->departureAirport()->elevation();
1232double FGRouteMgr::getDestinationFieldElevation()
const
1234 if (!_plan || !_plan->destinationAirport()) {
1238 return _plan->destinationAirport()->elevation();
1241int FGRouteMgr::getCruiseAltitudeFt()
const
1246 return _plan->cruiseAltitudeFt();
1249void FGRouteMgr::setCruiseAltitudeFt(
int ft)
1254 _plan->setCruiseAltitudeFt(ft);
1257int FGRouteMgr::getCruiseFlightLevel()
const
1262 return _plan->cruiseFlightLevel();
1265void FGRouteMgr::setCruiseFlightLevel(
int fl)
1270 _plan->setCruiseFlightLevel(fl);
1273int FGRouteMgr::getCruiseSpeedKnots()
const
1278 return _plan->cruiseSpeedKnots();
1281void FGRouteMgr::setCruiseSpeedKnots(
int kts)
1286 _plan->setCruiseSpeedKnots(kts);
1289double FGRouteMgr::getCruiseSpeedMach()
const
1294 return _plan->cruiseSpeedMach();
1297void FGRouteMgr::setCruiseSpeedMach(
double m)
1302 _plan->setCruiseSpeedMach(m);
1305string FGRouteMgr::getAlternate()
const
1307 if (!_plan || !_plan->alternate())
1310 return _plan->alternate()->ident();
1313std::string FGRouteMgr::getAlternateName()
const
1315 if (!_plan || !_plan->alternate())
1318 return _plan->alternate()->name();
1321void FGRouteMgr::setAlternate(
const string &icao)
1327 alternate->fireValueChanged();
1332 if ((index < 0) || (index >=
numWaypts())) {
1333 throw sg_range_exception(
"waypt index out of range",
"FGRouteMgr::wayptAtIndex");
1336 return mirror->getChild(
"wp", index);
1339bool FGRouteMgr::commandDefineUserWaypoint(
const SGPropertyNode * arg, SGPropertyNode * root)
1341 std::string ident = arg->getStringValue(
"ident");
1342 if (ident.empty()) {
1343 SG_LOG(SG_AUTOPILOT, SG_WARN,
"missing ident defining user waypoint");
1350 if (!dups.empty()) {
1351 SG_LOG(SG_AUTOPILOT, SG_WARN,
"defineUserWaypoint: non-unique waypoint identifier:" << ident);
1355 const bool temporary = arg->getBoolValue(
"temporary");
1356 SGGeod pos(SGGeod::fromDeg(arg->getDoubleValue(
"longitude-deg"),
1357 arg->getDoubleValue(
"latitude-deg")));
1358 const auto name = arg->getStringValue(
"name");
1364bool FGRouteMgr::commandDeleteUserWaypoint(
const SGPropertyNode * arg, SGPropertyNode * root)
1366 std::string ident = arg->getStringValue(
"ident");
1367 if (ident.empty()) {
1368 SG_LOG(SG_AUTOPILOT, SG_WARN,
"missing ident deleting user waypoint");
1375 SG_LOG(SG_AUTOPILOT, SG_WARN,
"no user waypoint with ident:" << ident);
1385 SGSubsystemMgr::GENERAL,
1386 {{
"gui", SGSubsystemMgr::Dependency::HARD}});
const FGAirport * fgFindAirportID(const std::string &id)
FGRunwayRef getRunwayByIdent(const std::string &aIdent) const
static FGAirportRef findByIdent(const std::string &aIdent)
Helper to look up an FGAirport instance by unique ident.
flightgear::STAR * findSTARWithIdent(const std::string &aIdent) const
bool hasRunwayWithIdent(const std::string &aIdent) const
flightgear::Approach * findApproachWithIdent(const std::string &aIdent) const
flightgear::SID * findSIDWithIdent(const std::string &aIdent) const
An XML-configured dialog box.
const SGPath & get_fg_home() const
static FGPositionedRef createWaypoint(FGPositioned::Type aType, const std::string &aIdent, const SGGeod &aPos, bool isTemporary=false, const std::string &aName={})
static FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, Filter *aFilter=NULL)
virtual const SGGeod & geod() const
static FGPositionedRef findFirstWithIdent(const std::string &aIdent, Filter *aFilter)
static FGPositionedList findAllWithIdent(const std::string &aIdent, Filter *aFilter=NULL, bool aExact=true)
Find all items with the specified ident.
const std::string & ident() const
static bool deleteWaypoint(FGPositionedRef aWpt)
Top level route manager class.
void deactivate()
deactivate the route if active
flightgear::WayptRef waypointFromString(const std::string &target, int insertPosition)
Buiild a waypoint from a string description.
flightgear::Waypt * currentWaypt() const
bool isRouteActive() const
void setFlightPlan(const flightgear::FlightPlanRef &plan)
flightgear::Waypt * wayptAtIndex(int index) const
bool activate()
Activate a built route.
void update(double dt) override
bool saveRoute(const SGPath &p)
void removeLegAtIndex(int aIndex)
SGPropertyNode_ptr wayptNodeAtIndex(int index) const
bool loadRoute(const SGPath &p)
void jumpToIndex(int index)
Set the current waypoint to the specified index.
flightgear::FlightPlanRef flightPlan() const
double headingDeg() const
Runway heading in degrees.
SGGeod pointOnCenterline(double aOffset) const
Retrieve a position on the extended centerline.
SGGeod end() const
Get the 'far' end - this is equivalent to calling pointOnCenterline(lengthFt());.
SGGeod threshold() const
Get the (possibly displaced) threshold point.
XML-configured GUI subsystem.
Describe an approach procedure, including the missed approach segment.
static Approach * createTempApproach(const std::string &aIdent, FGRunway *aRunway, const WayptVec &aPath)
Transition * findTransitionByName(const std::string &aIdent) const
Find an enroute transition waypoint by identifier.
flight-plan leg encapsulation
double distanceAlongRoute() const
RouteRestriction altitudeRestriction() const
void setSpeed(RouteRestriction ty, double speed, RouteUnits units=DEFAULT_UNITS)
RouteRestriction speedRestriction() const
double distanceNm() const
void setAltitude(RouteRestriction ty, double alt, RouteUnits units=DEFAULT_UNITS)
static FlightPlanRef create()
create a FlightPlan with isRoute not set
bool load(const SGPath &p)
Waypoint based upon a navaid.
Waypoint based upon a runway.
static SID * createTempSID(const std::string &aIdent, FGRunway *aRunway, const WayptVec &aPath)
Abstract base class for waypoints (and things that are treated similarly by navigation systems).
virtual std::string ident() const
Identifier assoicated with the waypoint.
std::vector< std::string > string_list
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
SGSharedPtr< FlightPlan > FlightPlanRef
MessageBoxResult modalMessageBox(const std::string &caption, const std::string &msg, const std::string &moreText)
SGSharedPtr< FGPositioned > FGPositionedRef
SGSharedPtr< Waypt > WayptRef
@ WPT_MISS
segment is part of missed approach
@ WPT_GENERATED
waypoint was created automatically (not manually entered/loaded) for example waypoints from airway ro...
std::vector< WayptRef > WayptVec
@ SPEED_RESTRICT_MACH
encode an 'AT' restriction in Mach, not IAS
std::vector< FGPositionedRef > FGPositionedList
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
static bool commandClearFlightPlan(const SGPropertyNode *, SGPropertyNode *)
static double headingDiffDeg(double a, double b)
flightgear::Approach * createDefaultApproach(FGRunway *aRunway, double aEnrouteCourse)
static bool commandInsertWaypt(const SGPropertyNode *arg, SGPropertyNode *)
flightgear::SID * createDefaultSID(FGRunway *aRunway, double enrouteCourse)
static bool commandDeleteWaypt(const SGPropertyNode *arg, SGPropertyNode *)
static bool commandSaveFlightPlan(const SGPropertyNode *arg, SGPropertyNode *)
static bool commandActivateFlightPlan(const SGPropertyNode *arg, SGPropertyNode *)
static bool commandSetActiveWaypt(const SGPropertyNode *arg, SGPropertyNode *)
static bool commandLoadFlightPlan(const SGPropertyNode *arg, SGPropertyNode *)
SGSubsystemMgr::Registrant< FGRouteMgr > registrantFGRouteMgr(SGSubsystemMgr::GENERAL, {{"gui", SGSubsystemMgr::Dependency::HARD}})