2009-10-11 12:37:13 +01:00
|
|
|
// rnav_waypt_controller.cxx - Waypoint-specific behaviours for RNAV systems
|
|
|
|
// Written by James Turner, started 2009.
|
|
|
|
//
|
|
|
|
// Copyright (C) 2009 Curtis L. Olson
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
#include "rnav_waypt_controller.hxx"
|
|
|
|
|
2010-10-20 11:48:06 +01:00
|
|
|
#include <cassert>
|
|
|
|
|
2009-10-11 12:37:13 +01:00
|
|
|
#include <simgear/sg_inlines.h>
|
|
|
|
#include <simgear/structure/exception.hxx>
|
|
|
|
|
|
|
|
#include <Airports/runways.hxx>
|
|
|
|
|
|
|
|
namespace flightgear
|
|
|
|
{
|
|
|
|
|
|
|
|
const double KNOTS_TO_METRES_PER_SECOND = SG_NM_TO_METER / 3600.0;
|
|
|
|
|
|
|
|
double pmod(double x, double y)
|
|
|
|
{
|
|
|
|
if (x < 0.0) {
|
|
|
|
return -fmod(x, y);
|
|
|
|
} else {
|
|
|
|
return fmod(x,y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// implementation of
|
|
|
|
// http://williams.best.vwh.net/avform.htm#Intersection
|
|
|
|
bool geocRadialIntersection(const SGGeoc& a, double r1, const SGGeoc& b, double r2, SGGeoc& result)
|
|
|
|
{
|
|
|
|
double crs13 = r1 * SG_DEGREES_TO_RADIANS;
|
|
|
|
double crs23 = r2 * SG_DEGREES_TO_RADIANS;
|
|
|
|
double dst12 = SGGeodesy::distanceRad(a, b);
|
|
|
|
|
|
|
|
//IF sin(lon2-lon1)<0
|
|
|
|
// crs12=acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
|
|
|
|
// crs21=2.*pi-acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
|
|
|
|
// ELSE
|
|
|
|
// crs12=2.*pi-acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
|
|
|
|
// crs21=acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
|
|
|
|
// ENDIF
|
|
|
|
|
|
|
|
|
|
|
|
// double diffLon = b.getLongitudeRad() - a.getLongitudeRad();
|
|
|
|
|
|
|
|
double sinLat1 = sin(a.getLatitudeRad());
|
|
|
|
double cosLat1 = cos(a.getLatitudeRad());
|
|
|
|
// double sinLat2 = sin(b.getLatitudeRad());
|
|
|
|
//double cosLat2 = cos(b.getLatitudeRad());
|
|
|
|
double sinDst12 = sin(dst12);
|
|
|
|
double cosDst12 = cos(dst12);
|
|
|
|
|
|
|
|
double crs12 = SGGeodesy::courseRad(a, b),
|
|
|
|
crs21 = SGGeodesy::courseRad(b, a);
|
|
|
|
|
2010-12-05 20:35:21 +01:00
|
|
|
//double degCrs12 = crs12 * SG_RADIANS_TO_DEGREES;
|
|
|
|
//double degCrs21 = crs21 * SG_RADIANS_TO_DEGREES;
|
2009-10-11 12:37:13 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
if (sin(diffLon) < 0.0) {
|
|
|
|
crs12 = acos((sinLat2 - sinLat1 * cosDst12) / (sinDst12 * cosLat1));
|
|
|
|
crs21 = SGMiscd::twopi() - acos((sinLat1 - sinLat2*cosDst12)/(sinDst12*cosLat2));
|
|
|
|
} else {
|
|
|
|
crs12 = SGMiscd::twopi() - acos((sinLat2 - sinLat1 * cosDst12)/(sinDst12 * cosLat1));
|
|
|
|
crs21 = acos((sinLat1 - sinLat2 * cosDst12)/(sinDst12 * cosLat2));
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
double ang1 = SGMiscd::normalizeAngle2(crs13-crs12);
|
|
|
|
double ang2 = SGMiscd::normalizeAngle2(crs21-crs23);
|
|
|
|
|
|
|
|
if ((sin(ang1) == 0.0) && (sin(ang2) == 0.0)) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "geocRadialIntersection: infinity of intersections");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((sin(ang1)*sin(ang2))<0.0) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "geocRadialIntersection: intersection ambiguous");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ang1 = fabs(ang1);
|
|
|
|
ang2 = fabs(ang2);
|
|
|
|
|
|
|
|
//ang3=acos(-cos(ang1)*cos(ang2)+sin(ang1)*sin(ang2)*cos(dst12))
|
|
|
|
//dst13=atan2(sin(dst12)*sin(ang1)*sin(ang2),cos(ang2)+cos(ang1)*cos(ang3))
|
|
|
|
//lat3=asin(sin(lat1)*cos(dst13)+cos(lat1)*sin(dst13)*cos(crs13))
|
|
|
|
|
|
|
|
//lon3=mod(lon1-dlon+pi,2*pi)-pi
|
|
|
|
|
|
|
|
double ang3 = acos(-cos(ang1) * cos(ang2) + sin(ang1) * sin(ang2) * cosDst12);
|
|
|
|
double dst13 = atan2(sinDst12 * sin(ang1) * sin(ang2), cos(ang2) + cos(ang1)*cos(ang3));
|
|
|
|
|
|
|
|
SGGeoc pt3;
|
|
|
|
SGGeodesy::advanceRadM(a, crs13, dst13 * SG_RAD_TO_NM * SG_NM_TO_METER, pt3);
|
|
|
|
|
|
|
|
double lat3 = asin(sinLat1 * cos(dst13) + cosLat1 * sin(dst13) * cos(crs13));
|
|
|
|
|
|
|
|
//dlon=atan2(sin(crs13)*sin(dst13)*cos(lat1),cos(dst13)-sin(lat1)*sin(lat3))
|
|
|
|
double dlon = atan2(sin(crs13)*sin(dst13)*cosLat1, cos(dst13)- (sinLat1 * sin(lat3)));
|
|
|
|
double lon3 = SGMiscd::normalizeAngle(-a.getLongitudeRad()-dlon);
|
|
|
|
|
|
|
|
result = SGGeoc::fromRadM(-lon3, lat3, a.getRadiusM());
|
|
|
|
//result = pt3;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
WayptController::~WayptController()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void WayptController::init()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void WayptController::setDone()
|
|
|
|
{
|
|
|
|
if (_isDone) {
|
|
|
|
SG_LOG(SG_AUTOPILOT, SG_WARN, "already done @ WayptController::setDone");
|
|
|
|
}
|
|
|
|
|
|
|
|
_isDone = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
double WayptController::timeToWaypt() const
|
|
|
|
{
|
|
|
|
double gs = _rnav->groundSpeedKts();
|
|
|
|
if (gs < 1.0) {
|
|
|
|
return -1.0; // stationary
|
|
|
|
}
|
|
|
|
|
|
|
|
gs*= KNOTS_TO_METRES_PER_SECOND;
|
|
|
|
return (distanceToWayptM() / gs);
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////
|
|
|
|
|
|
|
|
class BasicWayptCtl : public WayptController
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
BasicWayptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
|
2010-12-05 20:35:21 +01:00
|
|
|
WayptController(aRNAV, aWpt),
|
|
|
|
_distanceM(0.0),
|
|
|
|
_courseDev(0.0)
|
2009-10-11 12:37:13 +01:00
|
|
|
{
|
|
|
|
if (aWpt->flag(WPT_DYNAMIC)) {
|
|
|
|
throw sg_exception("BasicWayptCtrl doesn't work with dynamic waypoints");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void init()
|
|
|
|
{
|
|
|
|
_targetTrack = SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void update()
|
|
|
|
{
|
|
|
|
double brg, az2;
|
|
|
|
SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM);
|
|
|
|
_courseDev = brg - _targetTrack;
|
|
|
|
SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
|
|
|
|
|
|
|
|
if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return _distanceM;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double xtrackErrorNm() const
|
|
|
|
{
|
|
|
|
double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceM;
|
|
|
|
return x * SG_METER_TO_NM;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool toFlag() const
|
|
|
|
{
|
|
|
|
return (fabs(_courseDev) < 90.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double courseDeviationDeg() const
|
|
|
|
{
|
|
|
|
return _courseDev;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double trueBearingDeg() const
|
|
|
|
{
|
|
|
|
return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SGGeod position() const
|
|
|
|
{
|
|
|
|
return _waypt->position();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
double _distanceM;
|
|
|
|
double _courseDev;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Special controller for runways. For runways, we want very narrow deviation
|
2011-01-28 00:06:23 +01:00
|
|
|
* constraints, and to understand that any point along the paved area is
|
2009-10-11 12:37:13 +01:00
|
|
|
* equivalent to being 'at' the runway.
|
|
|
|
*/
|
|
|
|
class RunwayCtl : public WayptController
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
RunwayCtl(RNAV* aRNAV, const WayptRef& aWpt) :
|
2010-12-05 20:35:21 +01:00
|
|
|
WayptController(aRNAV, aWpt),
|
|
|
|
_runway(NULL),
|
|
|
|
_distanceM(0.0),
|
|
|
|
_courseDev(0.0)
|
2009-10-11 12:37:13 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void init()
|
|
|
|
{
|
|
|
|
_runway = static_cast<RunwayWaypt*>(_waypt.get())->runway();
|
|
|
|
_targetTrack = _runway->headingDeg();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void update()
|
|
|
|
{
|
|
|
|
double brg, az2;
|
|
|
|
// use the far end of the runway for course deviation calculations.
|
|
|
|
// this should do the correct thing both for takeoffs (including entering
|
|
|
|
// the runway at a taxiway after the threshold) and also landings.
|
|
|
|
// seperately compute the distance to the threshold for timeToWaypt calc
|
|
|
|
SGGeodesy::inverse(_rnav->position(), _runway->end(), brg, az2, _distanceM);
|
|
|
|
double _courseDev = brg - _targetTrack;
|
|
|
|
SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
|
|
|
|
|
2011-01-28 00:06:23 +01:00
|
|
|
if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
|
2009-10-11 12:37:13 +01:00
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return SGGeodesy::distanceM(_rnav->position(), _runway->threshold());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double xtrackErrorNm() const
|
|
|
|
{
|
|
|
|
double x = sin(_courseDev * SG_RADIANS_TO_DEGREES) * _distanceM;
|
|
|
|
return x * SG_METER_TO_NM;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double courseDeviationDeg() const
|
|
|
|
{
|
|
|
|
return _courseDev;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double trueBearingDeg() const
|
|
|
|
{
|
|
|
|
// as in update(), use runway->end here, so the value remains
|
|
|
|
// sensible whether taking off or landing.
|
|
|
|
return SGGeodesy::courseDeg(_rnav->position(), _runway->end());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SGGeod position() const
|
|
|
|
{
|
|
|
|
return _runway->threshold();
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
FGRunway* _runway;
|
|
|
|
double _distanceM;
|
|
|
|
double _courseDev;
|
|
|
|
};
|
|
|
|
|
|
|
|
class ConstHdgToAltCtl : public WayptController
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ConstHdgToAltCtl(RNAV* aRNAV, const WayptRef& aWpt) :
|
|
|
|
WayptController(aRNAV, aWpt)
|
|
|
|
|
|
|
|
{
|
|
|
|
if (_waypt->type() != "hdgToAlt") {
|
|
|
|
throw sg_exception("invalid waypoint type", "ConstHdgToAltCtl ctor");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_waypt->altitudeRestriction() == RESTRICT_NONE) {
|
|
|
|
throw sg_exception("invalid waypoint alt restriction", "ConstHdgToAltCtl ctor");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void init()
|
|
|
|
{
|
|
|
|
HeadingToAltitude* w = (HeadingToAltitude*) _waypt.get();
|
|
|
|
_targetTrack = w->headingDegMagnetic() + _rnav->magvarDeg();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void update()
|
|
|
|
{
|
|
|
|
double curAlt = _rnav->position().getElevationFt();
|
|
|
|
|
|
|
|
switch (_waypt->altitudeRestriction()) {
|
|
|
|
case RESTRICT_AT: {
|
|
|
|
double d = curAlt - _waypt->altitudeFt();
|
|
|
|
if (fabs(d) < 50.0) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "ConstHdgToAltCtl, reached target altitude " << _waypt->altitudeFt());
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case RESTRICT_ABOVE:
|
|
|
|
if (curAlt >= _waypt->altitudeFt()) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "ConstHdgToAltCtl, above target altitude " << _waypt->altitudeFt());
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RESTRICT_BELOW:
|
|
|
|
if (curAlt <= _waypt->altitudeFt()) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "ConstHdgToAltCtl, below target altitude " << _waypt->altitudeFt());
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RESTRICT_NONE:
|
|
|
|
assert(false);
|
|
|
|
break;
|
2011-02-02 22:05:54 +01:00
|
|
|
case SPEED_RESTRICT_MACH:
|
|
|
|
assert(false);
|
|
|
|
break;
|
2009-10-11 12:37:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double timeToWaypt() const
|
|
|
|
{
|
|
|
|
double d = fabs(_rnav->position().getElevationFt() - _waypt->altitudeFt());
|
|
|
|
return (d / _rnav->vspeedFPM()) * 60.0; // low pass filter here, probably
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double distanceToWayptM() const
|
|
|
|
{
|
|
|
|
double gsMsec = _rnav->groundSpeedKts() * KNOTS_TO_METRES_PER_SECOND;
|
|
|
|
return timeToWaypt() * gsMsec;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SGGeod position() const
|
|
|
|
{
|
|
|
|
SGGeod p;
|
|
|
|
double az2;
|
|
|
|
SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class InterceptCtl : public WayptController
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
InterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
|
2010-12-05 20:35:21 +01:00
|
|
|
WayptController(aRNAV, aWpt),
|
|
|
|
_trueRadial(0.0)
|
2009-10-11 12:37:13 +01:00
|
|
|
{
|
|
|
|
if (_waypt->type() != "radialIntercept") {
|
|
|
|
throw sg_exception("invalid waypoint type", "InterceptCtl ctor");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void init()
|
|
|
|
{
|
|
|
|
RadialIntercept* w = (RadialIntercept*) _waypt.get();
|
|
|
|
_trueRadial = w->radialDegMagnetic() + _rnav->magvarDeg();
|
|
|
|
_targetTrack = w->courseDegMagnetic() + _rnav->magvarDeg();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void update()
|
|
|
|
{
|
|
|
|
// note we want the outbound radial from the waypt, hence the ordering
|
|
|
|
// of arguments to courseDeg
|
|
|
|
double r = SGGeodesy::courseDeg(_waypt->position(), _rnav->position());
|
|
|
|
SG_LOG(SG_AUTOPILOT, SG_INFO, "current radial=" << r);
|
|
|
|
if (fabs(r - _trueRadial) < 0.5) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "InterceptCtl, intercepted radial " << _trueRadial);
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return SGGeodesy::distanceM(_rnav->position(), position());
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SGGeod position() const
|
|
|
|
{
|
|
|
|
SGGeoc c;
|
|
|
|
geocRadialIntersection(SGGeoc::fromGeod(_rnav->position()), _rnav->trackDeg(),
|
|
|
|
SGGeoc::fromGeod(_waypt->position()), _trueRadial, c);
|
|
|
|
return SGGeod::fromGeoc(c);
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
double _trueRadial;
|
|
|
|
};
|
|
|
|
|
|
|
|
class DMEInterceptCtl : public WayptController
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DMEInterceptCtl(RNAV* aRNAV, const WayptRef& aWpt) :
|
2010-12-05 20:35:21 +01:00
|
|
|
WayptController(aRNAV, aWpt),
|
|
|
|
_dme(NULL),
|
|
|
|
_distanceNm(0.0)
|
2009-10-11 12:37:13 +01:00
|
|
|
{
|
|
|
|
if (_waypt->type() != "dmeIntercept") {
|
|
|
|
throw sg_exception("invalid waypoint type", "DMEInterceptCtl ctor");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void init()
|
|
|
|
{
|
|
|
|
_dme = (DMEIntercept*) _waypt.get();
|
|
|
|
_targetTrack = _dme->courseDegMagnetic() + _rnav->magvarDeg();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void update()
|
|
|
|
{
|
|
|
|
_distanceNm = SGGeodesy::distanceNm(_rnav->position(), _dme->position());
|
|
|
|
double d = fabs(_distanceNm - _dme->dmeDistanceNm());
|
|
|
|
if (d < 0.1) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "DMEInterceptCtl, intercepted DME " << _dme->dmeDistanceNm());
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return fabs(_distanceNm - _dme->dmeDistanceNm()) * SG_NM_TO_METER;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SGGeod position() const
|
|
|
|
{
|
|
|
|
SGGeod p;
|
|
|
|
double az2;
|
|
|
|
SGGeodesy::direct(_rnav->position(), _targetTrack, distanceToWayptM(), p, az2);
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
DMEIntercept* _dme;
|
|
|
|
double _distanceNm;
|
|
|
|
};
|
|
|
|
|
|
|
|
class HoldCtl : public WayptController
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
HoldCtl(RNAV* aRNAV, const WayptRef& aWpt) :
|
|
|
|
WayptController(aRNAV, aWpt)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void init()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void update()
|
|
|
|
{
|
|
|
|
// fly inbound / outbound sides, or execute the turn
|
|
|
|
#if 0
|
|
|
|
if (inTurn) {
|
|
|
|
|
|
|
|
targetTrack += dt * turnRateSec * turnDirection;
|
|
|
|
if (inbound) {
|
|
|
|
if .. targetTrack has passed inbound radial, doen with this turn
|
|
|
|
} else {
|
|
|
|
if target track has passed reciprocal radial done with turn
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
check time / distance elapsed
|
|
|
|
|
|
|
|
if (sideDone) {
|
|
|
|
inTurn = true;
|
|
|
|
inbound = !inbound;
|
|
|
|
nextHeading = inbound;
|
|
|
|
if (!inbound) {
|
|
|
|
nextHeading += 180.0;
|
|
|
|
SG_NORMALIZE_RANGE(nextHeading, 0.0, 360.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return -1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SGGeod position() const
|
|
|
|
{
|
|
|
|
return _waypt->position();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class VectorsCtl : public WayptController
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
VectorsCtl(RNAV* aRNAV, const WayptRef& aWpt) :
|
|
|
|
WayptController(aRNAV, aWpt)
|
|
|
|
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void init()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void update()
|
|
|
|
{
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual double distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return -1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual SGGeod position() const
|
|
|
|
{
|
|
|
|
return _waypt->position();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
DirectToController::DirectToController(RNAV* aRNAV, const WayptRef& aWpt, const SGGeod& aOrigin) :
|
|
|
|
WayptController(aRNAV, aWpt),
|
2010-12-05 20:35:21 +01:00
|
|
|
_origin(aOrigin),
|
|
|
|
_distanceM(0.0),
|
|
|
|
_courseDev(0.0)
|
2009-10-11 12:37:13 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void DirectToController::init()
|
|
|
|
{
|
|
|
|
if (_waypt->flag(WPT_DYNAMIC)) {
|
|
|
|
throw sg_exception("can't direct-to a dynamic waypoint");
|
|
|
|
}
|
|
|
|
|
|
|
|
_targetTrack = SGGeodesy::courseDeg(_origin, _waypt->position());
|
|
|
|
}
|
|
|
|
|
|
|
|
void DirectToController::update()
|
|
|
|
{
|
|
|
|
double brg, az2;
|
|
|
|
SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM);
|
|
|
|
_courseDev = brg - _targetTrack;
|
|
|
|
SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
|
|
|
|
|
|
|
|
if ((fabs(_courseDev) > 90.0) && (_distanceM < _rnav->overflightArmDistanceM())) {
|
|
|
|
setDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
double DirectToController::distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return _distanceM;
|
|
|
|
}
|
|
|
|
|
|
|
|
double DirectToController::xtrackErrorNm() const
|
|
|
|
{
|
|
|
|
double x = sin(courseDeviationDeg() * SG_DEGREES_TO_RADIANS) * _distanceM;
|
|
|
|
return x * SG_METER_TO_NM;
|
|
|
|
}
|
|
|
|
|
|
|
|
double DirectToController::courseDeviationDeg() const
|
|
|
|
{
|
|
|
|
return _courseDev;
|
|
|
|
}
|
|
|
|
|
|
|
|
double DirectToController::trueBearingDeg() const
|
|
|
|
{
|
|
|
|
return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
|
|
|
|
}
|
|
|
|
|
|
|
|
SGGeod DirectToController::position() const
|
|
|
|
{
|
|
|
|
return _waypt->position();
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
OBSController::OBSController(RNAV* aRNAV, const WayptRef& aWpt) :
|
2010-12-05 20:35:21 +01:00
|
|
|
WayptController(aRNAV, aWpt),
|
|
|
|
_distanceM(0.0),
|
|
|
|
_courseDev(0.0)
|
2009-10-11 12:37:13 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void OBSController::init()
|
|
|
|
{
|
|
|
|
if (_waypt->flag(WPT_DYNAMIC)) {
|
|
|
|
throw sg_exception("can't use a dynamic waypoint for OBS mode");
|
|
|
|
}
|
|
|
|
|
|
|
|
_targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
|
|
|
|
}
|
|
|
|
|
|
|
|
void OBSController::update()
|
|
|
|
{
|
|
|
|
_targetTrack = _rnav->selectedMagCourse() + _rnav->magvarDeg();
|
|
|
|
double brg, az2;
|
|
|
|
SGGeodesy::inverse(_rnav->position(), _waypt->position(), brg, az2, _distanceM);
|
|
|
|
_courseDev = brg - _targetTrack;
|
|
|
|
SG_NORMALIZE_RANGE(_courseDev, -180.0, 180.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool OBSController::toFlag() const
|
|
|
|
{
|
|
|
|
return (fabs(_courseDev) < 90.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
double OBSController::distanceToWayptM() const
|
|
|
|
{
|
|
|
|
return _distanceM;
|
|
|
|
}
|
|
|
|
|
|
|
|
double OBSController::xtrackErrorNm() const
|
|
|
|
{
|
|
|
|
double x = sin(_courseDev * SG_DEGREES_TO_RADIANS) * _distanceM;
|
|
|
|
return x * SG_METER_TO_NM;
|
|
|
|
}
|
|
|
|
|
|
|
|
double OBSController::courseDeviationDeg() const
|
|
|
|
{
|
|
|
|
// if (fabs(_courseDev) > 90.0) {
|
|
|
|
// double d = -_courseDev;
|
|
|
|
// SG_NORMALIZE_RANGE(d, -90.0, 90.0);
|
|
|
|
// return d;
|
|
|
|
//}
|
|
|
|
|
|
|
|
return _courseDev;
|
|
|
|
}
|
|
|
|
|
|
|
|
double OBSController::trueBearingDeg() const
|
|
|
|
{
|
|
|
|
return SGGeodesy::courseDeg(_rnav->position(), _waypt->position());
|
|
|
|
}
|
|
|
|
|
|
|
|
SGGeod OBSController::position() const
|
|
|
|
{
|
|
|
|
return _waypt->position();
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
WayptController* WayptController::createForWaypt(RNAV* aRNAV, const WayptRef& aWpt)
|
|
|
|
{
|
|
|
|
if (!aWpt) {
|
|
|
|
throw sg_exception("Passed null waypt", "WayptController::createForWaypt");
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& wty(aWpt->type());
|
|
|
|
if (wty == "runway") {
|
|
|
|
return new RunwayCtl(aRNAV, aWpt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wty == "radialIntercept") {
|
|
|
|
return new InterceptCtl(aRNAV, aWpt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wty == "dmeIntercept") {
|
|
|
|
return new DMEInterceptCtl(aRNAV, aWpt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wty == "hdgToAlt") {
|
|
|
|
return new ConstHdgToAltCtl(aRNAV, aWpt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wty == "vectors") {
|
|
|
|
return new VectorsCtl(aRNAV, aWpt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wty == "hold") {
|
|
|
|
return new HoldCtl(aRNAV, aWpt);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new BasicWayptCtl(aRNAV, aWpt);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // of namespace flightgear
|
|
|
|
|