25#include <simgear/sg_inlines.h>
26#include <simgear/structure/exception.hxx>
60 double crossTrackError = asin(
61 sin(distanceOriginToPosition * SG_NM_TO_RAD) *
62 sin(courseDev * SG_DEGREES_TO_RADIANS)
64 return crossTrackError * SG_RAD_TO_NM;
67SGGeod
computeTurnCenter(
double turnRadiusM,
const SGGeod& basePos,
double inboundTrack,
double turnAngle)
72 const auto halfTurnHeading = inboundTrack + (turnAngle * 0.5);
73 double p = copysign(90.0, turnAngle);
74 double h = halfTurnHeading +
p;
75 SG_NORMALIZE_RANGE(h, 0.0, 360.0);
77 const double tcOffset = turnRadiusM / cos(turnAngle * 0.5 * SG_DEGREES_TO_RADIANS);
78 return SGGeodesy::direct(basePos, h, tcOffset);
104 SG_LOG(SG_AUTOPILOT, SG_DEV_WARN,
"already done @ WayptController::setDone");
112 double gs =
_rnav->groundSpeedKts();
175 _distanceAircraftTargetMeter(0.0),
179 throw sg_exception(
"BasicWayptCtrl doesn't work with dynamic waypoints");
191 double bearingAircraftToTarget;
193 bearingAircraftToTarget = SGGeodesy::courseDeg(
_rnav->position(),
_waypt->position());
194 _distanceAircraftTargetMeter = SGGeodesy::distanceM(
_rnav->position(),
_waypt->position());
197 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
199 if ((fabs(_courseDev) >
_rnav->overflightArmAngleDeg()) && (_distanceAircraftTargetMeter < _rnav->overflightArmDistanceM())) {
206 return _distanceAircraftTargetMeter;
211 double x = sin(
courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceAircraftTargetMeter;
212 return x * SG_METER_TO_NM;
217 return (fabs(_courseDev) <
_rnav->overflightArmAngleDeg());
227 return SGGeodesy::courseDeg(
_rnav->position(),
_waypt->position());
232 return _waypt->position();
236 double _distanceAircraftTargetMeter;
252 _distanceOriginAircraftMetre(0.0),
253 _distanceAircraftTargetMetre(0.0),
254 _courseOriginToAircraft(0.0),
255 _courseAircraftToTarget(0.0),
260 throw sg_exception(
"LegWayptCtl doesn't work with dynamic waypoints");
266 auto previousLeg =
_rnav->previousLegData();
268 _waypointOrigin = previousLeg.value().position;
269 _initialLegCourse = SGGeodesy::courseDeg(_waypointOrigin,
_waypt->position());
272 _waypointOrigin =
_rnav->position();
275 _courseAircraftToTarget = SGGeodesy::courseDeg(
_rnav->position(),
_waypt->position());
276 _distanceAircraftTargetMetre = SGGeodesy::distanceM(
_rnav->position(),
_waypt->position());
280 bool canReachLeg = (fabs(_initialLegCourse -_courseAircraftToTarget) < 45.0);
282 if (previousLeg && canReachLeg) {
288 _initialLegCourse = _courseAircraftToTarget;
289 _waypointOrigin =
_rnav->position();
294 _finalLegCourse = SGGeodesy::courseDeg(
_waypt->position(), _waypointOrigin) + 180;
295 SG_NORMALIZE_RANGE(_finalLegCourse, 0.0, 360.0);
298 if (previousLeg.has_value() && previousLeg.value().didFlyBy) {
299 _flyByTurnCenter = previousLeg.value().flyByTurnCenter;
300 _flyByTurnAngle = previousLeg.value().turnAngle;
301 _flyByTurnRadius = previousLeg.value().flyByRadius;
302 _entryFlyByActive =
true;
303 SG_NORMALIZE_RANGE(_flyByTurnAngle, -180.0, 180.0);
311 _didComputeTurn =
true;
317 if (!
_rnav->canFlyBy())
320 auto nextLegTrack =
_rnav->nextLegTrack();
322 if (!nextLegTrack.has_value()) {
326 _flyByTurnAngle = nextLegTrack.value() - _finalLegCourse;
327 SG_NORMALIZE_RANGE(_flyByTurnAngle, -180.0, 180.0);
329 if (fabs(_flyByTurnAngle) >
_rnav->maxFlyByTurnAngleDeg()) {
334 _flyByTurnRadius =
_rnav->turnRadiusNm() * SG_NM_TO_METER;
344 auto bearingToTurnCenter = SGGeodesy::courseDeg(
_rnav->position(), _flyByTurnCenter);
345 auto distToTurnCenter = SGGeodesy::distanceM(
_rnav->position(), _flyByTurnCenter);
347 if (!_flyByStarted) {
349 auto a = bearingToTurnCenter - _finalLegCourse;
350 SG_NORMALIZE_RANGE(a, -180.0, 180.0);
351 if (fabs(a) < 90.0) {
355 _flyByStarted =
true;
360 const auto halfPointAngle = (_finalLegCourse + (_flyByTurnAngle * 0.5));
361 auto b = bearingToTurnCenter - halfPointAngle;
362 SG_NORMALIZE_RANGE(b, -180.0, 180.0);
364 if (fabs(b) >= 90.0) {
371 _targetTrack = bearingToTurnCenter - copysign(90, _flyByTurnAngle);
375 _crossTrackError = (distToTurnCenter - _flyByTurnRadius) * SG_METER_TO_NM;
376 _courseDev = _crossTrackError * 10.0;
383 auto bearingToTurnCenter = SGGeodesy::courseDeg(
_rnav->position(), _flyByTurnCenter);
384 auto distToTurnCenter = SGGeodesy::distanceM(
_rnav->position(), _flyByTurnCenter);
386 auto b = bearingToTurnCenter - _initialLegCourse;
388 SG_NORMALIZE_RANGE(b, -180.0, 180.0);
389 if (fabs(b) >= 90.0) {
390 _entryFlyByActive =
false;
394 _targetTrack = bearingToTurnCenter - copysign(90, _flyByTurnAngle);
397 _crossTrackError = (distToTurnCenter - _flyByTurnRadius) * SG_METER_TO_NM;
398 _courseDev = _crossTrackError * 10.0;
403 _courseOriginToAircraft = SGGeodesy::courseDeg(_waypointOrigin,
_rnav->position());
404 _distanceOriginAircraftMetre = SGGeodesy::distanceM(_waypointOrigin,
_rnav->position());
406 _courseAircraftToTarget = SGGeodesy::courseDeg(
_rnav->position(),
_waypt->position());
407 _distanceAircraftTargetMetre = SGGeodesy::distanceM(
_rnav->position(),
_waypt->position());
409 if (_entryFlyByActive) {
414 if (!_didComputeTurn && (_distanceAircraftTargetMetre <
turnComputeDist)) {
418 if (_didComputeTurn && _doFlyBy) {
429 Suppose you are proceeding on a great circle route from
A to
B (course =crs_AB) and end up at D,
430 perhaps off course. (We presume that
A is ot a pole!) You can calculate the course from
A to D
431 (crs_AD) and the distance from
A to D (dist_AD)
using the formulae above. In terms of these the
432 cross track error, XTD, (distance off course) is given by
434 XTD =asin(sin(dist_AD)*sin(crs_AD-crs_AB))
435 (positive XTD means right of course, negative means left)
436 (If the point
A is the
N. or S. Pole
replace crs_AD-crs_AB with
437 lon_D-lon_B or lon_B-lon_D, respectively.)
442 double distOriginAircraftRad = _distanceOriginAircraftMetre * SG_METER_TO_NM * SG_NM_TO_RAD;
443 double xtkRad = asin(sin(distOriginAircraftRad) * sin((_courseOriginToAircraft - _initialLegCourse) * SG_DEGREES_TO_RADIANS));
449 _crossTrackError = -(xtkRad * SG_RAD_TO_NM);
456 double atd = acos(cos(distOriginAircraftRad) / cos(xtkRad));
465 double x = _courseOriginToAircraft - _initialLegCourse;
466 SG_NORMALIZE_RANGE(x, -180.0, 180.0);
467 if (fabs(x) > 90.0) {
472 const double atdM = atd * SG_RAD_TO_NM * SG_NM_TO_METER;
473 SGGeod abeamPoint = SGGeodesy::direct(_waypointOrigin, _initialLegCourse, atdM);
477 if (_distanceAircraftTargetMetre < (SG_NM_TO_METER * 2.0)) {
482 double desiredCourse = SGGeodesy::courseDeg(abeamPoint,
_waypt->position());
487 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
490 bool isMinimumOverFlightDistanceReached = _distanceAircraftTargetMetre <
_rnav->overflightDistanceM();
491 bool isOverFlightConeArmed = _distanceAircraftTargetMetre < (
_rnav->overflightArmDistanceM() +
_rnav->overflightDistanceM() );
492 bool leavingOverFlightCone = (fabs(_courseDev) >
_rnav->overflightArmAngleDeg());
494 if ( isMinimumOverFlightDistanceReached ){
498 if( isOverFlightConeArmed ){
500 if ( leavingOverFlightCone ) {
509 std::optional<RNAV::LegData>
legData()
const override
527 return _distanceAircraftTargetMetre;
532 return _crossTrackError;
547 return _courseAircraftToTarget;
552 return _waypt->position();
561 SGGeod _waypointOrigin;
562 double _initialLegCourse;
563 double _finalLegCourse;
564 double _distanceOriginAircraftMetre;
565 double _distanceAircraftTargetMetre;
566 double _courseOriginToAircraft;
567 double _courseAircraftToTarget;
570 double _crossTrackError;
572 bool _didComputeTurn =
false;
573 bool _doFlyBy =
false;
574 SGGeod _flyByTurnCenter;
575 double _flyByTurnAngle;
576 double _flyByTurnRadius;
577 bool _entryFlyByActive =
false;
578 bool _flyByStarted =
false;
592 _distanceAircraftRunwayEnd(0.0),
609 double bearingAircraftRunwayEnd;
615 bearingAircraftRunwayEnd = SGGeodesy::courseDeg(
_rnav->position(), _runway->end());
616 _distanceAircraftRunwayEnd = SGGeodesy::distanceM(
_rnav->position(), _runway->end());
619 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
621 if ((fabs(_courseDev) >
_rnav->overflightArmAngleDeg()) && (_distanceAircraftRunwayEnd < _rnav->overflightArmDistanceM())) {
628 return SGGeodesy::distanceM(
_rnav->position(), _runway->threshold());
633 double x = sin(_courseDev * SG_DEGREES_TO_RADIANS) * _distanceAircraftRunwayEnd;
634 return x * SG_METER_TO_NM;
646 return SGGeodesy::courseDeg(
_rnav->position(), _runway->end());
651 return _runway->threshold();
655 double _distanceAircraftRunwayEnd;
666 if (
_waypt->type() !=
"hdgToAlt") {
667 throw sg_exception(
"invalid waypoint type",
"ConstHdgToAltCtl ctor");
671 throw sg_exception(
"invalid waypoint alt restriction",
"ConstHdgToAltCtl ctor");
679 _filteredFPM = _lastFPM =
_rnav->vspeedFPM();
685 double curAlt =
_rnav->position().getElevationFt();
691 _lastFPM =
_rnav->vspeedFPM();
693 switch (
_waypt->altitudeRestriction()) {
697 double d = curAlt -
_waypt->altitudeFt();
698 if (fabs(d) < 50.0) {
699 SG_LOG(SG_INSTR, SG_INFO,
"ConstHdgToAltCtl, reached target altitude " <<
_waypt->altitudeFt());
705 if (curAlt >=
_waypt->altitudeFt()) {
706 SG_LOG(SG_INSTR, SG_INFO,
"ConstHdgToAltCtl, above target altitude " <<
_waypt->altitudeFt());
712 if (curAlt <= _waypt->altitudeFt()) {
713 SG_LOG(SG_INSTR, SG_INFO,
"ConstHdgToAltCtl, below target altitude " <<
_waypt->altitudeFt());
725 double d = fabs(
_rnav->position().getElevationFt() -
_waypt->altitudeFt());
726 return (d / _filteredFPM) * 60.0;
733 double gsMsec =
_rnav->groundSpeedKts() * SG_KT_TO_MPS;
746 double _lastFPM, _filteredFPM;
756 if (
_waypt->type() !=
"radialIntercept") {
757 throw sg_exception(
"invalid waypoint type",
"InterceptCtl ctor");
775 geocPos = SGGeoc::fromGeod(
_rnav->position()),
776 geocWayptPos = SGGeoc::fromGeod(
_waypt->position());
779 geocWayptPos, _trueRadial, c);
784 SGGeoc navidAdjusted = SGGeodesy::advanceDegM(geocWayptPos, _trueRadial, -10 * SG_NM_TO_METER);
786 navidAdjusted, _trueRadial, c);
788 SG_LOG(SG_INSTR, SG_WARN,
"InterceptCtl, bad intersection, skipping waypt");
794 _projectedPosition = SGGeod::fromGeoc(c);
795 _distanceToProjectedInterceptM = SGGeodesy::distanceM(
_rnav->position(), _projectedPosition);
796 if (!_didComputeTurn && (_distanceToProjectedInterceptM <
turnComputeDist)) {
807 double r = SGGeodesy::courseDeg(
_waypt->position(),
_rnav->position());
808 double bearingDiff = r - _trueRadial;
809 SG_NORMALIZE_RANGE(bearingDiff, -180.0, 180.0);
810 if (fabs(bearingDiff) < 0.5) {
820 auto bearingToTurnCenter = SGGeodesy::courseDeg(
_rnav->position(), _flyByTurnCenter);
821 auto distToTurnCenter = SGGeodesy::distanceM(
_rnav->position(), _flyByTurnCenter);
823 if (!_flyByStarted) {
826 SG_NORMALIZE_RANGE(a, -180.0, 180.0);
827 if (fabs(a) < 90.0) {
831 _flyByStarted =
true;
837 _targetTrack = bearingToTurnCenter - copysign(90, _flyByTurnAngle);
840 _crossTrackError = (distToTurnCenter - _flyByTurnRadius) * SG_METER_TO_NM;
846 _didComputeTurn =
true;
850 double inverseRadial = _trueRadial + 180.0;
851 SG_NORMALIZE_RANGE(inverseRadial, 0.0, 360.0);
853 SG_NORMALIZE_RANGE(_flyByTurnAngle, -180.0, 180.0);
855 if (fabs(_flyByTurnAngle) >
_rnav->maxFlyByTurnAngleDeg()) {
860 _flyByTurnRadius =
_rnav->turnRadiusNm() * SG_NM_TO_METER;
867 return _distanceToProjectedInterceptM;
872 return _projectedPosition;
879 return _crossTrackError;
889 SGGeod _projectedPosition;
890 bool _canFlyBy =
false;
891 bool _didComputeTurn =
false;
892 bool _doFlyBy =
false;
893 double _distanceToProjectedInterceptM = 0.0;
894 SGGeod _flyByTurnCenter;
895 double _flyByTurnAngle;
896 double _flyByTurnRadius;
897 bool _flyByStarted =
false;
898 double _crossTrackError = 0.0;
909 if (
_waypt->type() !=
"dmeIntercept") {
910 throw sg_exception(
"invalid waypoint type",
"DMEInterceptCtl ctor");
923 _distanceNm = SGGeodesy::distanceNm(
_rnav->position(), _dme->position());
924 double d = fabs(_distanceNm - _dme->dmeDistanceNm());
932 return fabs(_distanceNm - _dme->dmeDistanceNm()) * SG_NM_TO_METER;
937 SGGeoc geocPos = SGGeoc::fromGeod(
_rnav->position());
938 SGGeoc navid = SGGeoc::fromGeod(_dme->position());
939 double distRad = _dme->dmeDistanceNm() * SG_NM_TO_RAD;
942 SGGeoc bPt = SGGeodesy::advanceDegM(geocPos,
_targetTrack, 1e5);
945 SG_LOG(SG_AUTOPILOT, SG_WARN,
"DMEInterceptCtl::position failed");
946 return _dme->position();
964 if (aWpt->type() ==
"hold") {
965 const auto hold = static_cast<Hold*>(aWpt.ptr());
966 _holdCourse = hold->inboundRadial();
967 if (hold->isDistance())
968 _holdLegDistance = hold->timeOrDistance();
970 _holdLegTime = hold->timeOrDistance();
971 _leftHandTurns = hold->isLeftHanded();
977 _segmentEnd =
_waypt->position();
978 _state = LEG_TO_HOLD;
987void HoldCtl::computeEntry()
990 const double diff = SGMiscd::normalizePeriodic( -180.0, 180.0, _holdCourse - entryCourse);
992 if (_leftHandTurns) {
993 if ((diff > -70) && (diff < 120.0)) {
994 _state = ENTRY_DIRECT;
995 }
else if (diff < 0.0) {
996 _state = ENTRY_PARALLEL;
998 _state = ENTRY_TEARDROP;
1002 if ((diff > -120) && (diff < 70.0)) {
1003 _state = ENTRY_DIRECT;
1004 }
else if (diff > 0.0) {
1005 _state = ENTRY_PARALLEL;
1007 _state = ENTRY_TEARDROP;
1014 const auto rnavPos =
_rnav->position();
1015 const double dEnd = SGGeodesy::distanceNm(rnavPos, _segmentEnd);
1039 const double turnOffset = inLeftTurn() ? 90 : -90;
1040 const double bearingTurnCenter = SGGeodesy::courseDeg(rnavPos, _turnCenter);
1044 const double angleToTurn = SGMiscd::normalizePeriodic( -180.0, 180.0,
_targetTrack - _turnEndAngle);
1045 if (fabs(angleToTurn) < 0.5) {
1048 }
else if (_state == LEG_TO_HOLD) {
1049 checkInitialEntry(dEnd);
1050 }
else if ((_state == HOLD_INBOUND) || (_state == ENTRY_PARALLEL_INBOUND)) {
1051 if (checkOverHold()) {
1057 }
else if ((_state == HOLD_OUTBOUND) || (_state == ENTRY_TEARDROP)) {
1063 }
else if (_state == ENTRY_PARALLEL_OUTBOUND) {
1065 startParallelEntryTurn();
1082bool HoldCtl::checkOverHold()
1090 if (_holdCount == 0) {
1095 startOutboundTurn();
1099void HoldCtl::checkInitialEntry(
double dNm)
1101 _turnRadius =
_rnav->turnRadiusNm();
1102 if (dNm > _turnRadius) {
1107 if (_holdCount == 0) {
1114 _state = HOLD_EXITING;
1122 if (_state == ENTRY_DIRECT) {
1123 startOutboundTurn();
1124 }
else if (_state == ENTRY_TEARDROP) {
1125 _targetTrack = _holdCourse + (_leftHandTurns ? -150 : 150);
1127 _segmentEnd = outboundEndPoint();
1130 assert(_state == ENTRY_PARALLEL);
1131 _state = ENTRY_PARALLEL_OUTBOUND;
1134 const double legLengthM = holdLegLengthNm() * SG_NM_TO_METER;
1135 _segmentEnd = SGGeodesy::direct(
_waypt->position(), _holdCourse, -legLengthM);
1139void HoldCtl::startInboundTurn()
1141 _state = HOLD_INBOUND;
1143 _turnCenter = inboundTurnCenter();
1144 _turnRadius =
_rnav->turnRadiusNm();
1145 _turnEndAngle = _holdCourse;
1146 SG_NORMALIZE_RANGE(_turnEndAngle, 0.0, 360.0);
1149void HoldCtl::startOutboundTurn()
1151 _state = HOLD_OUTBOUND;
1153 _turnCenter = outboundTurnCenter();
1154 _turnRadius =
_rnav->turnRadiusNm();
1155 _turnEndAngle = _holdCourse + 180.0;
1156 SG_NORMALIZE_RANGE(_turnEndAngle, 0.0, 360.0);
1159void HoldCtl::startParallelEntryTurn()
1161 _state = ENTRY_PARALLEL_INBOUND;
1163 _turnRadius =
_rnav->turnRadiusNm();
1164 _turnCenter = inboundTurnCenter();
1165 _turnEndAngle = _holdCourse + (_leftHandTurns ? 45.0 : -45.0);
1166 SG_NORMALIZE_RANGE(_turnEndAngle, 0.0, 360.0);
1169void HoldCtl::exitTurn()
1175 _segmentEnd =
_waypt->position();
1178 case ENTRY_PARALLEL_INBOUND:
1184 _segmentEnd =
_waypt->position();
1191 _segmentEnd = outboundEndPoint();
1195 SG_LOG(SG_INSTR, SG_DEV_WARN,
"HoldCOntroller: bad state at exitTurn:" << _state);
1199SGGeod HoldCtl::outboundEndPoint()
1202 const double turnRadiusM =
_rnav->turnRadiusNm() * SG_NM_TO_METER;
1203 const double legLengthM = holdLegLengthNm() * SG_NM_TO_METER;
1204 const auto p1 = SGGeodesy::direct(
_waypt->position(), _holdCourse, -legLengthM);
1205 const double turnOffset = _leftHandTurns ? -90 : 90;
1206 return SGGeodesy::direct(p1, _holdCourse + turnOffset, turnRadiusM * 2.0);
1209SGGeod HoldCtl::outboundTurnCenter()
1211 const double turnOffset = _leftHandTurns ? -90 : 90;
1212 return SGGeodesy::direct(
_waypt->position(), _holdCourse + turnOffset,
_rnav->turnRadiusNm() * SG_NM_TO_METER);
1215SGGeod HoldCtl::inboundTurnCenter()
1217 const double legLengthM = holdLegLengthNm() * SG_NM_TO_METER;
1218 const auto p1 = SGGeodesy::direct(
_waypt->position(), _holdCourse, -legLengthM);
1219 const double turnOffset = _leftHandTurns ? -90 : 90;
1220 return SGGeodesy::direct(p1, _holdCourse + turnOffset,
_rnav->turnRadiusNm() * SG_NM_TO_METER);
1230 return _waypt->position();
1233bool HoldCtl::inLeftTurn()
const
1235 return (_state == ENTRY_PARALLEL_INBOUND) ? !_leftHandTurns : _leftHandTurns;
1238double HoldCtl::holdLegLengthNm()
const
1241 if (_holdLegTime > 0.0) {
1242 return _holdLegTime * gs / 3600.0;
1245 return _holdLegDistance;
1255 const double dR = SGGeodesy::distanceNm(_turnCenter,
_rnav->position());
1256 const double xtk = dR - _turnRadius;
1257 return inLeftTurn() ? -xtk : xtk;
1259 const double courseToSegmentEnd = SGGeodesy::courseDeg(
_rnav->position(), _segmentEnd);
1260 const double courseDev = courseToSegmentEnd -
_targetTrack;
1261 const auto d = SGGeodesy::distanceNm(
_rnav->position(), _segmentEnd);
1281 case LEG_TO_HOLD:
return "leg-to-hold";
1282 case HOLD_INIT:
return "hold-init";
1283 case ENTRY_DIRECT:
return "entry-direct";
1284 case ENTRY_PARALLEL:
1285 case ENTRY_PARALLEL_OUTBOUND:
1286 case ENTRY_PARALLEL_INBOUND:
1287 return "entry-parallel";
1288 case ENTRY_TEARDROP:
1289 return "entry-teardrop";
1291 case HOLD_OUTBOUND:
return "hold-outbound";
1292 case HOLD_INBOUND:
return "hold-inbound";
1293 case HOLD_EXITING:
return "hold-exiting";
1296 throw std::domain_error(
"Unsupported HoldState.");
1327 return _waypt->position();
1338 _distanceAircraftTargetMeter(0.0),
1340 _courseAircraftToTarget(0.0)
1347 SG_LOG(SG_AUTOPILOT, SG_WARN,
"can't use a dynamic waypoint for direct-to: " <<
_waypt->ident());
1357 double courseOriginToAircraft;
1358 double az2, distanceOriginToAircraft;
1359 SGGeodesy::inverse(_origin,
_rnav->position(), courseOriginToAircraft, az2, distanceOriginToAircraft);
1360 if (distanceOriginToAircraft < 100.0) {
1366 _courseAircraftToTarget = SGGeodesy::courseDeg(
_rnav->position(),
_waypt->position());
1367 _distanceAircraftTargetMeter = SGGeodesy::distanceM(
_rnav->position(),
_waypt->position());
1370 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
1372 bool isMinimumOverFlightDistanceReached = _distanceAircraftTargetMeter <
_rnav->overflightDistanceM();
1373 bool isOverFlightConeArmed = _distanceAircraftTargetMeter < (
_rnav->overflightArmDistanceM() +
_rnav->overflightDistanceM() );
1374 bool leavingOverFlightCone = (fabs(_courseAircraftToTarget -
_targetTrack) >
_rnav->overflightArmAngleDeg());
1376 if( isMinimumOverFlightDistanceReached ){
1379 if( isOverFlightConeArmed && leavingOverFlightCone ){
1387 return _distanceAircraftTargetMeter;
1402 return _courseAircraftToTarget;
1407 return _waypt->position();
1414 _distanceAircraftTargetMeter(0.0),
1416 _courseAircraftToTarget(0.0)
1423 SG_LOG(SG_AUTOPILOT, SG_WARN,
"can't use a dynamic waypoint for OBS mode" <<
_waypt->ident());
1435 _courseAircraftToTarget = SGGeodesy::courseDeg(
_rnav->position(),
_waypt->position());
1436 _distanceAircraftTargetMeter = SGGeodesy::distanceM(
_rnav->position(),
_waypt->position());
1440 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
1445 return (fabs(_courseDev) <
_rnav->overflightArmAngleDeg());
1450 return _distanceAircraftTargetMeter;
1471 return _courseAircraftToTarget;
1476 return _waypt->position();
1484 throw sg_exception(
"Passed null waypt",
"WayptController::createForWaypt");
1487 const std::string& wty(aWpt->type());
1488 if (wty ==
"runway") {
1492 if (wty ==
"radialIntercept") {
1496 if (wty ==
"dmeIntercept") {
1500 if (wty ==
"hdgToAlt") {
1504 if (wty ==
"vectors") {
1508 if (wty ==
"hold") {
1509 return new HoldCtl(aRNAV, aWpt);
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
virtual bool toFlag() const
to/from flag - true = to, false = from.
virtual SGGeod position() const
Position associated with the waypt.
virtual double xtrackErrorNm() const
virtual void update(double)
virtual double courseDeviationDeg() const
virtual double trueBearingDeg() const
Bearing to the waypoint, if this value is meaningful.
BasicWayptCtl(RNAV *aRNAV, const WayptRef &aWpt)
virtual double timeToWaypt() const
Compute time until the waypoint is done.
ConstHdgToAltCtl(RNAV *aRNAV, const WayptRef &aWpt)
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
virtual SGGeod position() const
Position associated with the waypt.
virtual void update(double dt)
virtual void update(double)
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
virtual SGGeod position() const
Position associated with the waypt.
DMEInterceptCtl(RNAV *aRNAV, const WayptRef &aWpt)
virtual double xtrackErrorNm() const
virtual void update(double dt)
virtual double courseDeviationDeg() const
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
virtual SGGeod position() const
Position associated with the waypt.
DirectToController(RNAV *aRNAV, const WayptRef &aWpt, const SGGeod &aOrigin)
virtual double trueBearingDeg() const
Bearing to the waypoint, if this value is meaningful.
double headingDegMagnetic() const
double xtrackErrorNm() const override
double courseDeviationDeg() const override
double distanceToWayptM() const override
Compute distance until the waypoint is done.
void setHoldCount(int count)
void update(double) override
SGGeod position() const override
Position associated with the waypt.
std::string status() const override
Allow waypoints to indicate a status value as a string.
HoldCtl(RNAV *aRNAV, const WayptRef &aWpt)
double distanceToWayptM() const override
Compute distance until the waypoint is done.
double xtrackErrorNm() const override
SGGeod position() const override
Position associated with the waypt.
InterceptCtl(RNAV *aRNAV, const WayptRef &aWpt)
void update(double) override
double courseDeviationDeg() const override
Controller for leg course interception.
virtual SGGeod position() const
Position associated with the waypt.
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
virtual double courseDeviationDeg() const
virtual double trueBearingDeg() const
Bearing to the waypoint, if this value is meaningful.
virtual double xtrackErrorNm() const
virtual bool toFlag() const
to/from flag - true = to, false = from.
void computeTurnAnticipation()
LegWayptCtl(RNAV *aRNAV, const WayptRef &aWpt)
std::optional< RNAV::LegData > legData() const override
void update(double) override
virtual void update(double dt)
OBSController(RNAV *aRNAV, const WayptRef &aWpt)
virtual double courseDeviationDeg() const
virtual double trueBearingDeg() const
Bearing to the waypoint, if this value is meaningful.
virtual double xtrackErrorNm() const
virtual SGGeod position() const
Position associated with the waypt.
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
virtual bool toFlag() const
to/from flag - true = to, false = from.
Abstract RNAV interface, for devices which implement an RNAV system - INS / GPS / FMS.
virtual SGGeod position()=0
virtual double groundSpeedKts()=0
Ground speed (along the track) in knots.
double courseDegMagnetic() const
double radialDegMagnetic() const
Special controller for runways.
RunwayCtl(RNAV *aRNAV, const WayptRef &aWpt)
virtual SGGeod position() const
Position associated with the waypt.
virtual double xtrackErrorNm() const
virtual double courseDeviationDeg() const
virtual double trueBearingDeg() const
Bearing to the waypoint, if this value is meaningful.
virtual void update(double)
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
Waypoint based upon a runway.
FGRunway * runway() const
virtual void update(double)
virtual SGGeod position() const
Position associated with the waypt.
VectorsCtl(RNAV *aRNAV, const WayptRef &aWpt)
virtual double distanceToWayptM() const
Compute distance until the waypoint is done.
virtual double trueBearingDeg() const
Bearing to the waypoint, if this value is meaningful.
virtual double courseDeviationDeg() const
WayptController(RNAV *aRNAV, const WayptRef &aWpt)
virtual double targetTrackDeg() const
std::unique_ptr< WayptController > _subController
void setSubController(WayptController *sub)
bool isDone() const
Is this controller finished?
virtual double distanceToWayptM() const =0
Compute distance until the waypoint is done.
virtual ~WayptController()
virtual std::string status() const
Allow waypoints to indicate a status value as a string.
virtual double timeToWaypt() const
Compute time until the waypoint is done.
static WayptController * createForWaypt(RNAV *rnav, const WayptRef &aWpt)
Static factory method, given a waypoint, return a controller bound to it, of the appropriate type.
virtual double xtrackErrorNm() const
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
const auto turnComputeDist
SGSharedPtr< Waypt > WayptRef
bool geocRadialIntersection(const SGGeoc &a, double r1, const SGGeoc &b, double r2, SGGeoc &result)
@ WPT_DYNAMIC
waypoint position is dynamic, i.e moves based on other criteria, such as altitude,...
@ WPT_OVERFLIGHT
must overfly the point directly
double greatCircleCrossTrackError(double distanceOriginToPosition, double courseDev)
Helper function Cross track error: http://williams.best.vwh.net/avform.htm#XTE.
@ RESTRICT_COMPUTED
data is computed, not a real restriction
SGGeod computeTurnCenter(double turnRadiusM, const SGGeod &basePos, double inboundTrack, double turnAngle)
double pointsKnownDistanceFromGC(const SGGeoc &a, const SGGeoc &b, const SGGeoc &d, double dist)
std::string replace(std::string str, const std::string &old, const std::string &newstr)
double fgGetLowPass(double current, double target, double timeratio)
Move a value towards a target.