1 // rnav_waypt_controller.cxx - Waypoint-specific behaviours for RNAV systems
2 // Written by James Turner, started 2009.
4 // Copyright (C) 2009 Curtis L. Olson
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.
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.
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.
20 #include "rnav_waypt_controller.hxx"
24 #include <simgear/sg_inlines.h>
25 #include <simgear/structure/exception.hxx>
27 #include <Airports/runways.hxx>
28 #include "Main/util.hxx" // for fgLowPass
30 extern double pointsKnownDistanceFromGC(const SGGeoc& a, const SGGeoc&b, const SGGeoc& d, double dist);
36 // http://williams.best.vwh.net/avform.htm#Intersection
37 bool geocRadialIntersection(const SGGeoc& a, double r1, const SGGeoc& b, double r2, SGGeoc& result)
39 double crs13 = r1 * SG_DEGREES_TO_RADIANS;
40 double crs23 = r2 * SG_DEGREES_TO_RADIANS;
41 double dst12 = SGGeodesy::distanceRad(a, b);
44 // crs12=acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
45 // crs21=2.*pi-acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
47 // crs12=2.*pi-acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
48 // crs21=acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
51 double sinLat1 = sin(a.getLatitudeRad());
52 double cosLat1 = cos(a.getLatitudeRad());
53 double sinDst12 = sin(dst12);
54 double cosDst12 = cos(dst12);
56 double crs12 = SGGeodesy::courseRad(a, b),
57 crs21 = SGGeodesy::courseRad(b, a);
60 // normalise to -pi .. pi range
61 double ang1 = SGMiscd::normalizeAngle(crs13-crs12);
62 double ang2 = SGMiscd::normalizeAngle(crs21-crs23);
64 if ((sin(ang1) == 0.0) && (sin(ang2) == 0.0)) {
65 SG_LOG(SG_INSTR, SG_INFO, "geocRadialIntersection: infinity of intersections");
69 if ((sin(ang1)*sin(ang2))<0.0) {
70 // SG_LOG(SG_INSTR, SG_INFO, "geocRadialIntersection: intersection ambiguous:"
71 // << ang1 << " " << ang2 << " sin1 " << sin(ang1) << " sin2 " << sin(ang2));
78 //ang3=acos(-cos(ang1)*cos(ang2)+sin(ang1)*sin(ang2)*cos(dst12))
79 //dst13=atan2(sin(dst12)*sin(ang1)*sin(ang2),cos(ang2)+cos(ang1)*cos(ang3))
80 //lat3=asin(sin(lat1)*cos(dst13)+cos(lat1)*sin(dst13)*cos(crs13))
82 //lon3=mod(lon1-dlon+pi,2*pi)-pi
84 double ang3 = acos(-cos(ang1) * cos(ang2) + sin(ang1) * sin(ang2) * cosDst12);
85 double dst13 = atan2(sinDst12 * sin(ang1) * sin(ang2), cos(ang2) + cos(ang1)*cos(ang3));
88 SGGeodesy::advanceRadM(a, crs13, dst13 * SG_RAD_TO_NM * SG_NM_TO_METER, pt3);
90 double lat3 = asin(sinLat1 * cos(dst13) + cosLat1 * sin(dst13) * cos(crs13));
92 //dlon=atan2(sin(crs13)*sin(dst13)*cos(lat1),cos(dst13)-sin(lat1)*sin(lat3))
93 double dlon = atan2(sin(crs13)*sin(dst13)*cosLat1, cos(dst13)- (sinLat1 * sin(lat3)));
94 double lon3 = SGMiscd::normalizeAngle(-a.getLongitudeRad()-dlon);
96 result = SGGeoc::fromRadM(-lon3, lat3, a.getRadiusM());
102 * Helper function Cross track error:
103 * http://williams.best.vwh.net/avform.htm#XTE
105 * param distanceOriginToPosition in Nautical Miles
106 * param courseDev difference between courseOriginToAircraft(AD) and courseOriginToTarget(AB)
107 * return XTE in Nautical Miles
111 * D(aircraft) perhaps off course
119 double greatCircleCrossTrackError(double distanceOriginToPosition,double courseDev){
120 double crossTrackError = asin(
121 sin(distanceOriginToPosition * SG_NM_TO_RAD) *
122 sin(courseDev * SG_DEGREES_TO_RADIANS)
124 return crossTrackError * SG_RAD_TO_NM;
128 ////////////////////////////////////////////////////////////////////////////
130 WayptController::~WayptController()
134 void WayptController::init()
138 void WayptController::setDone()
141 SG_LOG(SG_AUTOPILOT, SG_WARN, "already done @ WayptController::setDone");
147 double WayptController::timeToWaypt() const
149 double gs = _rnav->groundSpeedKts();
151 return -1.0; // stationary
155 return (distanceToWayptM() / gs);
160 class BasicWayptCtl : public WayptController
163 BasicWayptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
164 WayptController(aRNAV, aWpt),
165 _distanceAircraftTargetMeter(0.0),
168 if (aWpt->flag(WPT_DYNAMIC)) {
169 throw sg_exception("BasicWayptCtrl doesn't work with dynamic waypoints");
175 _targetTrack = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
178 virtual void update(double)
180 double bearingAircraftToTarget;
182 bearingAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
183 _distanceAircraftTargetMeter = SGGeodesy::distanceM(_rnav->position(), _waypt->position());
185 _courseDev = bearingAircraftToTarget - _targetTrack;
186 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
188 if ((fabs(_courseDev) > _rnav->overflightArmAngleDeg()) && (_distanceAircraftTargetMeter < _rnav->overflightArmDistanceM())) {
193 virtual double distanceToWayptM() const
195 return _distanceAircraftTargetMeter;
198 virtual double xtrackErrorNm() const
200 double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceAircraftTargetMeter;
201 return x * SG_METER_TO_NM;
204 virtual bool toFlag() const
206 return (fabs(_courseDev) < _rnav->overflightArmAngleDeg());
209 virtual double courseDeviationDeg() const
214 virtual double trueBearingDeg() const
216 return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
219 virtual SGGeod position() const
221 return _waypt->position();
225 double _distanceAircraftTargetMeter;
230 * Controller for leg course interception.
231 * In leg mode, we want to intercept the leg between 2 waypoints(A->B).
232 * If we can't reach the the selected waypoint leg,we going direct to B.
235 class LegWayptCtl : public WayptController
238 LegWayptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
239 WayptController(aRNAV, aWpt),
241 _distanceOriginAircraftMetre(0.0),
242 _distanceAircraftTargetMetre(0.0),
243 _courseOriginToAircraft(0.0),
244 _courseAircraftToTarget(0.0),
248 if (aWpt->flag(WPT_DYNAMIC)) {
249 throw sg_exception("LegWayptCtl doesn't work with dynamic waypoints");
255 double courseOriginTarget;
256 bool isPreviousLegValid = false;
258 _waypointOrigin = _rnav->previousLegWaypointPosition(isPreviousLegValid);
260 courseOriginTarget = SGGeodesy::courseDeg(_waypointOrigin,_waypt->position());
262 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
263 _distanceAircraftTargetMetre = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
266 // check reach the leg in 45Deg or going direct
267 bool canReachLeg = (fabs(courseOriginTarget -_courseAircraftToTarget) < 45.0);
269 if ( isPreviousLegValid && canReachLeg){
270 _targetTrack = courseOriginTarget;
272 _targetTrack = _courseAircraftToTarget;
273 _waypointOrigin = _rnav->position();
278 virtual void update(double)
280 _courseOriginToAircraft = SGGeodesy::courseDeg(_waypointOrigin,_rnav->position());
281 _distanceOriginAircraftMetre = SGGeodesy::distanceM(_waypointOrigin,_rnav->position());
283 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
284 _distanceAircraftTargetMetre = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
286 _courseDev = -(_courseOriginToAircraft - _targetTrack);
288 bool isMinimumOverFlightDistanceReached = _distanceAircraftTargetMetre < _rnav->overflightDistanceM();
289 bool isOverFlightConeArmed = _distanceAircraftTargetMetre < ( _rnav->overflightArmDistanceM() + _rnav->overflightDistanceM() );
290 bool leavingOverFlightCone = (fabs(_courseAircraftToTarget - _targetTrack) > _rnav->overflightArmAngleDeg());
292 if( isMinimumOverFlightDistanceReached ){
296 if( isOverFlightConeArmed ){
298 if ( leavingOverFlightCone ) {
307 virtual double distanceToWayptM() const
309 return _distanceAircraftTargetMetre;
312 virtual double xtrackErrorNm() const
314 return greatCircleCrossTrackError(_distanceOriginAircraftMetre * SG_METER_TO_NM, _courseDev);
317 virtual bool toFlag() const
322 virtual double courseDeviationDeg() const
327 virtual double trueBearingDeg() const
329 return _courseAircraftToTarget;
332 virtual SGGeod position() const
334 return _waypt->position();
341 * A(from), B(to), D(position) perhaps off course
343 SGGeod _waypointOrigin;
344 double _distanceOriginAircraftMetre;
345 double _distanceAircraftTargetMetre;
346 double _courseOriginToAircraft;
347 double _courseAircraftToTarget;
354 * Special controller for runways. For runways, we want very narrow deviation
355 * constraints, and to understand that any point along the paved area is
356 * equivalent to being 'at' the runway.
358 class RunwayCtl : public WayptController
361 RunwayCtl(RNAV* aRNAV, const WayptRef& aWpt) :
362 WayptController(aRNAV, aWpt),
364 _distanceAircraftRunwayEnd(0.0),
371 _runway = static_cast<RunwayWaypt*>(_waypt.get())->runway();
372 _targetTrack = _runway->headingDeg();
375 virtual void update(double)
377 double bearingAircraftRunwayEnd;
378 // use the far end of the runway for course deviation calculations.
379 // this should do the correct thing both for takeoffs (including entering
380 // the runway at a taxiway after the threshold) and also landings.
381 // seperately compute the distance to the threshold for timeToWaypt calc
383 bearingAircraftRunwayEnd = SGGeodesy::courseDeg(_rnav->position(), _runway->end());
384 _distanceAircraftRunwayEnd = SGGeodesy::distanceM(_rnav->position(), _runway->end());
386 double _courseDev = bearingAircraftRunwayEnd - _targetTrack;
387 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
389 if ((fabs(_courseDev) > _rnav->overflightArmAngleDeg()) && (_distanceAircraftRunwayEnd < _rnav->overflightArmDistanceM())) {
394 virtual double distanceToWayptM() const
396 return SGGeodesy::distanceM(_rnav->position(), _runway->threshold());
399 virtual double xtrackErrorNm() const
401 double x = sin(_courseDev * SG_DEGREES_TO_RADIANS) * _distanceAircraftRunwayEnd;
402 return x * SG_METER_TO_NM;
405 virtual double courseDeviationDeg() const
410 virtual double trueBearingDeg() const
412 // as in update(), use runway->end here, so the value remains
413 // sensible whether taking off or landing.
414 return SGGeodesy::courseDeg(_rnav->position(), _runway->end());
417 virtual SGGeod position() const
419 return _runway->threshold();
423 double _distanceAircraftRunwayEnd;
427 class ConstHdgToAltCtl : public WayptController
430 ConstHdgToAltCtl(RNAV* aRNAV, const WayptRef& aWpt) :
431 WayptController(aRNAV, aWpt)
434 if (_waypt->type() != "hdgToAlt") {
435 throw sg_exception("invalid waypoint type", "ConstHdgToAltCtl ctor");
438 if (_waypt->altitudeRestriction() == RESTRICT_NONE) {
439 throw sg_exception("invalid waypoint alt restriction", "ConstHdgToAltCtl ctor");
445 HeadingToAltitude* w = (HeadingToAltitude*) _waypt.get();
446 _targetTrack = w->headingDegMagnetic() + _rnav->magvarDeg();
447 _filteredFPM = _lastFPM = _rnav->vspeedFPM();
450 virtual void update(double dt)
452 double curAlt = _rnav->position().getElevationFt();
453 // adjust to get a stable FPM value; bigger values mean slower
454 // response but more stable.
455 const double RESPONSIVENESS = 1.0;
457 _filteredFPM = fgGetLowPass(_lastFPM, _rnav->vspeedFPM(), dt * RESPONSIVENESS);
458 _lastFPM = _rnav->vspeedFPM();
460 switch (_waypt->altitudeRestriction()) {
462 case RESTRICT_COMPUTED:
464 double d = curAlt - _waypt->altitudeFt();
465 if (fabs(d) < 50.0) {
466 SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, reached target altitude " << _waypt->altitudeFt());
472 if (curAlt >= _waypt->altitudeFt()) {
473 SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, above target altitude " << _waypt->altitudeFt());
479 if (curAlt <= _waypt->altitudeFt()) {
480 SG_LOG(SG_INSTR, SG_INFO, "ConstHdgToAltCtl, below target altitude " << _waypt->altitudeFt());
490 virtual double timeToWaypt() const
492 double d = fabs(_rnav->position().getElevationFt() - _waypt->altitudeFt());
493 return (d / _filteredFPM) * 60.0;
496 virtual double distanceToWayptM() const
498 // we could filter ground speed here, but it's likely stable enough,
499 // and timeToWaypt already filters the FPM value
500 double gsMsec = _rnav->groundSpeedKts() * SG_KT_TO_MPS;
501 return timeToWaypt() * gsMsec;
504 virtual SGGeod position() const
508 SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
513 double _lastFPM, _filteredFPM;
516 class InterceptCtl : public WayptController
519 InterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
520 WayptController(aRNAV, aWpt),
523 if (_waypt->type() != "radialIntercept") {
524 throw sg_exception("invalid waypoint type", "InterceptCtl ctor");
530 RadialIntercept* w = (RadialIntercept*) _waypt.get();
531 _trueRadial = w->radialDegMagnetic() + _rnav->magvarDeg();
532 _targetTrack = w->courseDegMagnetic() + _rnav->magvarDeg();
535 virtual void update(double)
538 geocPos = SGGeoc::fromGeod(_rnav->position()),
539 geocWayptPos = SGGeoc::fromGeod(_waypt->position());
541 bool ok = geocRadialIntersection(geocPos, _targetTrack,
542 geocWayptPos, _trueRadial, c);
544 // try with a backwards offset from the waypt pos, in case the
545 // procedure waypt location is too close. (eg, KSFO OCEAN SID)
547 SGGeoc navidAdjusted;
548 SGGeodesy::advanceRadM(geocWayptPos, _trueRadial, SG_NM_TO_METER * -10, navidAdjusted);
550 ok = geocRadialIntersection(geocPos, _targetTrack,
551 navidAdjusted, _trueRadial, c);
553 SG_LOG(SG_INSTR, SG_WARN, "InterceptCtl, bad intersection, skipping waypt");
559 _projectedPosition = SGGeod::fromGeoc(c);
562 // note we want the outbound radial from the waypt, hence the ordering
563 // of arguments to courseDeg
564 double r = SGGeodesy::courseDeg(_waypt->position(), _rnav->position());
565 double bearingDiff = r - _trueRadial;
566 SG_NORMALIZE_RANGE(bearingDiff, -180.0, 180.0);
567 if (fabs(bearingDiff) < 0.5) {
572 virtual double distanceToWayptM() const
574 return SGGeodesy::distanceM(_rnav->position(), position());
577 virtual SGGeod position() const
579 return _projectedPosition;
583 SGGeod _projectedPosition;
586 class DMEInterceptCtl : public WayptController
589 DMEInterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
590 WayptController(aRNAV, aWpt),
594 if (_waypt->type() != "dmeIntercept") {
595 throw sg_exception("invalid waypoint type", "DMEInterceptCtl ctor");
601 _dme = (DMEIntercept*) _waypt.get();
602 _targetTrack = _dme->courseDegMagnetic() + _rnav->magvarDeg();
605 virtual void update(double)
607 _distanceNm = SGGeodesy::distanceNm(_rnav->position(), _dme->position());
608 double d = fabs(_distanceNm - _dme->dmeDistanceNm());
614 virtual double distanceToWayptM() const
616 return fabs(_distanceNm - _dme->dmeDistanceNm()) * SG_NM_TO_METER;
619 virtual SGGeod position() const
621 SGGeoc geocPos = SGGeoc::fromGeod(_rnav->position());
622 SGGeoc navid = SGGeoc::fromGeod(_dme->position());
623 double distRad = _dme->dmeDistanceNm() * SG_NM_TO_RAD;
625 // compute radial GC course
627 SGGeodesy::advanceRadM(geocPos, _targetTrack, 100 * SG_NM_TO_RAD, bPt);
629 double dNm = pointsKnownDistanceFromGC(geocPos, bPt, navid, distRad);
631 SG_LOG(SG_AUTOPILOT, SG_WARN, "DMEInterceptCtl::position failed");
632 return _dme->position(); // horrible fallback
635 return SGGeodesy::direct(_rnav->position(), _targetTrack, dNm * SG_NM_TO_METER);
643 class HoldCtl : public WayptController
646 HoldCtl(RNAV* aRNAV, const WayptRef& aWpt) :
647 WayptController(aRNAV, aWpt)
657 virtual void update(double)
659 // fly inbound / outbound sides, or execute the turn
663 targetTrack += dt * turnRateSec * turnDirection;
665 if .. targetTrack has passed inbound radial, doen with this turn
667 if target track has passed reciprocal radial done with turn
670 check time / distance elapsed
675 nextHeading = inbound;
677 nextHeading += 180.0;
678 SG_NORMALIZE_RANGE(nextHeading, 0.0, 360.0);
688 virtual double distanceToWayptM() const
693 virtual SGGeod position() const
695 return _waypt->position();
699 class VectorsCtl : public WayptController
702 VectorsCtl(RNAV* aRNAV, const WayptRef& aWpt) :
703 WayptController(aRNAV, aWpt)
713 virtual void update(double)
718 virtual double distanceToWayptM() const
723 virtual SGGeod position() const
725 return _waypt->position();
731 ///////////////////////////////////////////////////////////////////////////////
733 DirectToController::DirectToController(RNAV* aRNAV, const WayptRef& aWpt, const SGGeod& aOrigin) :
734 WayptController(aRNAV, aWpt),
736 _distanceAircraftTargetMeter(0.0),
738 _courseAircraftToTarget(0.0)
742 void DirectToController::init()
744 if (_waypt->flag(WPT_DYNAMIC)) {
745 throw sg_exception("can't direct-to a dynamic waypoint");
748 _targetTrack = SGGeodesy::courseDeg(_origin, _waypt->position());
751 void DirectToController::update(double)
753 double courseOriginToAircraft;
755 courseOriginToAircraft = SGGeodesy::courseDeg(_origin,_rnav->position());
757 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
758 _distanceAircraftTargetMeter = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
760 _courseDev = -(courseOriginToAircraft - _targetTrack);
762 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
764 bool isMinimumOverFlightDistanceReached = _distanceAircraftTargetMeter < _rnav->overflightDistanceM();
765 bool isOverFlightConeArmed = _distanceAircraftTargetMeter < ( _rnav->overflightArmDistanceM() + _rnav->overflightDistanceM() );
766 bool leavingOverFlightCone = (fabs(_courseAircraftToTarget - _targetTrack) > _rnav->overflightArmAngleDeg());
768 if( isMinimumOverFlightDistanceReached ){
771 if( isOverFlightConeArmed && leavingOverFlightCone ){
777 double DirectToController::distanceToWayptM() const
779 return _distanceAircraftTargetMeter;
782 double DirectToController::xtrackErrorNm() const
784 return greatCircleCrossTrackError(_distanceAircraftTargetMeter * SG_METER_TO_NM, _courseDev);
787 double DirectToController::courseDeviationDeg() const
792 double DirectToController::trueBearingDeg() const
794 return _courseAircraftToTarget;
797 SGGeod DirectToController::position() const
799 return _waypt->position();
802 ///////////////////////////////////////////////////////////////////////////////
804 OBSController::OBSController(RNAV* aRNAV, const WayptRef& aWpt) :
805 WayptController(aRNAV, aWpt),
806 _distanceAircraftTargetMeter(0.0),
808 _courseAircraftToTarget(0.0)
812 void OBSController::init()
814 if (_waypt->flag(WPT_DYNAMIC)) {
815 throw sg_exception("can't use a dynamic waypoint for OBS mode");
818 _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
821 void OBSController::update(double)
823 _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
825 _courseAircraftToTarget = SGGeodesy::courseDeg(_rnav->position(),_waypt->position());
826 _distanceAircraftTargetMeter = SGGeodesy::distanceM(_rnav->position(),_waypt->position());
828 // _courseDev inverted we use point target as origin
829 _courseDev = (_courseAircraftToTarget - _targetTrack);
830 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
833 bool OBSController::toFlag() const
835 return (fabs(_courseDev) < _rnav->overflightArmAngleDeg());
838 double OBSController::distanceToWayptM() const
840 return _distanceAircraftTargetMeter;
843 double OBSController::xtrackErrorNm() const
845 return greatCircleCrossTrackError(_distanceAircraftTargetMeter * SG_METER_TO_NM, _courseDev);
848 double OBSController::courseDeviationDeg() const
850 // if (fabs(_courseDev) > 90.0) {
851 // double d = -_courseDev;
852 // SG_NORMALIZE_RANGE(d, -90.0, 90.0);
859 double OBSController::trueBearingDeg() const
861 return _courseAircraftToTarget;
864 SGGeod OBSController::position() const
866 return _waypt->position();
869 ///////////////////////////////////////////////////////////////////////////////
871 WayptController* WayptController::createForWaypt(RNAV* aRNAV, const WayptRef& aWpt)
874 throw sg_exception("Passed null waypt", "WayptController::createForWaypt");
877 const std::string& wty(aWpt->type());
878 if (wty == "runway") {
879 return new RunwayCtl(aRNAV, aWpt);
882 if (wty == "radialIntercept") {
883 return new InterceptCtl(aRNAV, aWpt);
886 if (wty == "dmeIntercept") {
887 return new DMEInterceptCtl(aRNAV, aWpt);
890 if (wty == "hdgToAlt") {
891 return new ConstHdgToAltCtl(aRNAV, aWpt);
894 if (wty == "vectors") {
895 return new VectorsCtl(aRNAV, aWpt);
899 return new HoldCtl(aRNAV, aWpt);
902 return new LegWayptCtl(aRNAV, aWpt);
905 } // of namespace flightgear