FlightGear next
rnav_waypt_controller.cxx
Go to the documentation of this file.
1// rnav_waypt_controller.cxx - Waypoint-specific behaviours for RNAV systems
2// Written by James Turner, started 2009.
3//
4// Copyright (C) 2009 Curtis L. Olson
5//
6// This program is free software; you can redistribute it and/or
7// modify it under the terms of the GNU General Public License as
8// published by the Free Software Foundation; either version 2 of the
9// License, or (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful, but
12// WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14// General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program; if not, write to the Free Software
18// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
21
22#include <cassert>
23#include <stdexcept>
24
25#include <simgear/sg_inlines.h>
26#include <simgear/structure/exception.hxx>
27
28#include <Airports/runways.hxx>
29#include "Main/util.hxx" // for fgLowPass
30
31extern double pointsKnownDistanceFromGC(const SGGeoc& a, const SGGeoc&b, const SGGeoc& d, double dist);
32
33namespace flightgear
34{
35
36const auto turnComputeDist = SG_NM_TO_METER * 4.0;
37
38// declared in routePath.cxx
39bool geocRadialIntersection(const SGGeoc& a, double r1, const SGGeoc& b, double r2, SGGeoc& result);
40
58
59double greatCircleCrossTrackError(double distanceOriginToPosition,double courseDev){
60 double crossTrackError = asin(
61 sin(distanceOriginToPosition * SG_NM_TO_RAD) *
62 sin(courseDev * SG_DEGREES_TO_RADIANS)
63 );
64 return crossTrackError * SG_RAD_TO_NM;
65}
66
67SGGeod computeTurnCenter(double turnRadiusM, const SGGeod& basePos, double inboundTrack, double turnAngle)
68{
69
70 // this is the heading half way through the turn. Perpendicular to
71 // this is our turn center
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);
76
77 const double tcOffset = turnRadiusM / cos(turnAngle * 0.5 * SG_DEGREES_TO_RADIANS);
78 return SGGeodesy::direct(basePos, h, tcOffset);
79}
80
82
86
88{
89 return true;
90}
91
93{
94 if (_subController) {
95 return _subController->isDone();
96 }
97
98 return _isDone;
99}
100
102{
103 if (_isDone) {
104 SG_LOG(SG_AUTOPILOT, SG_DEV_WARN, "already done @ WayptController::setDone");
105 }
106
107 _isDone = true;
108}
109
111{
112 double gs = _rnav->groundSpeedKts();
113 if (gs < 1.0) {
114 return -1.0; // stationary
115 }
116
117 gs*= SG_KT_TO_MPS;
118 return (distanceToWayptM() / gs);
119}
120
121std::string WayptController::status() const
122{
123 return {};
124}
125
127{
128 _subController.reset(sub);
129 if (_subController) {
130 // seems like a good idea to check this
131 assert(_subController->_rnav == _rnav);
132 _subController->init();
133 }
134}
135
137{
138 if (_subController)
139 return _subController->trueBearingDeg();
140
141 return _targetTrack;
142}
143
145{
146 if (_subController)
147 return _subController->targetTrackDeg();
148
149 return _targetTrack;
150}
151
153{
154 if (_subController)
155 return _subController->xtrackErrorNm();
156
157 return 0.0;
158}
159
161{
162 if (_subController)
163 return _subController->courseDeviationDeg();
164
165 return 0.0;
166}
167
169
171{
172public:
173 BasicWayptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
174 WayptController(aRNAV, aWpt),
175 _distanceAircraftTargetMeter(0.0),
176 _courseDev(0.0)
177 {
178 if (aWpt->flag(WPT_DYNAMIC)) {
179 throw sg_exception("BasicWayptCtrl doesn't work with dynamic waypoints");
180 }
181 }
182
183 bool init() override
184 {
185 _targetTrack = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
186 return true;
187 }
188
189 virtual void update(double)
190 {
191 double bearingAircraftToTarget;
192
193 bearingAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
194 _distanceAircraftTargetMeter = SGGeodesy::distanceM(_rnav->position(), _waypt->position());
195
196 _courseDev = bearingAircraftToTarget - _targetTrack;
197 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
198
199 if ((fabs(_courseDev) > _rnav->overflightArmAngleDeg()) && (_distanceAircraftTargetMeter < _rnav->overflightArmDistanceM())) {
200 setDone();
201 }
202 }
203
204 virtual double distanceToWayptM() const
205 {
206 return _distanceAircraftTargetMeter;
207 }
208
209 virtual double xtrackErrorNm() const
210 {
211 double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceAircraftTargetMeter;
212 return x * SG_METER_TO_NM;
213 }
214
215 virtual bool toFlag() const
216 {
217 return (fabs(_courseDev) < _rnav->overflightArmAngleDeg());
218 }
219
220 virtual double courseDeviationDeg() const
221 {
222 return _courseDev;
223 }
224
225 virtual double trueBearingDeg() const
226 {
227 return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
228 }
229
230 virtual SGGeod position() const
231 {
232 return _waypt->position();
233 }
234
235private:
236 double _distanceAircraftTargetMeter;
237 double _courseDev;
238};
239
245
247{
248public:
249 LegWayptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
250 WayptController(aRNAV, aWpt),
251 _waypointOrigin(),
252 _distanceOriginAircraftMetre(0.0),
253 _distanceAircraftTargetMetre(0.0),
254 _courseOriginToAircraft(0.0),
255 _courseAircraftToTarget(0.0),
256 _courseDev(0.0),
257 _toFlag(true)
258 {
259 if (aWpt->flag(WPT_DYNAMIC)) {
260 throw sg_exception("LegWayptCtl doesn't work with dynamic waypoints");
261 }
262 }
263
264 bool init() override
265 {
266 auto previousLeg = _rnav->previousLegData();
267 if (previousLeg) {
268 _waypointOrigin = previousLeg.value().position;
269 _initialLegCourse = SGGeodesy::courseDeg(_waypointOrigin,_waypt->position());
270 } else {
271 // capture current position
272 _waypointOrigin = _rnav->position();
273 }
274
275 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
276 _distanceAircraftTargetMetre = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
277
278
279 // check reach the leg in 45Deg or going direct
280 bool canReachLeg = (fabs(_initialLegCourse -_courseAircraftToTarget) < 45.0);
281
282 if (previousLeg && canReachLeg) {
283 _targetTrack = _initialLegCourse;
284 } else {
285 // can't reach the leg with out a crazy turn, just go direct to the
286 // destination waypt
287 _targetTrack = _courseAircraftToTarget;
288 _initialLegCourse = _courseAircraftToTarget;
289 _waypointOrigin = _rnav->position();
290 }
291
292 // turn angle depends on final leg course, not initial
293 // do this here so we have a chance of doing a fly-by at the end of the leg
294 _finalLegCourse = SGGeodesy::courseDeg(_waypt->position(), _waypointOrigin) + 180;
295 SG_NORMALIZE_RANGE(_finalLegCourse, 0.0, 360.0);
296
297 // turn-in logic
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);
304 }
305
306 return true;
307 }
308
310 {
311 _didComputeTurn = true;
312
313 if (_waypt->flag(WPT_OVERFLIGHT)) {
314 return; // can't fly-by
315 }
316
317 if (!_rnav->canFlyBy())
318 return;
319
320 auto nextLegTrack = _rnav->nextLegTrack();
321
322 if (!nextLegTrack.has_value()) {
323 return;
324 }
325
326 _flyByTurnAngle = nextLegTrack.value() - _finalLegCourse;
327 SG_NORMALIZE_RANGE(_flyByTurnAngle, -180.0, 180.0);
328
329 if (fabs(_flyByTurnAngle) > _rnav->maxFlyByTurnAngleDeg()) {
330 // too sharp, don't anticipate
331 return;
332 }
333
334 _flyByTurnRadius = _rnav->turnRadiusNm() * SG_NM_TO_METER;
335 _flyByTurnCenter = computeTurnCenter(_flyByTurnRadius, _waypt->position(), _finalLegCourse, _flyByTurnAngle);
336 _doFlyBy = true;
337 }
338
340 {
341 // find bearing to turn center
342 // when it hits 90 off our track
343
344 auto bearingToTurnCenter = SGGeodesy::courseDeg(_rnav->position(), _flyByTurnCenter);
345 auto distToTurnCenter = SGGeodesy::distanceM(_rnav->position(), _flyByTurnCenter);
346
347 if (!_flyByStarted) {
348 // check for the turn center being 90 degrees off one wing (start the turn)
349 auto a = bearingToTurnCenter - _finalLegCourse;
350 SG_NORMALIZE_RANGE(a, -180.0, 180.0);
351 if (fabs(a) < 90.0) {
352 return false; // keep flying normal leg
353 }
354
355 _flyByStarted = true;
356 }
357
358 // check for us passing the half-way point; that's when we should
359 // sequence to the next WP
360 const auto halfPointAngle = (_finalLegCourse + (_flyByTurnAngle * 0.5));
361 auto b = bearingToTurnCenter - halfPointAngle;
362 SG_NORMALIZE_RANGE(b, -180.0, 180.0);
363
364 if (fabs(b) >= 90.0) {
365 _toFlag = false;
366 setDone();
367 }
368
369 // in the actual turn, our desired track is always pependicular to the
370 // bearing to the turn center we computed
371 _targetTrack = bearingToTurnCenter - copysign(90, _flyByTurnAngle);
372
373 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
374
375 _crossTrackError = (distToTurnCenter - _flyByTurnRadius) * SG_METER_TO_NM;
376 _courseDev = _crossTrackError * 10.0; // arbitrary guess for now
377
378 return true;
379 }
380
382 {
383 auto bearingToTurnCenter = SGGeodesy::courseDeg(_rnav->position(), _flyByTurnCenter);
384 auto distToTurnCenter = SGGeodesy::distanceM(_rnav->position(), _flyByTurnCenter);
385
386 auto b = bearingToTurnCenter - _initialLegCourse;
387
388 SG_NORMALIZE_RANGE(b, -180.0, 180.0);
389 if (fabs(b) >= 90.0) {
390 _entryFlyByActive = false;
391 return; // we're done with the entry turn
392 }
393
394 _targetTrack = bearingToTurnCenter - copysign(90, _flyByTurnAngle);
395
396 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
397 _crossTrackError = (distToTurnCenter - _flyByTurnRadius) * SG_METER_TO_NM;
398 _courseDev = _crossTrackError * 10.0; // arbitrary guess for now
399 }
400
401 void update(double) override
402 {
403 _courseOriginToAircraft = SGGeodesy::courseDeg(_waypointOrigin,_rnav->position());
404 _distanceOriginAircraftMetre = SGGeodesy::distanceM(_waypointOrigin,_rnav->position());
405
406 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
407 _distanceAircraftTargetMetre = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
408
409 if (_entryFlyByActive) {
411 return;
412 }
413
414 if (!_didComputeTurn && (_distanceAircraftTargetMetre < turnComputeDist)) {
416 }
417
418 if (_didComputeTurn && _doFlyBy) {
419 bool ok = updateInTurn();
420 if (ok) {
421 return;
422 }
423
424 // otherwise we fall through
425 }
426
427 // from the Aviation Formulary
428#if 0
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
433
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.)
438#endif
439 // however, just for fun, our convention for polarity of the cross-track
440 // sign is opposite, so we add a -ve to the computation below.
441
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));
444
445 // convert to NM and flip sign for consistency with existing code.
446 // since we derive the abeam point and course-deviation from this, and
447 // thus the GPS cdi-deflection, if we don't fix this here, the sign of
448 // all of those comes out backwards.
449 _crossTrackError = -(xtkRad * SG_RAD_TO_NM);
450
451 /*
452 The "along track distance", ATD, the distance from A along the course towards B
453 to the point abeam D is given by:
454 ATD=acos(cos(dist_AD)/cos(XTD))
455 */
456 double atd = acos(cos(distOriginAircraftRad) / cos(xtkRad));
457
458 // fix sign of ATD, if we are 'behind' the waypoint origin, rather than
459 // in front of it. We need to set a -ve sign on atd, otherwise we compute
460 // an abeamPoint ahead of the waypoint origin, potentially so far ahead
461 // that the computed track is backwards.
462 // see test-case GPSTests::testCourseLegIntermediateWaypoint
463 // for this happening
464 {
465 double x = _courseOriginToAircraft - _initialLegCourse;
466 SG_NORMALIZE_RANGE(x, -180.0, 180.0);
467 if (fabs(x) > 90.0) {
468 atd = -atd;
469 }
470 }
471
472 const double atdM = atd * SG_RAD_TO_NM * SG_NM_TO_METER;
473 SGGeod abeamPoint = SGGeodesy::direct(_waypointOrigin, _initialLegCourse, atdM);
474
475 // if we're close to the target point, we enter something similar to the VOR zone
476 // of confusion. Force target track to the final course from the origin.
477 if (_distanceAircraftTargetMetre < (SG_NM_TO_METER * 2.0)) {
478 _targetTrack = SGGeodesy::courseDeg(_waypt->position(), _waypointOrigin) + 180.0;
479 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
480 } else {
481 // use the abeam point to compute the desired course
482 double desiredCourse = SGGeodesy::courseDeg(abeamPoint, _waypt->position());
483 _targetTrack = desiredCourse;
484 }
485
486 _courseDev = _courseAircraftToTarget - _targetTrack;
487 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
488
489
490 bool isMinimumOverFlightDistanceReached = _distanceAircraftTargetMetre < _rnav->overflightDistanceM();
491 bool isOverFlightConeArmed = _distanceAircraftTargetMetre < ( _rnav->overflightArmDistanceM() + _rnav->overflightDistanceM() );
492 bool leavingOverFlightCone = (fabs(_courseDev) > _rnav->overflightArmAngleDeg());
493
494 if ( isMinimumOverFlightDistanceReached ){
495 _toFlag = false;
496 setDone();
497 } else {
498 if( isOverFlightConeArmed ){
499 _toFlag = false;
500 if ( leavingOverFlightCone ) {
501 setDone();
502 }
503 }else{
504 _toFlag = true;
505 }
506 }
507 }
508
509 std::optional<RNAV::LegData> legData() const override
510 {
512 r.position = _waypt->position();
513 if (_doFlyBy) {
514 // copy all the fly by paramters, so the next controller can
515 // smoothly link up
516 r.didFlyBy = true;
517 r.flyByRadius = _flyByTurnRadius;
518 r.flyByTurnCenter = _flyByTurnCenter;
519 r.turnAngle = _flyByTurnAngle;
520 }
521
522 return r;
523 }
524
525 virtual double distanceToWayptM() const
526 {
527 return _distanceAircraftTargetMetre;
528 }
529
530 virtual double xtrackErrorNm() const
531 {
532 return _crossTrackError;
533 }
534
535 virtual bool toFlag() const
536 {
537 return _toFlag;
538 }
539
540 virtual double courseDeviationDeg() const
541 {
542 return _courseDev;
543 }
544
545 virtual double trueBearingDeg() const
546 {
547 return _courseAircraftToTarget;
548 }
549
550 virtual SGGeod position() const
551 {
552 return _waypt->position();
553 }
554
555private:
556
557 /*
558 * great circle route
559 * A(from), B(to), D(position) perhaps off course
560 */
561 SGGeod _waypointOrigin;
562 double _initialLegCourse;
563 double _finalLegCourse;
564 double _distanceOriginAircraftMetre;
565 double _distanceAircraftTargetMetre;
566 double _courseOriginToAircraft;
567 double _courseAircraftToTarget;
568 double _courseDev;
569 bool _toFlag;
570 double _crossTrackError;
571
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;
579};
580
587{
588public:
589 RunwayCtl(RNAV* aRNAV, const WayptRef& aWpt) :
590 WayptController(aRNAV, aWpt),
591 _runway(NULL),
592 _distanceAircraftRunwayEnd(0.0),
593 _courseDev(0.0)
594 {
595 }
596
597 bool init() override
598 {
599 _runway = static_cast<RunwayWaypt*>(_waypt.get())->runway();
600 if (!_runway) {
601 return false;
602 }
603 _targetTrack = _runway->headingDeg();
604 return true;
605 }
606
607 virtual void update(double)
608 {
609 double bearingAircraftRunwayEnd;
610 // use the far end of the runway for course deviation calculations.
611 // this should do the correct thing both for takeoffs (including entering
612 // the runway at a taxiway after the threshold) and also landings.
613 // seperately compute the distance to the threshold for timeToWaypt calc
614
615 bearingAircraftRunwayEnd = SGGeodesy::courseDeg(_rnav->position(), _runway->end());
616 _distanceAircraftRunwayEnd = SGGeodesy::distanceM(_rnav->position(), _runway->end());
617
618 _courseDev = bearingAircraftRunwayEnd - _targetTrack;
619 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
620
621 if ((fabs(_courseDev) > _rnav->overflightArmAngleDeg()) && (_distanceAircraftRunwayEnd < _rnav->overflightArmDistanceM())) {
622 setDone();
623 }
624 }
625
626 virtual double distanceToWayptM() const
627 {
628 return SGGeodesy::distanceM(_rnav->position(), _runway->threshold());
629 }
630
631 virtual double xtrackErrorNm() const
632 {
633 double x = sin(_courseDev * SG_DEGREES_TO_RADIANS) * _distanceAircraftRunwayEnd;
634 return x * SG_METER_TO_NM;
635 }
636
637 virtual double courseDeviationDeg() const
638 {
639 return _courseDev;
640 }
641
642 virtual double trueBearingDeg() const
643 {
644 // as in update(), use runway->end here, so the value remains
645 // sensible whether taking off or landing.
646 return SGGeodesy::courseDeg(_rnav->position(), _runway->end());
647 }
648
649 virtual SGGeod position() const
650 {
651 return _runway->threshold();
652 }
653private:
654 FGRunway* _runway;
655 double _distanceAircraftRunwayEnd;
656 double _courseDev;
657};
658
660{
661public:
662 ConstHdgToAltCtl(RNAV* aRNAV, const WayptRef& aWpt) :
663 WayptController(aRNAV, aWpt)
664
665 {
666 if (_waypt->type() != "hdgToAlt") {
667 throw sg_exception("invalid waypoint type", "ConstHdgToAltCtl ctor");
668 }
669
670 if (_waypt->altitudeRestriction() == RESTRICT_NONE) {
671 throw sg_exception("invalid waypoint alt restriction", "ConstHdgToAltCtl ctor");
672 }
673 }
674
675 bool init() override
676 {
678 _targetTrack = w->headingDegMagnetic() + _rnav->magvarDeg();
679 _filteredFPM = _lastFPM = _rnav->vspeedFPM();
680 return true;
681 }
682
683 virtual void update(double dt)
684 {
685 double curAlt = _rnav->position().getElevationFt();
686 // adjust to get a stable FPM value; bigger values mean slower
687 // response but more stable.
688 const double RESPONSIVENESS = 1.0;
689
690 _filteredFPM = fgGetLowPass(_lastFPM, _rnav->vspeedFPM(), dt * RESPONSIVENESS);
691 _lastFPM = _rnav->vspeedFPM();
692
693 switch (_waypt->altitudeRestriction()) {
694 case RESTRICT_AT:
695 case RESTRICT_COMPUTED:
696 {
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());
700 setDone();
701 }
702 } break;
703
704 case RESTRICT_ABOVE:
705 if (curAlt >= _waypt->altitudeFt()) {
706 SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, above target altitude " << _waypt->altitudeFt());
707 setDone();
708 }
709 break;
710
711 case RESTRICT_BELOW:
712 if (curAlt <= _waypt->altitudeFt()) {
713 SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, below target altitude " << _waypt->altitudeFt());
714 setDone();
715 }
716 break;
717
718 default:
719 break;
720 }
721 }
722
723 virtual double timeToWaypt() const
724 {
725 double d = fabs(_rnav->position().getElevationFt() - _waypt->altitudeFt());
726 return (d / _filteredFPM) * 60.0;
727 }
728
729 virtual double distanceToWayptM() const
730 {
731 // we could filter ground speed here, but it's likely stable enough,
732 // and timeToWaypt already filters the FPM value
733 double gsMsec = _rnav->groundSpeedKts() * SG_KT_TO_MPS;
734 return timeToWaypt() * gsMsec;
735 }
736
737 virtual SGGeod position() const
738 {
739 SGGeod p;
740 double az2;
741 SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
742 return p;
743 }
744
745private:
746 double _lastFPM, _filteredFPM;
747};
748
750{
751public:
752 InterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
753 WayptController(aRNAV, aWpt),
754 _trueRadial(0.0)
755 {
756 if (_waypt->type() != "radialIntercept") {
757 throw sg_exception("invalid waypoint type", "InterceptCtl ctor");
758 }
759 }
760
761 bool init() override
762 {
764 _trueRadial = w->radialDegMagnetic() + _rnav->magvarDeg();
765 _targetTrack = w->courseDegMagnetic() + _rnav->magvarDeg();
766
767 _canFlyBy = !_waypt->flag(WPT_OVERFLIGHT) && _rnav->canFlyBy();
768
769 return true;
770 }
771
772 void update(double) override
773 {
774 SGGeoc c,
775 geocPos = SGGeoc::fromGeod(_rnav->position()),
776 geocWayptPos = SGGeoc::fromGeod(_waypt->position());
777
778 bool ok = geocRadialIntersection(geocPos, _targetTrack,
779 geocWayptPos, _trueRadial, c);
780 if (!ok) {
781 // try with a backwards offset from the waypt pos, in case the
782 // procedure waypt location is too close. (eg, KSFO OCEAN SID)
783
784 SGGeoc navidAdjusted = SGGeodesy::advanceDegM(geocWayptPos, _trueRadial, -10 * SG_NM_TO_METER);
786 navidAdjusted, _trueRadial, c);
787 if (!ok) {
788 SG_LOG(SG_INSTR, SG_WARN, "InterceptCtl, bad intersection, skipping waypt");
789 setDone();
790 return;
791 }
792 }
793
794 _projectedPosition = SGGeod::fromGeoc(c);
795 _distanceToProjectedInterceptM = SGGeodesy::distanceM(_rnav->position(), _projectedPosition);
796 if (!_didComputeTurn && (_distanceToProjectedInterceptM < turnComputeDist)) {
797 computeTurn();
798 }
799
800 if (_doFlyBy) {
802 }
803
804
805 // note we want the outbound radial from the waypt, hence the ordering
806 // of arguments to courseDeg
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) {
811 setDone();
812 }
813 }
814
816 {
817 // find bearing to turn center
818 // when it hits 90 off our track
819
820 auto bearingToTurnCenter = SGGeodesy::courseDeg(_rnav->position(), _flyByTurnCenter);
821 auto distToTurnCenter = SGGeodesy::distanceM(_rnav->position(), _flyByTurnCenter);
822
823 if (!_flyByStarted) {
824 // check for the turn center being 90 degrees off one wing (start the turn)
825 auto a = bearingToTurnCenter - _targetTrack;
826 SG_NORMALIZE_RANGE(a, -180.0, 180.0);
827 if (fabs(a) < 90.0) {
828 return false;
829 }
830
831 _flyByStarted = true;
832 }
833
834
835 // in the actual turn, our desired track is always pependicular to the
836 // bearing to the turn center we computed
837 _targetTrack = bearingToTurnCenter - copysign(90, _flyByTurnAngle);
838
839 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
840 _crossTrackError = (distToTurnCenter - _flyByTurnRadius) * SG_METER_TO_NM;
841 return true;
842 }
843
845 {
846 _didComputeTurn = true;
847 if (!_canFlyBy)
848 return;
849
850 double inverseRadial = _trueRadial + 180.0;
851 SG_NORMALIZE_RANGE(inverseRadial, 0.0, 360.0);
852 _flyByTurnAngle = inverseRadial - _targetTrack;
853 SG_NORMALIZE_RANGE(_flyByTurnAngle, -180.0, 180.0);
854
855 if (fabs(_flyByTurnAngle) > _rnav->maxFlyByTurnAngleDeg()) {
856 // too sharp, no fly-by
857 return;
858 }
859
860 _flyByTurnRadius = _rnav->turnRadiusNm() * SG_NM_TO_METER;
861 _flyByTurnCenter = computeTurnCenter(_flyByTurnRadius, _projectedPosition, _targetTrack, _flyByTurnAngle);
862 _doFlyBy = true;
863 }
864
865double distanceToWayptM() const override
866 {
867 return _distanceToProjectedInterceptM;
868 }
869
870 SGGeod position() const override
871 {
872 return _projectedPosition;
873 }
874
875 double xtrackErrorNm() const override
876 {
877 if (!_flyByStarted)
878 return 0.0; // unless we're doing the fly-by, we're always on course
879 return _crossTrackError;
880 }
881
882 double courseDeviationDeg() const override
883 {
884 // guessed scaling factor
885 return xtrackErrorNm() * 10.0;
886 }
887private:
888 double _trueRadial;
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;
899};
900
902{
903public:
904 DMEInterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
905 WayptController(aRNAV, aWpt),
906 _dme(NULL),
907 _distanceNm(0.0)
908 {
909 if (_waypt->type() != "dmeIntercept") {
910 throw sg_exception("invalid waypoint type", "DMEInterceptCtl ctor");
911 }
912 }
913
914 bool init() override
915 {
916 _dme = (DMEIntercept*) _waypt.get();
917 _targetTrack = _dme->courseDegMagnetic() + _rnav->magvarDeg();
918 return true;
919 }
920
921 virtual void update(double)
922 {
923 _distanceNm = SGGeodesy::distanceNm(_rnav->position(), _dme->position());
924 double d = fabs(_distanceNm - _dme->dmeDistanceNm());
925 if (d < 0.1) {
926 setDone();
927 }
928 }
929
930 virtual double distanceToWayptM() const
931 {
932 return fabs(_distanceNm - _dme->dmeDistanceNm()) * SG_NM_TO_METER;
933 }
934
935 virtual SGGeod position() const
936 {
937 SGGeoc geocPos = SGGeoc::fromGeod(_rnav->position());
938 SGGeoc navid = SGGeoc::fromGeod(_dme->position());
939 double distRad = _dme->dmeDistanceNm() * SG_NM_TO_RAD;
940
941 // compute radial GC course
942 SGGeoc bPt = SGGeodesy::advanceDegM(geocPos, _targetTrack, 1e5);
943 double dNm = pointsKnownDistanceFromGC(geocPos, bPt, navid, distRad);
944 if (dNm < 0.0) {
945 SG_LOG(SG_AUTOPILOT, SG_WARN, "DMEInterceptCtl::position failed");
946 return _dme->position(); // horrible fallback
947 }
948
949 return SGGeodesy::direct(_rnav->position(), _targetTrack, dNm * SG_NM_TO_METER);
950 }
951
952private:
953 DMEIntercept* _dme;
954 double _distanceNm;
955};
956
957
958HoldCtl::HoldCtl(RNAV* aRNAV, const WayptRef& aWpt) :
959 WayptController(aRNAV, aWpt)
960
961{
962 // find published hold for aWpt
963 // do we have this?!
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();
969 else
970 _holdLegTime = hold->timeOrDistance();
971 _leftHandTurns = hold->isLeftHanded();
972 }
973}
974
976{
977 _segmentEnd = _waypt->position();
978 _state = LEG_TO_HOLD;
979
980 // use leg controller to fly us to the hold point - this also gives
981 // the normal legl behaviour if the hold is not enabled
983
984 return true;
985}
986
987void HoldCtl::computeEntry()
988{
989 const double entryCourse = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
990 const double diff = SGMiscd::normalizePeriodic( -180.0, 180.0, _holdCourse - entryCourse);
991
992 if (_leftHandTurns) {
993 if ((diff > -70) && (diff < 120.0)) {
994 _state = ENTRY_DIRECT;
995 } else if (diff < 0.0) {
996 _state = ENTRY_PARALLEL;
997 } else {
998 _state = ENTRY_TEARDROP;
999 }
1000 } else {
1001 // right handed entry angles
1002 if ((diff > -120) && (diff < 70.0)) {
1003 _state = ENTRY_DIRECT;
1004 } else if (diff > 0.0) {
1005 _state = ENTRY_PARALLEL;
1006 } else {
1007 _state = ENTRY_TEARDROP;
1008 }
1009 }
1010}
1011
1012void HoldCtl::update(double dt)
1013{
1014 const auto rnavPos = _rnav->position();
1015 const double dEnd = SGGeodesy::distanceNm(rnavPos, _segmentEnd);
1016
1017 // fly inbound / outbound sides, or execute the turn
1018 switch (_state) {
1019 case LEG_TO_HOLD:
1020 // update the leg controller
1021 _subController->update(dt);
1022 break;
1023
1024 case HOLD_EXITING:
1025 // in the common case of a hold in a procedure, we often just fly
1026 // the hold waypoint as leg. Keep running the Leg sub-controller until
1027 // it's done (WayptController::isDone calls the subcController
1028 // automaticlaly)
1029 if (_subController) {
1030 _subController->update(dt);
1031 }
1032 break;
1033
1034 default:
1035 break;
1036 }
1037
1038 if (_inTurn) {
1039 const double turnOffset = inLeftTurn() ? 90 : -90;
1040 const double bearingTurnCenter = SGGeodesy::courseDeg(rnavPos, _turnCenter);
1041 _targetTrack = bearingTurnCenter + turnOffset;
1042 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
1043
1044 const double angleToTurn = SGMiscd::normalizePeriodic( -180.0, 180.0, _targetTrack - _turnEndAngle);
1045 if (fabs(angleToTurn) < 0.5) {
1046 exitTurn();
1047 }
1048 } else if (_state == LEG_TO_HOLD) {
1049 checkInitialEntry(dEnd);
1050 } else if ((_state == HOLD_INBOUND) || (_state == ENTRY_PARALLEL_INBOUND)) {
1051 if (checkOverHold()) {
1052 // we are done
1053 } else {
1054 // straight line XTK/deviation
1055 // computed on-demand in xtrackErrorNm so nothing to do here
1056 }
1057 } else if ((_state == HOLD_OUTBOUND) || (_state == ENTRY_TEARDROP)) {
1058 if (dEnd < 0.2) {
1059 startInboundTurn();
1060 } else {
1061 // straight line XTK
1062 }
1063 } else if (_state == ENTRY_PARALLEL_OUTBOUND) {
1064 if (dEnd < 0.2) {
1065 startParallelEntryTurn(); // opposite direction to normal
1066 } else {
1067 // straight line XTK
1068 }
1069 }
1070}
1071
1073{
1074 _holdCount = count;
1075}
1076
1078{
1079 _holdCount = 0;
1080}
1081
1082bool HoldCtl::checkOverHold()
1083{
1084 // check distance to entry fix
1085 const double d = SGGeodesy::distanceNm(_rnav->position(), _waypt->position());
1086 if (d > 0.2) {
1087 return false;
1088 }
1089
1090 if (_holdCount == 0) {
1091 setDone();
1092 return true;
1093 }
1094
1095 startOutboundTurn();
1096 return true;
1097}
1098
1099void HoldCtl::checkInitialEntry(double dNm)
1100{
1101 _turnRadius = _rnav->turnRadiusNm();
1102 if (dNm > _turnRadius) {
1103 // keep going;
1104 return;
1105 }
1106
1107 if (_holdCount == 0) {
1108 // we're done, but we want to keep the leg controller going until
1109 // we're right on top
1110 setDone();
1111
1112 // ensure we keep running the Leg cub-controller until it's done,
1113 // which happens a bit later
1114 _state = HOLD_EXITING;
1115 return;
1116 }
1117
1118 // clear the leg controller we were using to fly us to the hold
1119 setSubController(nullptr);
1120 computeEntry();
1121
1122 if (_state == ENTRY_DIRECT) {
1123 startOutboundTurn();
1124 } else if (_state == ENTRY_TEARDROP) {
1125 _targetTrack = _holdCourse + (_leftHandTurns ? -150 : 150);
1126 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
1127 _segmentEnd = outboundEndPoint();
1128 } else {
1129 // parallel entry
1130 assert(_state == ENTRY_PARALLEL);
1131 _state = ENTRY_PARALLEL_OUTBOUND;
1132 _targetTrack = _holdCourse + 180;
1133 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
1134 const double legLengthM = holdLegLengthNm() * SG_NM_TO_METER;
1135 _segmentEnd = SGGeodesy::direct(_waypt->position(), _holdCourse, -legLengthM);
1136 }
1137}
1138
1139void HoldCtl::startInboundTurn()
1140{
1141 _state = HOLD_INBOUND;
1142 _inTurn = true;
1143 _turnCenter = inboundTurnCenter();
1144 _turnRadius = _rnav->turnRadiusNm();
1145 _turnEndAngle = _holdCourse;
1146 SG_NORMALIZE_RANGE(_turnEndAngle, 0.0, 360.0);
1147}
1148
1149void HoldCtl::startOutboundTurn()
1150{
1151 _state = HOLD_OUTBOUND;
1152 _inTurn = true;
1153 _turnCenter = outboundTurnCenter();
1154 _turnRadius = _rnav->turnRadiusNm();
1155 _turnEndAngle = _holdCourse + 180.0;
1156 SG_NORMALIZE_RANGE(_turnEndAngle, 0.0, 360.0);
1157}
1158
1159void HoldCtl::startParallelEntryTurn()
1160{
1161 _state = ENTRY_PARALLEL_INBOUND;
1162 _inTurn = true;
1163 _turnRadius = _rnav->turnRadiusNm();
1164 _turnCenter = inboundTurnCenter();
1165 _turnEndAngle = _holdCourse + (_leftHandTurns ? 45.0 : -45.0);
1166 SG_NORMALIZE_RANGE(_turnEndAngle, 0.0, 360.0);
1167}
1168
1169void HoldCtl::exitTurn()
1170{
1171 _inTurn = false;
1172 switch (_state) {
1173 case HOLD_INBOUND:
1174 _targetTrack = _holdCourse;
1175 _segmentEnd = _waypt->position();
1176 break;
1177
1178 case ENTRY_PARALLEL_INBOUND:
1179 // possible improvement : fly the current track until the bearing tp
1180 // the hold point matches the hold radial. This would cause us to fly
1181 // a neater parallel entry, rather than flying to the hold point
1182 // direct from where we are now.
1183 _targetTrack = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
1184 _segmentEnd = _waypt->position();
1185 break;
1186
1187 case HOLD_OUTBOUND:
1188 _targetTrack = _holdCourse + 180.0;
1189 SG_NORMALIZE_RANGE(_targetTrack, 0.0, 360.0);
1190 // start a timer for timed holds?
1191 _segmentEnd = outboundEndPoint();
1192 break;
1193
1194 default:
1195 SG_LOG(SG_INSTR, SG_DEV_WARN, "HoldCOntroller: bad state at exitTurn:" << _state);
1196 }
1197}
1198
1199SGGeod HoldCtl::outboundEndPoint()
1200{
1201 // FIXME flip for left hand-turns
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);
1207}
1208
1209SGGeod HoldCtl::outboundTurnCenter()
1210{
1211 const double turnOffset = _leftHandTurns ? -90 : 90;
1212 return SGGeodesy::direct(_waypt->position(), _holdCourse + turnOffset, _rnav->turnRadiusNm() * SG_NM_TO_METER);
1213}
1214
1215SGGeod HoldCtl::inboundTurnCenter()
1216{
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);
1221}
1222
1224{
1225 return -1.0;
1226}
1227
1228SGGeod HoldCtl::position() const
1229{
1230 return _waypt->position();
1231}
1232
1233bool HoldCtl::inLeftTurn() const
1234{
1235 return (_state == ENTRY_PARALLEL_INBOUND) ? !_leftHandTurns : _leftHandTurns;
1236}
1237
1238double HoldCtl::holdLegLengthNm() const
1239{
1240 const double gs = _rnav->groundSpeedKts();
1241 if (_holdLegTime > 0.0) {
1242 return _holdLegTime * gs / 3600.0;
1243 }
1244
1245 return _holdLegDistance;
1246}
1247
1249{
1250 if (_subController) {
1251 return _subController->xtrackErrorNm();
1252 }
1253
1254 if (_inTurn) {
1255 const double dR = SGGeodesy::distanceNm(_turnCenter, _rnav->position());
1256 const double xtk = dR - _turnRadius;
1257 return inLeftTurn() ? -xtk : xtk;
1258 } else {
1259 const double courseToSegmentEnd = SGGeodesy::courseDeg(_rnav->position(), _segmentEnd);
1260 const double courseDev = courseToSegmentEnd - _targetTrack;
1261 const auto d = SGGeodesy::distanceNm(_rnav->position(), _segmentEnd);
1262 return greatCircleCrossTrackError(d, courseDev);
1263 }
1264}
1265
1267{
1268 if (_subController) {
1269 return _subController->courseDeviationDeg();
1270 }
1271
1272 // convert XTK to 'dots' deviation
1273 // assuming 10-degree peg to peg, this means 0.1nm of course is
1274 // one degree of error, feels about right for a hold
1275 return xtrackErrorNm() * 10.0;
1276}
1277
1278 std::string HoldCtl::status() const
1279 {
1280 switch (_state) {
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";
1290
1291 case HOLD_OUTBOUND: return "hold-outbound";
1292 case HOLD_INBOUND: return "hold-inbound";
1293 case HOLD_EXITING: return "hold-exiting";
1294 }
1295
1296 throw std::domain_error("Unsupported HoldState.");
1297 }
1298
1300
1302{
1303public:
1304 VectorsCtl(RNAV* aRNAV, const WayptRef& aWpt) :
1305 WayptController(aRNAV, aWpt)
1306
1307 {
1308 }
1309
1310 bool init() override
1311 {
1312 return true;
1313 }
1314
1315 virtual void update(double)
1316 {
1317 setDone();
1318 }
1319
1320 virtual double distanceToWayptM() const
1321 {
1322 return -1.0;
1323 }
1324
1325 virtual SGGeod position() const
1326 {
1327 return _waypt->position();
1328 }
1329
1330private:
1331};
1332
1334
1335DirectToController::DirectToController(RNAV* aRNAV, const WayptRef& aWpt, const SGGeod& aOrigin) :
1336 WayptController(aRNAV, aWpt),
1337 _origin(aOrigin),
1338 _distanceAircraftTargetMeter(0.0),
1339 _courseDev(0.0),
1340 _courseAircraftToTarget(0.0)
1341{
1342}
1343
1345{
1346 if (_waypt->flag(WPT_DYNAMIC)) {
1347 SG_LOG(SG_AUTOPILOT, SG_WARN, "can't use a dynamic waypoint for direct-to: " << _waypt->ident());
1348 return false;
1349 }
1350
1351 _targetTrack = SGGeodesy::courseDeg(_origin, _waypt->position());
1352 return true;
1353}
1354
1356{
1357 double courseOriginToAircraft;
1358 double az2, distanceOriginToAircraft;
1359 SGGeodesy::inverse(_origin,_rnav->position(), courseOriginToAircraft, az2, distanceOriginToAircraft);
1360 if (distanceOriginToAircraft < 100.0) {
1361 // if we are very close to the origin point, use the target track so
1362 // course deviation comes out as zero
1363 courseOriginToAircraft = _targetTrack;
1364 }
1365
1366 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
1367 _distanceAircraftTargetMeter = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
1368
1369 _courseDev = _courseAircraftToTarget - _targetTrack;
1370 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
1371
1372 bool isMinimumOverFlightDistanceReached = _distanceAircraftTargetMeter < _rnav->overflightDistanceM();
1373 bool isOverFlightConeArmed = _distanceAircraftTargetMeter < ( _rnav->overflightArmDistanceM() + _rnav->overflightDistanceM() );
1374 bool leavingOverFlightCone = (fabs(_courseAircraftToTarget - _targetTrack) > _rnav->overflightArmAngleDeg());
1375
1376 if( isMinimumOverFlightDistanceReached ){
1377 setDone();
1378 }else{
1379 if( isOverFlightConeArmed && leavingOverFlightCone ){
1380 setDone();
1381 }
1382 }
1383}
1384
1386{
1387 return _distanceAircraftTargetMeter;
1388}
1389
1391{
1392 return greatCircleCrossTrackError(_distanceAircraftTargetMeter * SG_METER_TO_NM, _courseDev);
1393}
1394
1396{
1397 return _courseDev;
1398}
1399
1401{
1402 return _courseAircraftToTarget;
1403}
1404
1406{
1407 return _waypt->position();
1408}
1409
1411
1413 WayptController(aRNAV, aWpt),
1414 _distanceAircraftTargetMeter(0.0),
1415 _courseDev(0.0),
1416 _courseAircraftToTarget(0.0)
1417{
1418}
1419
1421{
1422 if (_waypt->flag(WPT_DYNAMIC)) {
1423 SG_LOG(SG_AUTOPILOT, SG_WARN, "can't use a dynamic waypoint for OBS mode" << _waypt->ident());
1424 return false;
1425 }
1426
1427 _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
1428 return true;
1429}
1430
1432{
1433 _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
1434
1435 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
1436 _distanceAircraftTargetMeter = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
1437
1438 // _courseDev inverted we use point target as origin
1439 _courseDev = (_courseAircraftToTarget - _targetTrack);
1440 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
1441}
1442
1444{
1445 return (fabs(_courseDev) < _rnav->overflightArmAngleDeg());
1446}
1447
1449{
1450 return _distanceAircraftTargetMeter;
1451}
1452
1454{
1455 return greatCircleCrossTrackError(_distanceAircraftTargetMeter * SG_METER_TO_NM, _courseDev);
1456}
1457
1459{
1460// if (fabs(_courseDev) > 90.0) {
1461 // double d = -_courseDev;
1462 // SG_NORMALIZE_RANGE(d, -90.0, 90.0);
1463 // return d;
1464 //}
1465
1466 return _courseDev;
1467}
1468
1470{
1471 return _courseAircraftToTarget;
1472}
1473
1475{
1476 return _waypt->position();
1477}
1478
1480
1482{
1483 if (!aWpt) {
1484 throw sg_exception("Passed null waypt", "WayptController::createForWaypt");
1485 }
1486
1487 const std::string& wty(aWpt->type());
1488 if (wty == "runway") {
1489 return new RunwayCtl(aRNAV, aWpt);
1490 }
1491
1492 if (wty == "radialIntercept") {
1493 return new InterceptCtl(aRNAV, aWpt);
1494 }
1495
1496 if (wty == "dmeIntercept") {
1497 return new DMEInterceptCtl(aRNAV, aWpt);
1498 }
1499
1500 if (wty == "hdgToAlt") {
1501 return new ConstHdgToAltCtl(aRNAV, aWpt);
1502 }
1503
1504 if (wty == "vectors") {
1505 return new VectorsCtl(aRNAV, aWpt);
1506 }
1507
1508 if (wty == "hold") {
1509 return new HoldCtl(aRNAV, aWpt);
1510 }
1511
1512 return new LegWayptCtl(aRNAV, aWpt);
1513}
1514
1515} // of namespace flightgear
1516
#define p(x)
#define RESPONSIVENESS
Definition adf.cxx:31
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 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 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 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
Definition waypoint.hxx:205
double xtrackErrorNm() const override
double courseDeviationDeg() const override
double distanceToWayptM() const override
Compute distance until the waypoint is done.
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)
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.
LegWayptCtl(RNAV *aRNAV, const WayptRef &aWpt)
std::optional< RNAV::LegData > legData() const override
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
Definition waypoint.hxx:273
double radialDegMagnetic() const
Definition waypoint.hxx:276
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 double distanceToWayptM() const
Compute distance until the waypoint is done.
Waypoint based upon a runway.
Definition waypoint.hxx:117
FGRunway * runway() const
Definition waypoint.hxx:129
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 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.
#define B
#define A
#define N
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
const auto turnComputeDist
SGSharedPtr< Waypt > WayptRef
bool geocRadialIntersection(const SGGeoc &a, double r1, const SGGeoc &b, double r2, SGGeoc &result)
Definition routePath.cxx:39
@ WPT_DYNAMIC
waypoint position is dynamic, i.e moves based on other criteria, such as altitude,...
Definition route.hxx:49
@ WPT_OVERFLIGHT
must overfly the point directly
Definition route.hxx:44
double greatCircleCrossTrackError(double distanceOriginToPosition, double courseDev)
Helper function Cross track error: http://williams.best.vwh.net/avform.htm#XTE.
@ RESTRICT_NONE
Definition route.hxx:71
@ RESTRICT_AT
Definition route.hxx:72
@ RESTRICT_BELOW
Definition route.hxx:74
@ RESTRICT_COMPUTED
data is computed, not a real restriction
Definition route.hxx:78
@ RESTRICT_ABOVE
Definition route.hxx:73
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.
Definition util.cxx:46