This class steers the aircraft from one waypoint to the next. There are 3 phases to this process: the outbound radial phase, the great circle phase and the approach and turn phase.
The aircraft begins its journey from its airport of origin. This is deemed to be the first waypoint of the route. The aircraft therefore begins its journey by flying outbound from this first waypoint. It flies along the particular outbound radial from this first waypoint which points to the second waypoint in the route. The steering in this phase of the leg [the journey between two waypoints] is normally done by reference to a radio aid such as a VOR station. Hence the name of the method getRadioHeading() which expedites this phase. The outbound radial steering is done by the second clause of the method's main if statement.
If the next waypoint is less than 300km away, the outbound radial which is used is not the one which points directly at the next waypoint. Instead, the radial used is the one which is the tangent to the next waypoint's appropriate turning guide circle. Turning guide circles are explained later. The outbound radial from the current waypoint is displayed on the applet's map canvas as a short cyan-coloured line.
This phase takes place only for legs (inter-waypoint distances) which are greater than 300km. When the aircraft is more than 150km from its nearest waypoint, navigational references with respect to that waypoint become affected significantly by the curvature of the Earth. The direction North from the point of view of the aircraft is significantly different from the direction North seen by an observer at the waypoint. It is therefore necessary in this phase of the inter-waypoint transit to refer all bearings to the aircraft's notion of where North is.
The objective in this phase is to keep the aircraft flying as closely as possible to the great circle which passes through the two waypoints which mark the beginning and end of the leg. To do this, both waypoints are used for navigation: not just one as in the case of the other two phases. If the bearing of receding waypoint as seen from the aircraft is 180° from the bearing of the approaching waypoint as seen from the aircraft, then the aircraft is on the said great circle.
The great circle phase steering method getGPSheading() measures the difference between these two bearings and if that difference is not 180° it applies a steering correction to reduce the shortfall at a rate which is a positive function of the size of the error. This error can be seen on the applet's map. During a great circle phase, a magenta-coloured line points towards the approaching waypoint and a yellow one towards the receding waypoint. If together they do not exactly form a straight line, then the aircraft of not quite on the great circle joining the two waypoints.
The most direct way to do this computation requires knowledge of the aircraft's current position (its latitude and longitude). The topical way to get this is from a GPS receiver which uses the satellite-based Global Positioning System. However, the aircraft's position could also be acquired from an inertial platform which uses gyros and accelerometers. Perhaps one day a global positioning system may be developed which is based on natural radio sources such as quasars. If this were to prove feasible, it would certainly be cheaper, long-lasting and safe from technical malfunction and sabotage.
Once the aircraft gets within 150km of the approaching waypoint, or over half way from the previous one, whichever occurs sooner, the guidance software switches into the approach and turn phase. The geometry of this phase is discussed in detail in the section on the waypoint encounter applet. It is expedited in this class by the getStrOff() method below.
The little waypoint encounter demonstration applet does not deal with strong winds in a way acceptable to circumstances of controlled airspace. During an approach, the aircraft will be guided faithfully on a course which is tangential to the turning guide circle as it should - even under conditions of strong cross winds. However, it will get blown progressively round the circle thereby not adhering to a designated approach course. In this class, therefore, an additional steering method called getWndOff() confines the aircraft to a narrow approach corridor. Whenever the wind attempts to blow the aircraft outside this corridor, the getWndOff() method kicks in to steer it back into the corridor.
The aircraft's speed is determined by the getReqSpd() method below. This makes the speed a function of the aircraft's distance from the nearest waypoint. The reason is quite arbitrary. Although, this helps to cut down the time taken to cross long oceanic legs, it results in speeds which would be more credibly associated with a cruise missile or a flying saucer!
/**
* Waypoint Encounter Class for my Moving Map package
* @author Robert J Morton
* @version 06 January 1998 */
/* Contains data and methods to transact a navigational encounter
between an aircraft and a waypoint. Each encounter is a trans-
action with a transient lifespan which lasts from when the
aircraft enters the waypoint's range of influence until it
leaves it. */
class wpenc implements navconst { //Waypoint Encounter (Spherical Geometry version)
private static wpenc we; //reference to current waypoint encounter object
private aircraft ac; //reference to current aircraft object
private waypnt wp; //reference to current waypoint
private waypnt pw; //reference to previous waypoint
private waypnt nw; //reference to next waypoint
private double InRad; //inbound radial from previous waypoint
private double OutRad; //outbound radial to next waypoint
private double rBrg; //bearing of waypoint from aircraft
private double tBrg; //bearing of aircraft from waypoint
private double ReqHdg; //heading the aircraft must fly
private boolean approach; //true if aircraft has not yet passed over waypoint
private double pDst; //previously computed distance to waypoint
private double Dst; //currently computed distance to waypoint
private double tcr; //radius of aircraft's minimum desired turning circle
private double tcd; //diameter of turning circle - double the above
private double TCR; //square of aircraft's turning circle radius
private double RunDst; //distance to run past this waypoint before acquiring next
private boolean toFlag; //to/from flag for the waypoint encounter
private double ThDst; //threshold approach distance to decelerate to SPEED
private double RS = 100 / KphRps; //100 kph in radians per second (min speed on landing)
private double DL; //half-width of approach corridor
private double CM; //distance at which corridor rules end on approach
wpenc(waypnt w) { //SET UP FOR APPROACH WITH NEW OUTBOUND RADIAL
we = this; //reference current waypoint encounter object
ac = aircraft.getCurrent(); //get reference to aircraft object
wp = w; //get the reference of the current waypoint
waypnt.setCurrent(wp); //set the reference of the current waypoint
pw = wp.getPrev(); //reference to previous waypoint object
nw = wp.getNext(); //reference to next waypoint object
InRad = wp.getInRad(); //get inbound radial from previous waypoint
OutRad = wp.getOutRad(); //get outbound radial to next waypoint (radians)
tcr = ac.getTCR(); //get aircraft's turning circle radius
tcd = tcr + tcr; //turning circle diameter
ThDst = tcd + tcd; //threshold approach distance to decelerate to SPEED
TCR = tcr * tcr; //square of turning radius
wp.DandB(); //compute range and bearings to this waypoint
Dst = wp.getDst(); //find initial distance and bearings for new waypoint
RunDst = wp.getDST() / 2; //distance to run past waypoint before capturing next
CM = ac.getCM(); //corridor rules cease when aircraft gets this close
pDst = Dst + .1; //initialise previous distance value
approach = true; //A waypoint encounter always starts off with
toFlag = true; // the aircraft leaving the starting point
DL = wp.getDL(); //half-width of approach corridor for current waypoint
}
boolean enRoute() { //ADVANCES AIRCRAFT AND UPDATES ALL ENCOUNTER VARIABLES
if(approach || Dst < RunDst) { //if still approaching or not yet gone RunDst past waypoint
ac.advance(
getReqHdg(), getReqSpd() //advance the aircraft along its track
);
pDst = Dst; //make last time's distance this time's previous distance
wp.DandB(); Dst = wp.getDst(); //compute distance & bearings between aircraft and waypoint
return true; //return that the aircraft is still encountering waypoint
} return false; //return that the aircraft has just finished this encounter
}
private double getReqHdg() { //COMPUTE THE HEADING ON WHICH THE AIRCRAFT MUST FLY
tBrg = wp.gettBrg(); //bearing of the aircraft viewed from the waypoint
rBrg = wp.getrBrg(); //bearing of the waypoint viewed from the aircraft
if(Dst < pDst) toFlag = true; //if aircraft approaching waypoint, set the 'to' flag true
else toFlag = false; //else set the 'to' flag false (= 'from')
if(approach && !toFlag && Dst < tcr) //If aircraft is now receding but is still closer than its
approach = false; // turning radius, it is deemed to have passed the waypoint.
if(Dst < wp.getRange()) //if close enough for Euclidean approach & turn/retreat geometry
ReqHdg = getRadioHeading(); //get short-range radio heading
else //if more than 150 km from either waypoint
ReqHdg = getGPSheading(); //get trans-oceanic GPS or inertial heading
return ReqHdg = ac.RatAng(ReqHdg); //return the rationalised (0-2Pi range) required heading
}
private double getReqSpd() { //DETERMINE THE REQUIRED SPEED OF THE AIRCRAFT
double x = Dst, rs; //aircraft's distance from waypoint, required speed
if(wp.getDest() && !approach) //if aircraft has passed over the destination waypoint
x = -1.75 * x; //decelerate after passing over waypoint
if((rs = ac.getSPEED() * (1 + x / ThDst)) < RS)
rs = RS; //minimum speed
return rs; //return required speed (to ac.advance())
}
double getStrOff(double a) { //COMPUTE STEERING OFF-SET FROM WAYPOINT TO GUIDE-CIRCLE TANGENT
a = ac.BipAng(a - OutRad + Pi); //deviation from the inbound radial
boolean right = true; //indicates whether radial deviation is right or left
if(a < 0) { //If it is to the left of the inbound radial from
a = -a; right = false; // the waypoint's point of view, make it positive.
} // and use the flag to indicate its sense.
double b = tcr * Math.cos(a); //Now is a good time to refer to the Waypoint Encounter
double c = Dst - tcr * Math.sin(a); // 'Pre-Capture' diagram in AirNav6.htm
double e = b * b + c * c - TCR; //square of distance to where tangent touches circle
double s = 0; //steering off-set: angle between bearing to waypoint and tangent
if(e > 0 && c != 0) { //if aircraft has not yet reached the turning circle
s = Math.atan2(b, c) //Compute steering offset from waypoint bearing required to
- Math.atan2(tcr, Math.sqrt(e)); // keep the aircraft on a tangent to the turning circle.
if(right) s = -s; //Set steering offset negative if aircraft to right of OutRad + Pi
} return s; //return the required steering offset
}
private double getWndOff(double a) { //KEEP AIRCRAFT WITHIN WIND DRIFT CORRIDOR
double q = Dst //aircraft's approach distance from waypoint
* Math.sin(ac.BipAng(a - InRad)) //aircraft's deviation from inbound radial InRad
+ DL; //half-width of the approach confinement corridor
double s = 0; //default value of corridor re-entry steering off-set
if(Math.abs(q) > Math.abs(DL)) { //if outside the approach confinement corridor
double e = ac.getAPD(); //get the along-track side of steering triangle
if(e != 0) { //provided it is non-zero (it's from another class!)
if((q /= e) > 10) q = 10; //Impose an excursion limit on tangent of the
else if(q < -10) q = -10; // steering off-set (or corridor re-entry) angle.
s = Math.atan(q); //compute the said angle
}
}
return s; //return it as a steering offset from rBrg
}
/* The following method computes the required heading from bearing
information which is relative to the waypoint's North, not the
aircraft's north. However, the aircraft can only set its heading
relative to its own view of which direction North is in. The
returned ReqHdg will therefore be accurate only up to about 150km.
This is of course different if the waypoint itself is supplying
steering information as in the case of an ILS. */
private double getRadioHeading() { //USING WAY-POINT CENTRED BEARING INFORMATION
if(approach) { //If aircraft currently in approach phase...
double s = 0; //set default for steering off-set angle
if(Dst > CM) //if still within the corrodor approach run
s = getWndOff(tBrg); //keep aircraft within corridor boundaries
if(s == 0) //no steering command from corridor confinement
s = getStrOff(tBrg); //so invoke normal approach steering
if(s != 0) //do not disturb last time's ReqHdg value if s is zero
ReqHdg = rBrg + s; //bearing of waypoint from aircraft + steering correction
} else { //if receding, track the waypoint's outbound radial
double d = Dst * Math.sin(ac.BipAng(OutRad - tBrg));
double e = ac.getAPD(); //base of steering triangle
if(e != 0) { //better check: it comes from another class!
if((d /= e) > 10) d = 10; //Impose an excusion limit on the tangent
else if(d < -10) d = -10; // of the steering off-set angle.
ReqHdg = OutRad //Off-set the required heading by the amount
+ Math.atan(d); // required to bring aircraft back on track
} // along the outbound radial to the next waypoint.
}
return ReqHdg; //return required heading
}
/* The following method returns a Required Heading value which
is relative to the aircraft's own perception of where North
is. However, its ability to correct for wind drift gets worse
the greater the distance between waypoints. There is a better
(but more complicated) treatment which makes the corrective
angle proportional to the actual distance off course. */
private double getGPSheading() { //USES ALL BEARINGS RELATIVE TO AIRCRAFT
if(toFlag) { //if approaching current waypoint
pw.DandB(); //compute bearing of previous waypoint
ReqHdg = 2 * rBrg - pw.getrBrg() - Pi;
} else { //else if receding from current waypoint
nw.DandB(); //compute bearing of next waypoint
ReqHdg = 2 * nw.getrBrg() - rBrg - Pi; //Steer twice the reverse of the difference between
} // the bearing of the next waypoint and the extended
return ReqHdg; // bearing line from the previous waypoint.
}
boolean getToFlag() {return toFlag;} //return the approach status
static wpenc getCurrent() {return we;} //return reference to current waypoint encounter object
}
/* Robert J Morton, the author of this program,
is a poor but Right Honourable Member of the
Ancient and Noble Order of the Long-term Unemployed.
Offers of work please to: robmorton@clara.net */