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"
22 #include <simgear/sg_inlines.h>
23 #include <simgear/structure/exception.hxx>
25 #include <Airports/runways.hxx>
30 const double KNOTS_TO_METRES_PER_SECOND = SG_NM_TO_METER / 3600.0;
32 double pmod(double x, double y)
42 // http://williams.best.vwh.net/avform.htm#Intersection
43 bool geocRadialIntersection(const SGGeoc& a, double r1, const SGGeoc& b, double r2, SGGeoc& result)
45 double crs13 = r1 * SG_DEGREES_TO_RADIANS;
46 double crs23 = r2 * SG_DEGREES_TO_RADIANS;
47 double dst12 = SGGeodesy::distanceRad(a, b);
50 // crs12=acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
51 // crs21=2.*pi-acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
53 // crs12=2.*pi-acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
54 // crs21=acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
58 // double diffLon = b.getLongitudeRad() - a.getLongitudeRad();
60 double sinLat1 = sin(a.getLatitudeRad());
61 double cosLat1 = cos(a.getLatitudeRad());
62 // double sinLat2 = sin(b.getLatitudeRad());
63 //double cosLat2 = cos(b.getLatitudeRad());
64 double sinDst12 = sin(dst12);
65 double cosDst12 = cos(dst12);
67 double crs12 = SGGeodesy::courseRad(a, b),
68 crs21 = SGGeodesy::courseRad(b, a);
70 double degCrs12 = crs12 * SG_RADIANS_TO_DEGREES;
71 double degCrs21 = crs21 * SG_RADIANS_TO_DEGREES;
74 if (sin(diffLon) < 0.0) {
75 crs12 = acos((sinLat2 - sinLat1 * cosDst12) / (sinDst12 * cosLat1));
76 crs21 = SGMiscd::twopi() - acos((sinLat1 - sinLat2*cosDst12)/(sinDst12*cosLat2));
78 crs12 = SGMiscd::twopi() - acos((sinLat2 - sinLat1 * cosDst12)/(sinDst12 * cosLat1));
79 crs21 = acos((sinLat1 - sinLat2 * cosDst12)/(sinDst12 * cosLat2));
83 double ang1 = SGMiscd::normalizeAngle2(crs13-crs12);
84 double ang2 = SGMiscd::normalizeAngle2(crs21-crs23);
86 if ((sin(ang1) == 0.0) && (sin(ang2) == 0.0)) {
87 SG_LOG(SG_GENERAL, SG_WARN, "geocRadialIntersection: infinity of intersections");
91 if ((sin(ang1)*sin(ang2))<0.0) {
92 SG_LOG(SG_GENERAL, SG_WARN, "geocRadialIntersection: intersection ambiguous");
99 //ang3=acos(-cos(ang1)*cos(ang2)+sin(ang1)*sin(ang2)*cos(dst12))
100 //dst13=atan2(sin(dst12)*sin(ang1)*sin(ang2),cos(ang2)+cos(ang1)*cos(ang3))
101 //lat3=asin(sin(lat1)*cos(dst13)+cos(lat1)*sin(dst13)*cos(crs13))
103 //lon3=mod(lon1-dlon+pi,2*pi)-pi
105 double ang3 = acos(-cos(ang1) * cos(ang2) + sin(ang1) * sin(ang2) * cosDst12);
106 double dst13 = atan2(sinDst12 * sin(ang1) * sin(ang2), cos(ang2) + cos(ang1)*cos(ang3));
109 SGGeodesy::advanceRadM(a, crs13, dst13 * SG_RAD_TO_NM * SG_NM_TO_METER, pt3);
111 double lat3 = asin(sinLat1 * cos(dst13) + cosLat1 * sin(dst13) * cos(crs13));
113 //dlon=atan2(sin(crs13)*sin(dst13)*cos(lat1),cos(dst13)-sin(lat1)*sin(lat3))
114 double dlon = atan2(sin(crs13)*sin(dst13)*cosLat1, cos(dst13)- (sinLat1 * sin(lat3)));
115 double lon3 = SGMiscd::normalizeAngle(-a.getLongitudeRad()-dlon);
117 result = SGGeoc::fromRadM(-lon3, lat3, a.getRadiusM());
122 ////////////////////////////////////////////////////////////////////////////
124 WayptController::~WayptController()
128 void WayptController::init()
132 void WayptController::setDone()
135 SG_LOG(SG_AUTOPILOT, SG_WARN, "already done @ WayptController::setDone");
141 double WayptController::timeToWaypt() const
143 double gs = _rnav->groundSpeedKts();
145 return -1.0; // stationary
148 gs*= KNOTS_TO_METRES_PER_SECOND;
149 return (distanceToWayptM() / gs);
154 class BasicWayptCtl : public WayptController
157 BasicWayptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
158 WayptController(aRNAV, aWpt)
160 if (aWpt->flag(WPT_DYNAMIC)) {
161 throw sg_exception("BasicWayptCtrl doesn't work with dynamic waypoints");
167 _targetTrack = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
170 virtual void update()
173 SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM);
174 _courseDev = brg - _targetTrack;
175 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
177 if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
182 virtual double distanceToWayptM() const
187 virtual double xtrackErrorNm() const
189 double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceM;
190 return x * SG_METER_TO_NM;
193 virtual bool toFlag() const
195 return (fabs(_courseDev) < 90.0);
198 virtual double courseDeviationDeg() const
203 virtual double trueBearingDeg() const
205 return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
208 virtual SGGeod position() const
210 return _waypt->position();
219 * Special controller for runways. For runways, we want very narrow deviation
220 * contraints, and to understand that any point along the paved area is
221 * equivalent to being 'at' the runway.
223 class RunwayCtl : public WayptController
226 RunwayCtl(RNAV* aRNAV, const WayptRef& aWpt) :
227 WayptController(aRNAV, aWpt)
233 _runway = static_cast<RunwayWaypt*>(_waypt.get())->runway();
234 _targetTrack = _runway->headingDeg();
237 virtual void update()
240 // use the far end of the runway for course deviation calculations.
241 // this should do the correct thing both for takeoffs (including entering
242 // the runway at a taxiway after the threshold) and also landings.
243 // seperately compute the distance to the threshold for timeToWaypt calc
244 SGGeodesy::inverse(_rnav->position(), _runway->end(), brg, az2, _distanceM);
245 double _courseDev = brg - _targetTrack;
246 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
248 if (fabs(_courseDev) > 90.0) {
253 virtual double distanceToWayptM() const
255 return SGGeodesy::distanceM(_rnav->position(), _runway->threshold());
258 virtual double xtrackErrorNm() const
260 double x = sin(_courseDev * SG_RADIANS_TO_DEGREES) * _distanceM;
261 return x * SG_METER_TO_NM;
264 virtual double courseDeviationDeg() const
269 virtual double trueBearingDeg() const
271 // as in update(), use runway->end here, so the value remains
272 // sensible whether taking off or landing.
273 return SGGeodesy::courseDeg(_rnav->position(), _runway->end());
276 virtual SGGeod position() const
278 return _runway->threshold();
286 class ConstHdgToAltCtl : public WayptController
289 ConstHdgToAltCtl(RNAV* aRNAV, const WayptRef& aWpt) :
290 WayptController(aRNAV, aWpt)
293 if (_waypt->type() != "hdgToAlt") {
294 throw sg_exception("invalid waypoint type", "ConstHdgToAltCtl ctor");
297 if (_waypt->altitudeRestriction() == RESTRICT_NONE) {
298 throw sg_exception("invalid waypoint alt restriction", "ConstHdgToAltCtl ctor");
304 HeadingToAltitude* w = (HeadingToAltitude*) _waypt.get();
305 _targetTrack = w->headingDegMagnetic() + _rnav->magvarDeg();
308 virtual void update()
310 double curAlt = _rnav->position().getElevationFt();
312 switch (_waypt->altitudeRestriction()) {
314 double d = curAlt - _waypt->altitudeFt();
315 if (fabs(d) < 50.0) {
316 SG_LOG(SG_GENERAL, SG_INFO, "ConstHdgToAltCtl, reached target altitude " << _waypt->altitudeFt());
322 if (curAlt >= _waypt->altitudeFt()) {
323 SG_LOG(SG_GENERAL, SG_INFO, "ConstHdgToAltCtl, above target altitude " << _waypt->altitudeFt());
329 if (curAlt <= _waypt->altitudeFt()) {
330 SG_LOG(SG_GENERAL, SG_INFO, "ConstHdgToAltCtl, below target altitude " << _waypt->altitudeFt());
341 virtual double timeToWaypt() const
343 double d = fabs(_rnav->position().getElevationFt() - _waypt->altitudeFt());
344 return (d / _rnav->vspeedFPM()) * 60.0; // low pass filter here, probably
347 virtual double distanceToWayptM() const
349 double gsMsec = _rnav->groundSpeedKts() * KNOTS_TO_METRES_PER_SECOND;
350 return timeToWaypt() * gsMsec;
353 virtual SGGeod position() const
357 SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
362 class InterceptCtl : public WayptController
365 InterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
366 WayptController(aRNAV, aWpt)
369 if (_waypt->type() != "radialIntercept") {
370 throw sg_exception("invalid waypoint type", "InterceptCtl ctor");
376 RadialIntercept* w = (RadialIntercept*) _waypt.get();
377 _trueRadial = w->radialDegMagnetic() + _rnav->magvarDeg();
378 _targetTrack = w->courseDegMagnetic() + _rnav->magvarDeg();
381 virtual void update()
383 // note we want the outbound radial from the waypt, hence the ordering
384 // of arguments to courseDeg
385 double r = SGGeodesy::courseDeg(_waypt->position(), _rnav->position());
386 SG_LOG(SG_AUTOPILOT, SG_INFO, "current radial=" << r);
387 if (fabs(r - _trueRadial) < 0.5) {
388 SG_LOG(SG_GENERAL, SG_INFO, "InterceptCtl, intercepted radial " << _trueRadial);
393 virtual double distanceToWayptM() const
395 return SGGeodesy::distanceM(_rnav->position(), position());
398 virtual SGGeod position() const
401 geocRadialIntersection(SGGeoc::fromGeod(_rnav->position()), _rnav->trackDeg(),
402 SGGeoc::fromGeod(_waypt->position()), _trueRadial, c);
403 return SGGeod::fromGeoc(c);
409 class DMEInterceptCtl : public WayptController
412 DMEInterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
413 WayptController(aRNAV, aWpt)
416 if (_waypt->type() != "dmeIntercept") {
417 throw sg_exception("invalid waypoint type", "DMEInterceptCtl ctor");
423 _dme = (DMEIntercept*) _waypt.get();
424 _targetTrack = _dme->courseDegMagnetic() + _rnav->magvarDeg();
427 virtual void update()
429 _distanceNm = SGGeodesy::distanceNm(_rnav->position(), _dme->position());
430 double d = fabs(_distanceNm - _dme->dmeDistanceNm());
432 SG_LOG(SG_GENERAL, SG_INFO, "DMEInterceptCtl, intercepted DME " << _dme->dmeDistanceNm());
437 virtual double distanceToWayptM() const
439 return fabs(_distanceNm - _dme->dmeDistanceNm()) * SG_NM_TO_METER;
442 virtual SGGeod position() const
446 SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
455 class HoldCtl : public WayptController
458 HoldCtl(RNAV* aRNAV, const WayptRef& aWpt) :
459 WayptController(aRNAV, aWpt)
469 virtual void update()
471 // fly inbound / outbound sides, or execute the turn
475 targetTrack += dt * turnRateSec * turnDirection;
477 if .. targetTrack has passed inbound radial, doen with this turn
479 if target track has passed reciprocal radial done with turn
482 check time / distance elapsed
487 nextHeading = inbound;
489 nextHeading += 180.0;
490 SG_NORMALIZE_RANGE(nextHeading, 0.0, 360.0);
500 virtual double distanceToWayptM() const
505 virtual SGGeod position() const
507 return _waypt->position();
511 class VectorsCtl : public WayptController
514 VectorsCtl(RNAV* aRNAV, const WayptRef& aWpt) :
515 WayptController(aRNAV, aWpt)
525 virtual void update()
530 virtual double distanceToWayptM() const
535 virtual SGGeod position() const
537 return _waypt->position();
543 ///////////////////////////////////////////////////////////////////////////////
545 DirectToController::DirectToController(RNAV* aRNAV, const WayptRef& aWpt, const SGGeod& aOrigin) :
546 WayptController(aRNAV, aWpt),
551 void DirectToController::init()
553 if (_waypt->flag(WPT_DYNAMIC)) {
554 throw sg_exception("can't direct-to a dynamic waypoint");
557 _targetTrack = SGGeodesy::courseDeg(_origin, _waypt->position());
560 void DirectToController::update()
563 SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM);
564 _courseDev = brg - _targetTrack;
565 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
567 if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
572 double DirectToController::distanceToWayptM() const
577 double DirectToController::xtrackErrorNm() const
579 double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceM;
580 return x * SG_METER_TO_NM;
583 double DirectToController::courseDeviationDeg() const
588 double DirectToController::trueBearingDeg() const
590 return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
593 SGGeod DirectToController::position() const
595 return _waypt->position();
598 ///////////////////////////////////////////////////////////////////////////////
600 OBSController::OBSController(RNAV* aRNAV, const WayptRef& aWpt) :
601 WayptController(aRNAV, aWpt)
605 void OBSController::init()
607 if (_waypt->flag(WPT_DYNAMIC)) {
608 throw sg_exception("can't use a dynamic waypoint for OBS mode");
611 _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
614 void OBSController::update()
616 _targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
618 SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM);
619 _courseDev = brg - _targetTrack;
620 SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
623 bool OBSController::toFlag() const
625 return (fabs(_courseDev) < 90.0);
628 double OBSController::distanceToWayptM() const
633 double OBSController::xtrackErrorNm() const
635 double x = sin(_courseDev * SG_DEGREES_TO_RADIANS) * _distanceM;
636 return x * SG_METER_TO_NM;
639 double OBSController::courseDeviationDeg() const
641 // if (fabs(_courseDev) > 90.0) {
642 // double d = -_courseDev;
643 // SG_NORMALIZE_RANGE(d, -90.0, 90.0);
650 double OBSController::trueBearingDeg() const
652 return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
655 SGGeod OBSController::position() const
657 return _waypt->position();
660 ///////////////////////////////////////////////////////////////////////////////
662 WayptController* WayptController::createForWaypt(RNAV* aRNAV, const WayptRef& aWpt)
665 throw sg_exception("Passed null waypt", "WayptController::createForWaypt");
668 const std::string& wty(aWpt->type());
669 if (wty == "runway") {
670 return new RunwayCtl(aRNAV, aWpt);
673 if (wty == "radialIntercept") {
674 return new InterceptCtl(aRNAV, aWpt);
677 if (wty == "dmeIntercept") {
678 return new DMEInterceptCtl(aRNAV, aWpt);
681 if (wty == "hdgToAlt") {
682 return new ConstHdgToAltCtl(aRNAV, aWpt);
685 if (wty == "vectors") {
686 return new VectorsCtl(aRNAV, aWpt);
690 return new HoldCtl(aRNAV, aWpt);
693 return new BasicWayptCtl(aRNAV, aWpt);
696 } // of namespace flightgear