29#include <simgear/math/sg_random.hxx>
30#include <simgear/sg_inlines.h>
31#include <simgear/math/sg_geodesy.hxx>
32#include <simgear/structure/exception.hxx>
41 if ((TTW <= 0.0) || (TTW >= 356400.5)) {
45 unsigned TTW_seconds = (unsigned) (TTW + 0.5);
46 unsigned TTW_minutes = 0;
47 unsigned TTW_hours = 0;
49 TTW_hours = TTW_seconds / 3600;
50 TTW_minutes = (TTW_seconds / 60) % 60;
51 TTW_seconds = TTW_seconds % 60;
53 static char TTW_str[16];
54 snprintf(TTW_str, 16,
"%02u:%02u:%02u",
55 TTW_hours, TTW_minutes, TTW_seconds);
63GPS::Config::Config() :
64 _enableTurnAnticipation(false),
66 _overflightDistance(0.02),
67 _overflightArmDistance(1.0),
68 _overflightArmAngle(90.0),
69 _waypointAlertTime(30.0),
70 _requireHardSurface(true),
71 _cdiMaxDeflectionNm(3.0),
72 _driveAutopilot(true),
73 _courseSelectable(false),
74 _followLegTrackToFix(true)
78void GPS::Config::bind(
GPS* aOwner, SGPropertyNode* aCfg)
80 aOwner->tie(aCfg,
"turn-rate-deg-sec", SGRawValuePointer<double>(&_turnRate));
81 aOwner->tie(aCfg,
"enable-fly-by", SGRawValuePointer<bool>(&_enableTurnAnticipation));
82 aOwner->tie(aCfg,
"wpt-alert-time", SGRawValuePointer<double>(&_waypointAlertTime));
83 aOwner->tie(aCfg,
"hard-surface-runways-only", SGRawValuePointer<bool>(&_requireHardSurface));
84 aOwner->tie(aCfg,
"cdi-max-deflection-nm", SGRawValuePointer<double>(&_cdiMaxDeflectionNm));
85 aOwner->tie(aCfg,
"drive-autopilot", SGRawValuePointer<bool>(&_driveAutopilot));
86 aOwner->tie(aCfg,
"course-selectable", SGRawValuePointer<bool>(&_courseSelectable));
87 aOwner->tie(aCfg,
"follow-leg-track-to-fix", SGRawValuePointer<bool>(&_followLegTrackToFix));
88 aOwner->tie(aCfg,
"over-flight-distance-nm", SGRawValuePointer<double>(&_overflightDistance));
89 aOwner->tie(aCfg,
"over-flight-arm-distance-nm", SGRawValuePointer<double>(&_overflightArmDistance));
90 aOwner->tie(aCfg,
"over-flight-arm-angle-deg", SGRawValuePointer<double>(&_overflightArmAngle));
91 aOwner->tie(aCfg,
"delegate-sequencing", SGRawValuePointer<bool>(&_delegateSequencing));
92 aOwner->tie(aCfg,
"max-fly-by-turn-angle-deg", SGRawValueMethods<GPS, double>(*aOwner, &
GPS::maxFlyByTurnAngleDeg, &GPS::setFlyByMaxTurnAngle));
95void GPS::setFlyByMaxTurnAngle(
double maxAngle)
97 _config.setMaxFlyByTurnAngle(maxAngle);
99 _route->setMaxFlyByTurnAngle(maxAngle);
104GPS::GPS ( SGPropertyNode *node,
bool defaultGPSMode) :
105 _selectedCourse(0.0),
108 _lastPosValid(false),
109 _defaultGPSMode(defaultGPSMode),
111 _name(node->getStringValue(
"name",
"gps")),
112 _num(node->getIntValue(
"number", 0)),
113 _callbackFlightPlanChanged(SGPropertyChangeCallback<
GPS>(this,&
GPS::routeManagerFlightPlanChanged,
114 fgGetNode(
"/autopilot/route-manager/signals/flightplan-changed", true))),
115 _callbackRouteActivated(SGPropertyChangeCallback<
GPS>(this,&
GPS::routeActivated,
116 fgGetNode(
"/autopilot/route-manager/active", true)))
118 string branch =
"/instrumentation/" + _name;
119 _gpsNode =
fgGetNode(branch, _num,
true );
120 _scratchNode = _gpsNode->getChild(
"scratch", 0,
true);
122 SGPropertyNode *wp_node = _gpsNode->getChild(
"wp", 0,
true);
123 _currentWayptNode = wp_node->getChild(
"wp", 1,
true);
126 _searchIsRoute =
false;
127 _searchHasNext =
false;
139 _magvar_node =
fgGetNode(
"/environment/magnetic-variation-deg",
true);
140 _serviceable_node = _gpsNode->getChild(
"serviceable", 0,
true);
141 _serviceable_node->setBoolValue(
true);
142 _electrical_node =
fgGetNode(
"/systems/electrical/outputs/gps",
true);
145 _raim_node = _gpsNode->getChild(
"raim", 0,
true);
146 _odometer_node = _gpsNode->getChild(
"odometer", 0,
true);
147 _trip_odometer_node = _gpsNode->getChild(
"trip-odometer", 0,
true);
148 _true_bug_error_node = _gpsNode->getChild(
"true-bug-error-deg", 0,
true);
149 _magnetic_bug_error_node = _gpsNode->getChild(
"magnetic-bug-error-deg", 0,
true);
150 _eastWestVelocity = _gpsNode->getChild(
"ew-velocity-msec", 0,
true);
151 _northSouthVelocity = _gpsNode->getChild(
"ns-velocity-msec", 0,
true);
155 SGPropertyNode* wp1Crs = _currentWayptNode->getChild(
"desired-course-deg", 0,
true);
156 wp1Crs->alias(_gpsNode->getChild(
"desired-course-deg", 0,
true),
true);
158 _tracking_bug_node = _gpsNode->getChild(
"tracking-bug", 0,
true);
162 _routeDistanceNm = _gpsNode->getChild(
"route-distance-nm", 0,
true);
163 _routeETE = _gpsNode->getChild(
"ETE", 0,
true);
167 SGPropertyNode* toFlag = _gpsNode->getChild(
"to-flag", 0,
true);
168 toFlag->alias(_currentWayptNode->getChild(
"to-flag"),
true);
170 SGPropertyNode* fromFlag = _gpsNode->getChild(
"from-flag", 0,
true);
171 fromFlag->alias(_currentWayptNode->getChild(
"from-flag"),
true);
174 _apDrivingFlag =
fgGetNode(
"/autopilot/settings/gps-driving-true-heading",
true);
175 _apTrueHeading =
fgGetNode(
"/autopilot/settings/true-heading-deg",
true);
190 _config.bind(
this, _gpsNode->getChild(
"config", 0,
true));
193 tie(_gpsNode,
"selected-course-deg", SGRawValueMethods<GPS, double>
194 (*
this, &GPS::getSelectedCourse, &GPS::setSelectedCourse));
196 tie(_gpsNode,
"desired-course-deg", SGRawValueMethods<GPS, double>
197 (*
this, &GPS::getDesiredCourse, NULL));
198 _desiredCourseNode = _gpsNode->getChild(
"desired-course-deg", 0,
true);
200 tieSGGeodReadOnly(_gpsNode, _indicated_pos,
"indicated-longitude-deg",
201 "indicated-latitude-deg",
"indicated-altitude-ft");
203 tie(_gpsNode,
"indicated-vertical-speed", SGRawValueMethods<GPS, double>
204 (*
this, &GPS::getVerticalSpeed, NULL));
205 tie(_gpsNode,
"indicated-track-true-deg", SGRawValueMethods<GPS, double>
206 (*
this, &GPS::getTrueTrack, NULL));
207 tie(_gpsNode,
"indicated-track-magnetic-deg", SGRawValueMethods<GPS, double>
208 (*
this, &GPS::getMagTrack, NULL));
209 tie(_gpsNode,
"indicated-ground-speed-kt", SGRawValueMethods<GPS, double>
210 (*
this, &GPS::getGroundspeedKts, NULL));
213 tie(_gpsNode,
"mode", SGRawValueMethods<GPS, const char*>(*
this, &GPS::getMode, NULL));
214 tie(_gpsNode,
"command", SGRawValueMethods<GPS, const char*>(*
this, &GPS::getCommand, &GPS::setCommand));
215 tieSGGeod(_scratchNode, _scratchPos,
"longitude-deg",
"latitude-deg",
"altitude-ft");
218 tie(_scratchNode,
"valid", SGRawValueMethods<GPS, bool>(*
this, &GPS::getScratchValid, NULL));
219 tie(_scratchNode,
"distance-nm", SGRawValueMethods<GPS, double>(*
this, &GPS::getScratchDistance, NULL));
220 tie(_scratchNode,
"true-bearing-deg", SGRawValueMethods<GPS, double>(*
this, &GPS::getScratchTrueBearing, NULL));
221 tie(_scratchNode,
"mag-bearing-deg", SGRawValueMethods<GPS, double>(*
this, &GPS::getScratchMagBearing, NULL));
222 tie(_scratchNode,
"has-next", SGRawValueMethods<GPS, bool>(*
this, &GPS::getScratchHasNext, NULL));
223 _scratchValid =
false;
225 _scratchNode->setStringValue(
"type",
"");
226 _scratchNode->setStringValue(
"query",
"");
229 SGPropertyNode *wp_node = _gpsNode->getChild(
"wp", 0,
true);
230 SGPropertyNode* wp0_node = wp_node->getChild(
"wp", 0,
true);
231 tieSGGeodReadOnly(wp0_node, _wp0_position,
"longitude-deg",
"latitude-deg",
"altitude-ft");
232 tie(wp0_node,
"ID", SGRawValueMethods<GPS, const char*>
233 (*
this, &GPS::getWP0Ident, NULL));
234 tie(wp0_node,
"name", SGRawValueMethods<GPS, const char*>
235 (*
this, &GPS::getWP0Name, NULL));
237 tie(_currentWayptNode,
"valid", SGRawValueMethods<GPS, bool>
238 (*
this, &GPS::getWP1IValid, NULL));
240 tie(_currentWayptNode,
"ID", SGRawValueMethods<GPS, const char*>
241 (*
this, &GPS::getWP1Ident, NULL));
242 tie(_currentWayptNode,
"name", SGRawValueMethods<GPS, const char*>
243 (*
this, &GPS::getWP1Name, NULL));
245 tie(_currentWayptNode,
"distance-nm", SGRawValueMethods<GPS, double>
246 (*
this, &GPS::getWP1Distance, NULL));
247 tie(_currentWayptNode,
"bearing-true-deg", SGRawValueMethods<GPS, double>
248 (*
this, &GPS::getWP1Bearing, NULL));
249 tie(_currentWayptNode,
"bearing-mag-deg", SGRawValueMethods<GPS, double>
250 (*
this, &GPS::getWP1MagBearing, NULL));
251 tie(_currentWayptNode,
"TTW-sec", SGRawValueMethods<GPS, double>
252 (*
this, &GPS::getWP1TTW, NULL));
253 tie(_currentWayptNode,
"TTW", SGRawValueMethods<GPS, const char*>
254 (*
this, &GPS::getWP1TTWString, NULL));
256 tie(_currentWayptNode,
"course-deviation-deg", SGRawValueMethods<GPS, double>
257 (*
this, &GPS::getWP1CourseDeviation, NULL));
258 tie(_currentWayptNode,
"course-error-nm", SGRawValueMethods<GPS, double>
259 (*
this, &GPS::getWP1CourseErrorNm, NULL));
260 tie(_currentWayptNode,
"to-flag", SGRawValueMethods<GPS, bool>
261 (*
this, &GPS::getWP1ToFlag, NULL));
262 tie(_currentWayptNode,
"from-flag", SGRawValueMethods<GPS, bool>
263 (*
this, &GPS::getWP1FromFlag, NULL));
266 tie(wp_node,
"leg-distance-nm", SGRawValueMethods<GPS, double>(*
this, &GPS::getLegDistance, NULL));
267 tie(wp_node,
"leg-true-course-deg", SGRawValueMethods<GPS, double>(*
this, &GPS::getLegCourse, NULL));
268 tie(wp_node,
"leg-mag-course-deg", SGRawValueMethods<GPS, double>(*
this, &GPS::getLegMagCourse, NULL));
271 tie(_gpsNode,
"cdi-deflection", SGRawValueMethods<GPS,double>
272 (*
this, &GPS::getCDIDeflection));
274 _currentWpLatNode = _currentWayptNode->getNode(
"latitude-deg",
true);
275 _currentWpLonNode = _currentWayptNode->getNode(
"longitude-deg",
true);
276 _currentWpAltNode = _currentWayptNode->getNode(
"altitude-ft",
true);
284 _currentWaypt = _prevWaypt =
nullptr;
286 _tiedProperties.Untie();
288 _currentWayptNode.clear();
289 _currentWpLatNode.clear();
290 _currentWpLonNode.clear();
291 _currentWpAltNode.clear();
298 _last_speed_kts = 0.0;
299 _last_pos = SGGeod();
300 _lastPosValid =
false;
301 _indicated_pos = SGGeod();
302 _last_vertical_speed = 0.0;
303 _last_true_track = 0.0;
304 _lastEWVelocity = _lastNSVelocity = 0.0;
305 _currentWaypt = _prevWaypt = NULL;
306 _legDistanceNm = -1.0;
308 _raim_node->setDoubleValue(0.0);
309 _indicated_pos = SGGeod();
310 _odometer_node->setDoubleValue(0);
311 _trip_odometer_node->setDoubleValue(0);
312 _tracking_bug_node->setDoubleValue(0);
313 _true_bug_error_node->setDoubleValue(0);
314 _magnetic_bug_error_node->setDoubleValue(0);
315 _northSouthVelocity->setDoubleValue(0.0);
316 _eastWestVelocity->setDoubleValue(0.0);
322 if (!_defaultGPSMode) {
326 bool elecOn = !_electrical_node->hasValue() || _electrical_node->getBoolValue();
327 if (!_serviceable_node->getBoolValue() || !elecOn) {
333 if (delta_time_sec <= 0.0) {
337 _raim_node->setDoubleValue(1.0);
338 _indicated_pos =
globals->get_aircraft_position();
339 updateBasicData(delta_time_sec);
342 if (_wayptController.get()) {
343 _wayptController->update(delta_time_sec);
344 updateCurrentWpNode(_wayptController->position());
345 _desiredCourse = getLegMagCourse();
347 _gpsNode->setStringValue(
"rnav-controller-status", _wayptController->status());
349 if (_wayptController->isDone()) {
360 if (_dataValid && (_mode ==
"init")) {
362 routeManagerFlightPlanChanged(
nullptr);
365 if (!routeMgr || !routeMgr->isRouteActive()) {
373 selectOBSMode(
nullptr);
383 _last_pos = _indicated_pos;
384 _lastPosValid = !(_last_pos == SGGeod());
390 _route->removeDelegate(
this);
393 _wayptController.reset();
396void GPS::routeManagerFlightPlanChanged(SGPropertyNode*)
399 _route->removeDelegate(
this);
402 SG_LOG(SG_INSTR, SG_DEBUG,
"GPS saw route-manager flight-plan replaced.");
403 auto routeMgr =
globals->get_subsystem<FGRouteMgr>();
408 _route = routeMgr->flightPlan();
410 _route->addDelegate(
this);
413 if (routeMgr->isRouteActive()) {
416 selectOBSMode(_currentWaypt);
420void GPS::routeActivated(SGPropertyNode* aNode)
423 if (_config.delegateDoesSequencing()) {
427 bool isActive = aNode->getBoolValue();
429 SG_LOG(SG_INSTR, SG_INFO,
"GPS::route activated, switching to LEG mode");
433 if (_dataValid && getWP1FromFlag()) {
434 SG_LOG(SG_INSTR, SG_INFO,
"GPS::route activated, FROM wp1, sequencing");
437 }
else if (_mode ==
"leg") {
438 SG_LOG(SG_INSTR, SG_INFO,
"GPS::route deactivated, switching to OBS mode");
439 selectOBSMode(_currentWaypt);
452 return _indicated_pos;
457 return _last_true_track;
462 return _last_speed_kts;
467 return _last_vertical_speed;
472 return _magvar_node->getDoubleValue();
477 return _config.overflightDistanceNm() * SG_NM_TO_METER;
482 return _config.overflightArmDistanceNm() * SG_NM_TO_METER;
487 return _config.overflightArmAngleDeg();
492 return _selectedCourse;
497 return _config.maxFlyByTurnAngleDeg();
502 auto next = _route->nextLeg();
506 return next->courseDeg();
513 if (_wp0Data.has_value())
528 if (waypt->
type() ==
"runway") {
538 return _config.turnAnticipationEnabled();
544GPS::updateBasicData(
double dt)
546 if (!_lastPosValid) {
552 SGGeodesy::inverse(_last_pos, _indicated_pos, _last_true_track, track2_deg, distance_m );
556 if ((distance_m / dt) > 100000.0) {
557 SG_LOG(SG_INSTR, SG_DEBUG,
"GPS detected reposition, resetting data");
562 double speed_kt = ((distance_m * SG_METER_TO_NM) * ((1 / dt) * 3600.0));
563 double vertical_speed_mpm = ((_indicated_pos.getElevationM() - _last_pos.getElevationM()) * 60 / dt);
564 _last_vertical_speed = vertical_speed_mpm * SG_METER_TO_FEET;
566 speed_kt =
fgGetLowPass(_last_speed_kts, speed_kt, dt/5.0);
567 _last_speed_kts = speed_kt;
569 SGGeod
g = _indicated_pos;
570 g.setLongitudeDeg(_last_pos.getLongitudeDeg());
571 double northSouthM = dist(SGVec3d::fromGeod(_last_pos), SGVec3d::fromGeod(g));
572 northSouthM = copysign(northSouthM, _indicated_pos.getLatitudeDeg() - _last_pos.getLatitudeDeg());
574 double nsMSec =
fgGetLowPass(_lastNSVelocity, northSouthM / dt, dt/2.0);
575 _lastNSVelocity = nsMSec;
576 _northSouthVelocity->setDoubleValue(nsMSec);
579 g.setLatitudeDeg(_last_pos.getLatitudeDeg());
580 double eastWestM = dist(SGVec3d::fromGeod(_last_pos), SGVec3d::fromGeod(g));
581 eastWestM = copysign(eastWestM, _indicated_pos.getLongitudeDeg() - _last_pos.getLongitudeDeg());
583 double ewMSec =
fgGetLowPass(_lastEWVelocity, eastWestM / dt, dt/2.0);
584 _lastEWVelocity = ewMSec;
585 _eastWestVelocity->setDoubleValue(ewMSec);
587 double odometer = _odometer_node->getDoubleValue();
588 _odometer_node->setDoubleValue(odometer + distance_m * SG_METER_TO_NM);
589 odometer = _trip_odometer_node->getDoubleValue();
590 _trip_odometer_node->setDoubleValue(odometer + distance_m * SG_METER_TO_NM);
598GPS::updateTrackingBug()
600 double tracking_bug = _tracking_bug_node->getDoubleValue();
601 double true_bug_error = tracking_bug - getTrueTrack();
602 double magnetic_bug_error = tracking_bug - getMagTrack();
605 SG_NORMALIZE_RANGE(true_bug_error, -180.0, 180.0);
606 SG_NORMALIZE_RANGE(magnetic_bug_error, -180.0, 180.0);
608 _true_bug_error_node->setDoubleValue(true_bug_error);
609 _magnetic_bug_error_node->setDoubleValue(magnetic_bug_error);
612void GPS::currentWaypointChanged()
619 int index = _route->currentIndex(),
620 count = _route->numLegs();
621 if ((index < 0) || (index >= count)) {
622 _currentWaypt=
nullptr;
629 _prevWaypt = _route->previousLeg()->waypoint();
631 _wp0_position = _indicated_pos;
633 _wp0_position = _prevWaypt->position();
637 _currentWaypt = _route->currentLeg()->waypoint();
638 if (_wayptController && (_wayptController->waypoint() == _prevWaypt)) {
640 _wp0Data = _wayptController->legData();
644 _desiredCourse = getLegMagCourse();
645 _desiredCourseNode->fireValueChanged();
649void GPS::waypointsChanged()
651 if (_mode !=
"leg") {
655 SG_LOG(SG_INSTR, SG_DEBUG,
"GPS route edited while in LEG mode, updating waypoints");
656 currentWaypointChanged();
659void GPS::doSequence()
665 if (_config.delegateDoesSequencing()) {
670 if (_mode ==
"dto") {
671 SG_LOG(SG_INSTR, SG_INFO,
"GPS DTO reached destination point");
672 if (_route && _route->isActive()) {
675 const int index = _route->findWayptIndex(_currentWaypt->position());
677 SG_LOG(SG_INSTR, SG_INFO,
"GPS DTO resuming LEG mode at " << _route->legAtIndex(index)->waypoint()->ident());
679 _route->setCurrentIndex(index);
685 selectOBSMode(_currentWaypt);
686 }
else if (_mode ==
"leg") {
687 const int nextIndex = _route->currentIndex() + 1;
688 if (nextIndex >= _route->numLegs()) {
689 SG_LOG(SG_INSTR, SG_INFO,
"GPS built-in sequencing, reached end of route,");
691 selectOBSMode(_currentWaypt);
694 _route->setCurrentIndex(nextIndex);
704 if (_config.delegateDoesSequencing()) {
709 if (_mode ==
"leg") {
710 selectOBSMode(_currentWaypt);
714void GPS::endOfFlightPlan()
726double GPS::computeTurnRadiusNm(
double aGroundSpeedKts)
const
729 double turnTime = 360.0 / _config.turnRateDegSec();
732 double c = turnTime * (aGroundSpeedKts / 3600.0);
735 return c / (2 *
M_PI);
738void GPS::updateRouteData()
740 double totalDistance = 0.0;
742 if (_wayptController) {
743 totalDistance = _wayptController->distanceToWayptM() * SG_METER_TO_NM;
748 for (
int i=_route->currentIndex()+1;
i<_route->numLegs(); ++
i) {
749 auto leg = _route->legAtIndex(
i);
751 if (leg->waypoint()->flag(
WPT_MISS))
754 totalDistance += leg->distanceNm();
758 _routeDistanceNm->setDoubleValue(totalDistance * SG_METER_TO_NM);
759 if (_last_speed_kts > 1.0) {
760 double TTW = ((totalDistance * SG_METER_TO_NM) / _last_speed_kts) * 3600.0;
765void GPS::driveAutopilot()
767 if (!_config.driveAutopilot() || !_defaultGPSMode) {
768 _apDrivingFlag->setBoolValue(
false);
775 bool drive = _mode ==
"leg";
776 _apDrivingFlag->setBoolValue(drive);
780 _apTrueHeading->setDoubleValue(getWP1Bearing());
784void GPS::wp1Changed()
789 if (_mode ==
"leg") {
791 if (_currentWaypt->type() ==
"hold") {
793 auto leg = _route->currentLeg();
794 auto holdCtl =
static_cast<flightgear::HoldCtl*
>(_wayptController.get());
795 holdCtl->setHoldCount(leg->holdCount());
797 }
else if (_mode ==
"obs") {
798 _wayptController.reset(
new OBSController(
this, _currentWaypt));
799 }
else if (_mode ==
"dto") {
800 _wayptController.reset(
new DirectToController(
this, _currentWaypt, _wp0_position));
803 const bool ok = _wayptController && _wayptController->init();
805 SG_LOG(SG_AUTOPILOT, SG_WARN,
"GPS failed to init RNAV controller for waypoint " << _currentWaypt->ident());
806 _wayptController.reset();
808 _gpsNode->setStringValue(
"rnav-controller-status",
"");
812 _gpsNode->setStringValue(
"rnav-controller-status", _wayptController->status());
814 if (_mode ==
"obs") {
815 _legDistanceNm = -1.0;
817 _wayptController->update(0.0);
818 _gpsNode->setStringValue(
"rnav-controller-status", _wayptController->status());
820 _legDistanceNm = _wayptController->distanceToWayptM() * SG_METER_TO_NM;
823 updateCurrentWpNode(_wayptController->position());
825 _desiredCourse = getLegMagCourse();
829void GPS::updateCurrentWpNode(
const SGGeod&
p)
831 _currentWpLatNode->setDoubleValue(
p.getLatitudeDeg());
832 _currentWpLonNode->setDoubleValue(
p.getLongitudeDeg());
833 _currentWpAltNode->setDoubleValue(
p.getElevationFt());
839void GPS::setSelectedCourse(
double crs)
841 if (_selectedCourse == crs) {
845 _selectedCourse = crs;
846 if ((_mode ==
"obs") || _config.courseSelectable()) {
847 _desiredCourse = _selectedCourse;
848 _desiredCourseNode->fireValueChanged();
852double GPS::getLegDistance()
const
854 if (!_dataValid || (_mode ==
"obs")) {
858 return _legDistanceNm;
861double GPS::getLegCourse()
const
863 if (!_dataValid || !_wayptController.get()) {
867 return _wayptController->targetTrackDeg();
870double GPS::getLegMagCourse()
const
876 double m = getLegCourse() - _magvar_node->getDoubleValue();
877 SG_NORMALIZE_RANGE(m, 0.0, 360.0);
881double GPS::getMagTrack()
const
887 double m = getTrueTrack() - _magvar_node->getDoubleValue();
888 SG_NORMALIZE_RANGE(m, 0.0, 360.0);
892double GPS::getCDIDeflection()
const
899 if (_config.cdiDeflectionIsAngular()) {
900 defl = getWP1CourseDeviation();
901 SG_CLAMP_RANGE(defl, -10.0, 10.0);
903 double fullScale = _config.cdiDeflectionLinearPeg();
904 double normError = getWP1CourseErrorNm() / fullScale;
905 SG_CLAMP_RANGE(normError, -1.0, 1.0);
906 defl = normError * 10.0;
912const char* GPS::getWP0Ident()
const
914 if (!_dataValid || (_mode !=
"leg") || !_prevWaypt) {
920 static char identBuf[16];
921 strncpy(identBuf, _prevWaypt->ident().c_str(), 15);
926const char* GPS::getWP0Name()
const
928 if (!_dataValid || !_prevWaypt || !_prevWaypt->source()) {
932 return _prevWaypt->source()->name().c_str();
935bool GPS::getWP1IValid()
const
937 return _dataValid && _currentWaypt.get();
940const char* GPS::getWP1Ident()
const
942 if (!_dataValid || !_currentWaypt) {
948 static char identBuf[16];
949 strncpy(identBuf, _currentWaypt->ident().c_str(), 15);
954const char* GPS::getWP1Name()
const
956 if (!_dataValid || !_currentWaypt || !_currentWaypt->source()) {
960 return _currentWaypt->source()->name().c_str();
963double GPS::getWP1Distance()
const
965 if (!_dataValid || !_wayptController.get()) {
969 return _wayptController->distanceToWayptM() * SG_METER_TO_NM;
972double GPS::getWP1TTW()
const
974 if (!_dataValid || !_wayptController.get()) {
978 return _wayptController->timeToWaypt();
981const char* GPS::getWP1TTWString()
const
983 double t = getWP1TTW();
991double GPS::getWP1Bearing()
const
993 if (!_dataValid || !_wayptController.get()) {
997 return _wayptController->trueBearingDeg();
1000double GPS::getWP1MagBearing()
const
1002 if (!_dataValid || !_wayptController.get()) {
1006 double magBearing = _wayptController->trueBearingDeg() - _magvar_node->getDoubleValue();
1007 SG_NORMALIZE_RANGE(magBearing, 0.0, 360.0);
1011double GPS::getWP1CourseDeviation()
const
1013 if (!_dataValid || !_wayptController.get()) {
1017 return _wayptController->courseDeviationDeg();
1020double GPS::getWP1CourseErrorNm()
const
1022 if (!_dataValid || !_wayptController.get()) {
1026 return _wayptController->xtrackErrorNm();
1029bool GPS::getWP1ToFlag()
const
1031 if (!_dataValid || !_wayptController.get()) {
1035 return _wayptController->toFlag();
1038bool GPS::getWP1FromFlag()
const
1040 if (!_dataValid || !_wayptController.get()) {
1044 return !getWP1ToFlag();
1048double GPS::getScratchDistance()
const
1050 if (!_scratchValid) {
1054 return SGGeodesy::distanceNm(_indicated_pos, _scratchPos);
1057double GPS::getScratchTrueBearing()
const
1059 if (!_scratchValid) {
1063 return SGGeodesy::courseDeg(_indicated_pos, _scratchPos);
1066double GPS::getScratchMagBearing()
const
1068 if (!_scratchValid) {
1072 double crs = getScratchTrueBearing() - _magvar_node->getDoubleValue();
1073 SG_NORMALIZE_RANGE(crs, 0.0, 360.0);
1082void GPS::setCommand(
const char* aCmd)
1084 SG_LOG(SG_INSTR, SG_DEBUG,
"GPS command:" << aCmd);
1086 if (!strcmp(aCmd,
"direct")) {
1088 }
else if (!strcmp(aCmd,
"obs")) {
1091 selectOBSMode(isScratchPositionValid() ?
nullptr : _currentWaypt);
1092 }
else if (!strcmp(aCmd,
"leg")) {
1094 }
else if (!strcmp(aCmd,
"exit-hold")) {
1097 }
else if (!strcmp(aCmd,
"load-route-wpt")) {
1098 loadRouteWaypoint();
1099 }
else if (!strcmp(aCmd,
"nearest")) {
1101 }
else if (!strcmp(aCmd,
"search")) {
1102 _searchNames =
false;
1104 }
else if (!strcmp(aCmd,
"search-names")) {
1105 _searchNames =
true;
1107 }
else if (!strcmp(aCmd,
"next")) {
1109 }
else if (!strcmp(aCmd,
"previous")) {
1111 }
else if (!strcmp(aCmd,
"define-user-wpt")) {
1113 }
else if (!strcmp(aCmd,
"route-insert-before")) {
1114 int index = _scratchNode->getIntValue(
"index");
1115 if (index < 0 || (_route->numLegs() == 0)) {
1116 index = _route->numLegs();
1117 }
else if (index >= _route->numLegs()) {
1118 SG_LOG(SG_INSTR, SG_WARN,
"GPS:route-insert-before, bad index:" << index);
1122 insertWaypointAtIndex(index);
1123 }
else if (!strcmp(aCmd,
"route-insert-after")) {
1124 int index = _scratchNode->getIntValue(
"index");
1125 if (index < 0 || (_route->numLegs() == 0)) {
1126 index = _route->numLegs();
1127 }
else if (index >= _route->numLegs()) {
1128 SG_LOG(SG_INSTR, SG_WARN,
"GPS:route-insert-after, bad index:" << index);
1134 insertWaypointAtIndex(index);
1135 }
else if (!strcmp(aCmd,
"route-delete")) {
1136 int index = _scratchNode->getIntValue(
"index");
1138 index = _route->numLegs();
1139 }
else if (index >= _route->numLegs()) {
1140 SG_LOG(SG_INSTR, SG_WARN,
"GPS:route-delete, bad index:" << index);
1144 removeWaypointAtIndex(index);
1147 SG_LOG(SG_INSTR, SG_WARN,
"GPS:unrecognized command:" << aCmd);
1151void GPS::clearScratch()
1153 _scratchPos = SGGeod::fromDegFt(-9999.0, -9999.0, -9999.0);
1154 _scratchNode->setBoolValue(
"valid",
false);
1156 _scratchNode->setStringValue(
"type",
"");
1157 _scratchNode->setStringValue(
"query",
"");
1161bool GPS::isScratchPositionValid()
const
1163 if ((_scratchPos.getLongitudeDeg() < -9990.0) ||
1164 (_scratchPos.getLatitudeDeg() < -9990.0)) {
1173 if (!isScratchPositionValid()) {
1177 std::string ident(_scratchNode->getStringValue(
"ident"));
1183 if (!isScratchPositionValid()) {
1188 _wp0_position = _indicated_pos;
1192 _currentWaypt =
new NavaidWaypoint(pos, NULL);
1194 _currentWaypt =
new BasicWaypt(_scratchPos, _scratchNode->getStringValue(
"ident"), NULL);
1203 if (!waypt && isScratchPositionValid()) {
1206 waypt =
new NavaidWaypoint(pos, NULL);
1208 waypt =
new BasicWaypt(_scratchPos, _scratchNode->getStringValue(
"ident"), NULL);
1214 _wp0_position = _indicated_pos;
1215 _currentWaypt = waypt;
1219void GPS::selectLegMode()
1221 if (_mode ==
"leg") {
1226 SG_LOG(SG_INSTR, SG_WARN,
"GPS:selectLegMode: no active route");
1240 _wp0_position = _indicated_pos;
1243 currentWaypointChanged();
1248void GPS::loadRouteWaypoint()
1250 _scratchValid =
false;
1251 int index = _scratchNode->getIntValue(
"index", -9999);
1254 if ((index < 0) || (index >= _route->numLegs())) {
1255 index = _route->currentIndex();
1258 _searchIsRoute =
true;
1259 setScratchFromRouteWaypoint(index);
1262void GPS::setScratchFromRouteWaypoint(
int aIndex)
1264 assert(_searchIsRoute);
1265 if ((aIndex < 0) || (aIndex >= _route->numLegs())) {
1266 SG_LOG(SG_INSTR, SG_WARN,
"GPS:setScratchFromRouteWaypoint: route-index out of bounds");
1270 _searchResultIndex = aIndex;
1271 WayptRef wp = _route->legAtIndex(aIndex)->waypoint();
1272 _scratchNode->setStringValue(
"ident", wp->ident());
1273 _scratchPos = wp->position();
1274 _scratchValid =
true;
1275 _scratchNode->setIntValue(
"index", aIndex);
1278void GPS::loadNearest()
1280 string sty(_scratchNode->getStringValue(
"type"));
1283 SG_LOG(SG_INSTR, SG_WARN,
"GPS:loadNearest: request type is invalid:" << sty);
1287 unique_ptr<FGPositioned::Filter> f(createFilter(ty));
1288 int limitCount = _scratchNode->getIntValue(
"max-results", 1);
1289 double cutoffDistance = _scratchNode->getDoubleValue(
"cutoff-nm", 400.0);
1291 SGGeod searchPos = _indicated_pos;
1292 if (isScratchPositionValid()) {
1293 searchPos = _scratchPos;
1300 _searchResultIndex = 0;
1301 _searchIsRoute =
false;
1303 if (_searchResults.empty()) {
1307 setScratchFromCachedSearchResult();
1312 switch (aPos->
type()) {
1339 return new FGAirport::HardSurfaceFilter();
1344 return new SearchFilter;
1347 return new FGPositioned::TypeFilter(aTy);
1353 string sty(_scratchNode->getStringValue(
"type"));
1355 _searchQuery = _scratchNode->getStringValue(
"query");
1356 if (_searchQuery.empty()) {
1357 SG_LOG(SG_INSTR, SG_WARN,
"empty GPS search query");
1362 _searchExact = _scratchNode->getBoolValue(
"exact",
true);
1363 _searchResultIndex = 0;
1364 _searchIsRoute =
false;
1366 unique_ptr<FGPositioned::Filter> f(createFilter(_searchType));
1373 bool orderByRange = _scratchNode->getBoolValue(
"order-by-distance",
true);
1378 if (_searchResults.empty()) {
1383 setScratchFromCachedSearchResult();
1386bool GPS::getScratchHasNext()
const
1389 if (_searchIsRoute) {
1390 lastResult = _route->numLegs() - 1;
1392 lastResult = (int) _searchResults.size() - 1;
1395 if (lastResult < 0) {
1399 return (_searchResultIndex < lastResult);
1402void GPS::setScratchFromCachedSearchResult()
1404 int index = _searchResultIndex;
1406 if ((index < 0) || (index >= (
int) _searchResults.size())) {
1407 SG_LOG(SG_INSTR, SG_WARN,
"GPS:setScratchFromCachedSearchResult: index out of bounds:" << index);
1411 setScratchFromPositioned(_searchResults[index], index);
1414void GPS::setScratchFromPositioned(FGPositioned* aPos,
int aIndex)
1419 _scratchPos = aPos->
geod();
1420 _scratchNode->setStringValue(
"name", aPos->
name());
1421 _scratchNode->setStringValue(
"ident", aPos->
ident());
1425 _scratchNode->setIntValue(
"index", aIndex);
1428 _scratchValid =
true;
1429 _scratchNode->setIntValue(
"result-count", _searchResults.size());
1431 switch (aPos->
type()) {
1433 _scratchNode->setDoubleValue(
"frequency-mhz",
static_cast<FGNavRecord*
>(aPos)->get_freq() / 100.0);
1437 _scratchNode->setDoubleValue(
"frequency-khz",
static_cast<FGNavRecord*
>(aPos)->get_freq() / 100.0);
1441 addAirportToScratch((FGAirport*)aPos);
1452void GPS::addAirportToScratch(FGAirport* aAirport)
1454 for (
unsigned int r=0; r<aAirport->
numRunways(); ++r) {
1455 SGPropertyNode* rwyNd = _scratchNode->getChild(
"runways", r,
true);
1460 rwyNd->setStringValue(
"id", rwy->
ident().c_str());
1461 rwyNd->setIntValue(
"length-ft", rwy->
lengthFt());
1462 rwyNd->setIntValue(
"width-ft", rwy->
widthFt());
1463 rwyNd->setIntValue(
"heading-deg", rwy->
headingDeg());
1468 rwyNd->setDoubleValue(
"ils-frequency-mhz", rwy->
ILS()->
get_freq() / 100.0);
1473void GPS::nextResult()
1475 if (!getScratchHasNext()) {
1480 if (_searchIsRoute) {
1481 setScratchFromRouteWaypoint(++_searchResultIndex);
1483 ++_searchResultIndex;
1484 setScratchFromCachedSearchResult();
1488void GPS::previousResult()
1490 if (_searchResultIndex <= 0) {
1495 --_searchResultIndex;
1497 if (_searchIsRoute) {
1498 setScratchFromRouteWaypoint(_searchResultIndex);
1500 setScratchFromCachedSearchResult();
1504void GPS::defineWaypoint()
1506 if (!isScratchPositionValid()) {
1507 SG_LOG(SG_INSTR, SG_WARN,
"GPS:defineWaypoint: invalid lat/lon");
1511 string ident = _scratchNode->getStringValue(
"ident");
1512 if (ident.size() < 2) {
1513 SG_LOG(SG_INSTR, SG_WARN,
"GPS:defineWaypoint: waypoint identifier must be at least two characters");
1520 if (!dups.empty()) {
1521 SG_LOG(SG_INSTR, SG_WARN,
"GPS:defineWaypoint: non-unique waypoint identifier, ho-hum");
1524 SG_LOG(SG_INSTR, SG_INFO,
"GPS:defineWaypoint: creating waypoint:" << ident);
1526 _searchResults.clear();
1527 _searchResults.push_back(wpt);
1528 setScratchFromPositioned(wpt.get(), -1);
1531void GPS::insertWaypointAtIndex(
int aIndex)
1534 if ((aIndex < 0) || (aIndex > _route->numLegs())) {
1535 throw sg_range_exception(
"GPS::insertWaypointAtIndex: index out of bounds");
1538 if (!isScratchPositionValid()) {
1539 SG_LOG(SG_INSTR, SG_WARN,
"GPS:insertWaypointAtIndex: invalid lat/lon");
1543 string ident = _scratchNode->getStringValue(
"ident");
1545 WayptRef wpt =
new BasicWaypt(_scratchPos, ident, NULL);
1546 _route->insertWayptAtIndex(wpt, aIndex);
1549void GPS::removeWaypointAtIndex(
int aIndex)
1551 if ((aIndex < 0) || (aIndex >= _route->numLegs())) {
1552 throw sg_range_exception(
"GPS::removeWaypointAtIndex: index out of bounds");
1555 _route->deleteIndex(aIndex);
1561void GPS::tieSGGeod(SGPropertyNode* aNode, SGGeod& aRef,
1562 const char* lonStr,
const char* latStr,
const char* altStr)
1564 tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg));
1565 tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg));
1568 tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, &SGGeod::setElevationFt));
1572void GPS::tieSGGeodReadOnly(SGPropertyNode* aNode, SGGeod& aRef,
1573 const char* lonStr,
const char* latStr,
const char* altStr)
1575 tie(aNode, lonStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLongitudeDeg, NULL));
1576 tie(aNode, latStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getLatitudeDeg, NULL));
1579 tie(aNode, altStr, SGRawValueMethods<SGGeod, double>(aRef, &SGGeod::getElevationFt, NULL));
1583void GPS::commandExitHold()
1585 if (_currentWaypt && (_currentWaypt->type() ==
"hold")) {
1586 auto holdCtl =
static_cast<flightgear::HoldCtl*
>(_wayptController.get());
1587 holdCtl->exitHold();
1589 SG_LOG(SG_INSTR, SG_WARN,
"GPS:exit hold requested, but not currently in a hold");
1596SGSubsystemMgr::InstancedRegistrant<GPS> registrantGPS(
1597 SGSubsystemMgr::FDM,
1598 {{
"instrumentation", SGSubsystemMgr::Dependency::HARD}});
unsigned int numRunways() const
FGRunwayRef getRunwayByIndex(unsigned int aIndex) const
static Type typeFromName(const std::string &aName)
Map a candidate type string to a real type.
static FGPositionedList findAllWithName(const std::string &aName, Filter *aFilter=NULL, bool aExact=true)
As above, but searches names instead of idents.
static FGPositionedRef createWaypoint(FGPositioned::Type aType, const std::string &aIdent, const SGGeod &aPos, bool isTemporary=false, const std::string &aName={})
virtual const std::string & name() const
Return the name of this positioned.
static FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, Filter *aFilter=NULL)
virtual const SGGeod & geod() const
static void sortByRange(FGPositionedList &, const SGGeod &aPos)
Sort an FGPositionedList by distance from a position.
static FGPositionedList findAllWithIdent(const std::string &aIdent, Filter *aFilter=NULL, bool aExact=true)
Find all items with the specified ident.
static const char * nameForType(Type aTy)
Map a type to a human-readable string.
static FGPositionedList findClosestN(const SGGeod &aPos, unsigned int aN, double aCutoffNm, Filter *aFilter=NULL)
Find the closest N items to a position, which pass the specified filter A cutoff range in NM must be ...
const std::string & ident() const
static FGPositionedRef findClosest(const SGGeod &aPos, double aCutoffNm, Filter *aFilter=NULL)
Find the closest item to a position, which pass the specified filter A cutoff range in NM must be spe...
Top level route manager class.
double headingDeg() const
Runway heading in degrees.
SGGeod end() const
Get the 'far' end - this is equivalent to calling pointOnCenterline(lengthFt());.
FGNavRecord * ILS() const
std::optional< double > nextLegTrack() override
double magvarDeg() override
Magnetic variation at current position.
double maxFlyByTurnAngleDeg() const override
maximum angle in degrees where flyBy is permitted.
double trackDeg() override
True track in degrees.
double vspeedFPM() override
Vertical speed in ft/minute.
double selectedMagCourse() override
device selected course (eg, from autopilot / MCP / OBS) in degrees
double groundSpeedKts() override
Ground speed (along the track) in knots.
std::optional< LegData > previousLegData() override
device leg previous waypoint position(eg, from route manager)
SGGeod position() override
bool canFlyBy() const override
double overflightArmAngleDeg() override
angle for overflight sequencing.
void update(double delta_time_sec) override
double overflightArmDistanceM() override
minimum distance to a waypoint for overflight sequencing.
GPS(SGPropertyNode *node, bool defaultGPSMode=false)
double overflightDistanceM() override
minimum distance to switch next waypoint.
virtual void sequence()
Invoked when the C++ code determines the active leg is done / next leg should be sequenced.
flight-plan leg encapsulation
Waypoint based upon a navaid.
double turnRadiusNm()
compute turn radius based on current ground-speed
Waypoint based upon a runway.
FGRunway * runway() const
static WayptController * createForWaypt(RNAV *rnav, const WayptRef &aWpt)
Static factory method, given a waypoint, return a controller bound to it, of the appropriate type.
Abstract base class for waypoints (and things that are treated similarly by navigation systems).
virtual std::string type() const =0
virtual SGGeod position() const =0
static const char * makeTTWString(double TTW)
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
SGSharedPtr< FGPositioned > FGPositionedRef
SGSharedPtr< Waypt > WayptRef
@ WPT_DYNAMIC
waypoint position is dynamic, i.e moves based on other criteria, such as altitude,...
@ WPT_MISS
segment is part of missed approach
std::vector< FGPositionedRef > FGPositionedList
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
double fgGetLowPass(double current, double target, double timeratio)
Move a value towards a target.