Way Point Navigation

As well as receiving Morse signals from radio navigation stations, the aircraft's receivers also derive navigational information from them.

ADF Output

ADF receivers use special aerials comprising two coils at right angles to each other to resolve the direction a received signal is coming from. This gives the pilot the direction of the received station from the aircraft.

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:

VOR Output

VOR stations emit one signal which has the same phase in all directions and another signal whose phase angle varies as the direction of emission. For example, if you are south-east of the VOR station, the second signal will be 135 degrees out of phase with the first signal. By detecting the phase difference between the two signals it receives, the NAV receiver determines the aircraft's bearing from the station, t->Brg.

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.

Navigation Computer

The values of r->ISR and t->Brg are passed to the navigation computer which derives from these the heading the aircraft must fly, r->ReqHdg, in order for the aircraft to make a smooth turn on to the radial so that it is travelling in line with the radial as it passes over the station.

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.

The GetReqHdg() Function

The following 'C' function simulates the navigation computer's function which computes the required heading. The required heading r->ReqHdg has nothing to do with the aircraft's actual current heading.
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);
}

Exerciser for GetReqHdg()

To prove that GetReqHdg() will correctly give the heading the aircraft must follow to bring it to a smooth intercept of the selected radial, the following Windows program uses the same formulae to trace out the trajectory of the aircraft for every possible radial from 0 to 360º.

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.
This page's parent within this Web Site. About this Web Site. Its home page. Email its Author.