The bearing calculated by TxSig() is the bearing of the aircraft from the station. In spherical geometry, the bearing of the station from the aircraft is not simply 180 degrees minus the bearing of the aircraft from the station as it would be on a flat landscape. The ADF bearing therefore has to be computed separately by DandB() with the swap flag set to TRUE.
r->Brg = DandB( t, MaxDst, TRUE );
The direction of a station being received by the ADF receiver is then presented by a radio direction needle on the compass instrument:

A VOR station is placed at an airways junction or waypoint at which a change of course is necessary. The pilot selects the radial r->ISR on which he wishes to approach the station with the intention of intercepting that radial, flying along it towards the station, and then onwards from the station along the opposite radial.
The following diagram shows the pilot to have selected radial 300 (the line on compass bearing 300 which goes slightly north of west out from the station). So he intends to fly on his present heading of approximately north-east and then turn eastwards when he intercepts the station's Radial 300.
The pilot selects the radial he requires, r->ISR, by entering it in via his keyboard or dial switches. The NAV receiver provides the aircraft's current bearing from the VOR station, t->Brg and its current distance from the station, t->Dst.
It is uneconomic for the aircraft to start approaching the radial when it is still a long way out from the station. The navigation computer therefore maintains the value of r->ReqHdg so as to keep the aircraft flying towards the station along a straight course which is tangential to one of what are called the station's 'Circles of Capture'. These are set to have a radius R equal to the diameter of the aircraft's smallest comfortable turning circle.
The navigation computer therefore computes the heading r->ReqHdg which the aircraft must fly by working out the steering offset r->StrOff from the station bearing r->Brg which will keep the aircraft aimed at the edge of the circle on the side of the selected radial closest to the aircraft. When the aircraft reaches the edge of the circle, it follows the circumference on to the selected radial, r->ISR until it reaches the station. It thus intercepts the selected radial at the station. At this point it is said to have captured the radial.
Once the aircraft has passed over the station, it must fly along outbound radial, r->OSR, the 180º projection of the inbound selected radial, r->ISR. The required heading, r->ReqHdg must be maintained to bring the aircraft smoothly back on to r->OSR whenever it drifts off. After capture, the navigation computer therefore determines r->ReqHdg in a different way as follows:
Being referenced to r->Brg, the bearing of the station from the point of view of the aircraft, the above formula takes account of spherical geometry and so works at long distance as well as short distance.
A slight problem is that when the aircraft passes over the station, it always in fact passes it a small but nonetheless finite distance to one side or the other causing r->Brg to whip suddenly through 180º. The resulting value of r->ReqHdg from the above formula therefore causes the aircraft to leave the station in what can amount to any direction. This is overcome by imposing a reasonable upper limit on the steering offset r->StrOff.
double GetReqHdg(struct TxData *t, struct RxData *r) {
#define R 40 //radius of aircraft's min turning circle
#define RR 1600 //square of this radius
double a, //used for intermediate angular quantities
b, c, e, //sides of construction triangles see diag
d = t->Dst; //distance between aircraft and waypoint
BOOL f = FALSE; //to indicate whether to use + or - root
/*If the aircraft has not so far reached the station and
it is now closer than (within) the radius of the minimum
turning circle and it is now receding from the station
then it is deemed now to have passed station. */
if(r->capture == FALSE && d < R && d > t->PrevDst)
r->capture = TRUE;
/*If the aircraft has not yet reached the station
then get its deviation from the selected radial,
determine which side of the radial it is, and
finally find b and c (see Pre-capture diagram).*/
if (r->capture == FALSE) {
r->RadDev = (a = BipAng(t->Brg - r->ISR));
if (a < 0) { a += Pi; f = TRUE; }
b = R * cos(a); c = d - R * sin(a);
*/If the aircraft is outside the minimum turning circle
compute the square root of the appropriate sign, then
compute the rationalised required heading it must fly.*/
if((e = b * b + c * c - RR) > 0) {
e = sqrt(e); if(f == TRUE) e = -e;
r->ReqHdg = RatAng
(r->Brg + (r->StrOff = atan(R/e)- atan(b/c)));
}
} //Else if the aircraft has passed the station, first
else //compute a and b (see the Post-capture diagram)....
{
r->RadDev = (a = BipAng(t->Brg - r->OSR));
a = r->Brg - a - a - Pi;
r->StrOff = (b = BipAng(r->OSR - a)); //Get steering offset
if (b > +1) a = r->OSR + 1; //and limit it to +/-
if (b < -1) a = r->OSR - 1; //one radian.
r->ReqHdg = RatAng(a);
}
return(r->ReqHdg); //return the aircraft's required heading
}
When adding and subtracting a number of angles, some of which could be compass bearings and some of which could be deviations from reference lines, we can end up with very large angular quantities which can be positive or negative. Many mathematical functions can only accept angles from 0 to 2pi or from -pi/2 to +pi/2. So we need to be able to rationalise and polarise angles.
These two tasks are done by RatAng() and BipAng() below which should appear before (or at least prototyped before) GetReqHdg():
//Put a wild angle in range 0 to 2pi
double RatAng(double theta) {
while(theta < 0) theta += TwoPi;
while(theta > TwoPi) theta -= TwoPi;
return(theta);
}
//Make a wild angle bipolar
double BipAng(double theta) {
while(theta < -Pi) theta += TwoPi;
while(theta > Pi) theta -= TwoPi;
return(theta);
}
Although not used in GetReqHdg(), later on we will also need to be able to make sure that an angle such as a corrective deviation cannot exceed an operating limit. This is done by LimAng(), which for completeness is shown now below:
//Impose an upper and lower limit on a bipolar angle
double LimAng(double theta, double limit) {
if(theta < 0)
if(theta < -limit) theta = -limit;
else
if(theta > limit) theta = limit;
return(theta);
}
Use the generic Windows program Output.C in the Windows SDK Guide To Programming. Define and declare the RxData and TxData structures Rx and Tx plus the following at the beginning of the source file:
HPEN hBlackPen, hWhitePen, hYellowPen;
HBRUSH hWhiteBrush, hBlueBrush;
int th; //text height (leading)
In MainWndProc(), modify the WM_PAINT, WM_CREATE and WM_DESTROY cases as follows:
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
GndTrk(hWnd, hDC);
EndPaint(hWnd, &ps);
break;
case WM_CREATE:
hBlackPen = GetStockObject(BLACK_PEN);
hWhitePen = CreatePen(PS_SOLID, 3, RGB(255,255,255));
hYellowPen = CreatePen(PS_SOLID, 1, RGB(200,200,0));
hWhiteBrush = GetStockObject(WHITE_BRUSH);
hBlueBrush = CreateSolidBrush(RGB(0,0,119));
break;
case WM_DESTROY:
DeleteObject(hBlackPen);
DeleteObject(hWhitePen);
DeleteObject(hYellowPen);
DeleteObject(hWhiteBrush);
DeleteObject(hBlueBrush);
PostQuitMessage(0);
return(NULL);
Then add the following at the end of the generic source file:
#define Pi 3.1415926535
#define TwoPi 6.2831853070
#define PiBy2 1.5707963267
#define DegRad 57.29577 // No of degrees in a radian
Put RatAng() and BipAng() in here followed by the digital read-outs display function:
ShowDeg(HDC hDC, int n, double a) {
extern int th;
RECT rcTextBox;
char s[8];
int i;
if (n < 12) //If not displaying distance
a *= DegRad; //then convert to degrees
i = a; if ((a -= i) > .5) i++;
sprintf(s, " %3.3d", i);
SetRect(
&rcTextBox, 120, 10 + th * n, 170, TOP + th * (n + 1)
);
DrawText(hDC, s,strlen(s), &rcTextBox,
DT_RIGHT | DT_NOPREFIX | DT_NOCLIP);
}
Finally, put in GetReqHdg()'s exerciser, GndTrk():
GndTrk(HWND hWnd, HDC hDC) {
#define X 400 //in logical pixels
#define Y 152 //in logical pixels
#define TD 140 //distance of touchdown beyond station
#define R 40 //Radius of turning circle
extern int th;
extern HPEN hBlackPen, hWhitePen, hYellowPen;
extern HBRUSH hWhiteBrush, hBlueBrush;
struct TxData *t = &Tx; //can't pass externally-declared ptrs
struct RxData *r = &Rx; //internally-declared ptrs are auto!
int SelRad, x, y; //SR in degrees, co-ords of aircraft
double a, b, isr, xx, yy, x1, y1, x2, y2;
char s[24], ToFrom;
TEXTMETRIC tm;
GetTextMetrics(hDC, &tm);
th = tm.tmExternalLeading + tm.tmHeight;
y = 10; strcpy(s,"AIRCRAFT TRACK FOR");
TextOut(hDC, 10, y, s, strlen(s));
y += th; strcpy(s,"AUTOMATIC CAPTURE ");
TextOut(hDC, 10, y, s, strlen(s));
y += th; strcpy(s,"OF WAYPOINT RADIAL");
TextOut(hDC, 10, y, s, strlen(s));
y += th;
y += th; strcpy(s,"Selected Radial" );
TextOut(hDC, 10, y, s, strlen(s));
y += th; strcpy(s,"Aircraft Bearing");
TextOut(hDC, 10, y, s, strlen(s));
y += th; strcpy(s,"Radial Deviation");
TextOut(hDC, 10, y, s, strlen(s));
y += th;
y += th; strcpy(s,"Station Bearing" );
TextOut(hDC, 10, y, s, strlen(s));
y += th; strcpy(s,"Steering Offset" );
TextOut(hDC, 10, y, s, strlen(s));
y += th; strcpy(s,"Required Heading");
TextOut(hDC, 10, y, s, strlen(s));
y += th;
y += th; strcpy(s,"Distance");
TextOut(hDC, 10, y, s, strlen(s));
for(SelRad = 0; SelRad <= 360; SelRad++) //for each degree
{ //of the compass
r->ISR = (isr = SelRad / DegRad);
r->OSR = RatAng(isr + Pi);
//FORM AND CLEAR THE GROUND TRACK DISPLAY RECTANGLE
SelectObject(hDC, hBlackPen);
SelectObject(hDC, hBlueBrush);
Rectangle(hDC, X - 220, Y - 150, X + 150, Y + 150);
//DRAW THE RED & GREEN CIRCLES
a = isr + PiBy2; x1 = R * sin(a); y1 = -R * cos(a);
a = isr - PiBy2; x2 = R * sin(a); y2 = -R * cos(a);
for (b = 0; b <= TwoPi; b += .017453292) {
xx = X + R * sin(b); yy = Y + R * cos(b);
SetPixel(hDC, x = x1 + xx, y = y1 + yy, RGB(255,0,0));
SetPixel(hDC, x = x2 + xx, y = y2 + yy, RGB(0,255,0));
}
//DRAW THE SELECTED RADIAL LINE IN YELLOW
SelectObject(hDC, hYellowPen);
x = TD * sin(isr); y = -TD * cos(isr);
MoveTo(hDC, X - x, Y - y);
LineTo(hDC, X + x / 2, Y + y / 2);
//DRAW A WHITE BLOB TO REPRESENT THE WAYPOINT
SelectObject(hDC, hBlackPen);
SelectObject(hDC, hWhiteBrush);
Ellipse(hDC, X - 5, Y - 5, X + 5, Y + 5);
//PLOT THE AIRCRAFT TRACK
xx = -210; yy = 0; //starting position of aircraft
t->PrevDst = (t->Dst = sqrt(xx * xx + yy * yy) + 1) + 1;
SelectObject(hDC, hWhitePen);
MoveTo(hDC, x = X + xx, y = Y + yy);//move to starting posn
r->capture = FALSE; //not yet reached the station
while(((a = t->Dst) < t->PrevDst && a > R) || a < TD) {
t->PrevDst = a;
t->Dst = sqrt(xx * xx + yy * yy); //initial dist from stn
r->Brg = RatAng((t->Brg = RatAng(atan2(xx, yy))) + Pi);
xx += sin(a = GetReqHdg(t, r)); //update aircraft's posn
yy += cos(a);
LineTo(hDC, x = X + xx, y = Y - yy); //plot its position
//on the screen
ShowDeg(hDC, 4, r->ISR);
ShowDeg(hDC, 5, t->Brg);
ShowDeg(hDC, 6, r->RadDev); //Radial Deviation
ShowDeg(hDC, 8, r->Brg);
ShowDeg(hDC, 9, r->StrOff);
ShowDeg(hDC, 10, r->ReqHdg);
a = t->Dst;
if (r->capture == FALSE) a = -a;
ShowDeg(hDC, 12, a);
}
for(b = 0; b < 3000; b++); //delay
}
}
I have since implemented this demonstration as a Java applet.