The Radio Environment

Details of all radio navigation stations are entered into and kept in a Ground Station Data Source file GSS.DAT. A compact ground station data file GS.DAT is then generated from GSS.DAT by the Ground Station Data compiler:

The record for each station within GS.DAT has the following structure:

struct StnData {
  char    Type;      //NDB,RRG,VOR,ILS,DME,AMK,BMK,OMK,MMK,IMK
  double  Lat;       //Latitude
  double  Lng;       //Longitude
  double  Ht;        //Ht of stn above sea level (Earth radians)
  double  Rng;       //Effective Range
  double  Hdg;       //Runway or airway heading
  int     RadPat;    //0=omni,1=RRG,2=Marker lobe,3=ILS lobe
  long    Frq;       //Frequency
  char CallSign[16]; //call sign coding string
  time_t  TimeOut;   //Time before next dist & bearing check
  time_t  OldTime;   //Time when dist & bearing last checked
  BOOL Receivable;   //Flag saying if station is in range
} Stn;
The first 9 items are constants for a given station, the last 3 vary with aircraft position. GS.DAT is generated from a ground station data source file GSS.DAT into which details of each ground station are typed initially.

Active Stations List

For a station to be active during simulation, its details have to be in memory. So we need to copy the details of any station which is in (or about to come into) range of the aircraft into a data structure in memory. Conversely, to conserve memory, we must delete stations from memory when they go more than a certain amount out of range of the aircraft.

So that we can refer to an active station's data easily, we make the active stations list comprise an array of pointers, each element of which contains a pointer which points to a station's data structure as follows:

Station data is kept in a structure of type TxData. Tx is the name of the array so the pointer-constant, Tx points to the first element of the array. NumTx is the maximum number of elements in the array, ie the maximum number of stations we can accommodate concurrently. TX points to the last element + 1 of the array. TxHi points to the highest currently occupied element. tx points to the element containing the pointer to the data pertaining to the station which the a program is currently dealing with.

The TxData Structure

The data structure for each currently-active station has the following contents.
struct TxData {        //DATA STRUCTURE FOR AN ACTIVE GROUND STN
  int StnNum;          //Stn's record No within GS.DAT file
  int StnType;         //see below
  double Lat;          //Latitude
  double Lng;          //Longitude
  double Ht;           //Ht of stn > sea level in Earth radians
  double Rng;          //Effective Range
  double Hdg;          //Runway or airway heading
  double Len;          //Runway length (for ILS only)
  double GS;           //Glide Slope Angle (for ILS only)
  int  RadPat;         //0=omni,1=RRG,2=Marker lobe,3=ILS lobe
  long Frq;            //Frequency
  char CallSign[16];   //call sign coding string
  BOOL Single;         //morse version of callsign in Morse[]

  double Dst, PrevDst; //Current & previous dist from aircraft
  double Brg;          //Current true bearing from aircraft
  double Sig;          //Signal strength of tx at aircraft
  char *pCallSign;     //ptr to current posn in callsign string
  char Morse[48];      //Morse sequence in timer durations
  char *pMorse;        //points to current Morse element
  int Repeat;          //Nš of morse seq repeats still to do
  int Pitch;           //audio freq of morse tone (see below)
  int MorseTimer;      //duration of current Morse Code element
  BOOL MorseKey;       //current state of Morse Key 
}
*Tx[ NumTx ],          //ptrs to NumTx active stn structures
*(*TxHi);              //ptr to Tx[highest occupied element+1]

StnType   1 = NDB, 2 = RRG, 3 = VOR, 4 = VOR/DME, 
          5 = ILS, 6 = ILS/DME, 7 = DME, 8 = AMK, 
          9 = BMK, 10 = OMK, 11 = MMK, 12 = IMK

Pitch     0 = 1020Hz, 1 = 1300Hz, 2 = 3000Hz
The items above the blank line are fixed items copied from the GS.DAT file. The items below the blank line are updated by the real-time NAV software.

SetUpTxData()

When a station comes within receivable range, this function copies its details into a newly-allocated TxData structure. Apart from simply copying the fixed data items from the appropriate GS.DAT disk record, SetUpTxData() also possibly sets up the station's callsign for keying. If a station has a simple callsign sequence (ie only one semi-colon appears in it - see later in the section on callsign keying), SetUpTxData() calls SetUpCallSign() which translates the callsign sequence into Morse code once for the whole time the station is active. If the station has a multiple callsign (ie more than one semi-colon appears in it) then each segment of the callsign is set up dynamically during the on-going callsign keying cycle.
SetUpTxData(struct TxData *t) {
  char *u = Stn.CallSign, //ptr to callsign data in disk stream
  *v = t->CallSign;       //ptr to callsign in TxData structure
  t->Type = Stn.Type;     //Type of Station
  t->Lat = Stn.Lat;       //Its Latitude
  t->Lng = Stn.Lng;       //Its Longitude
  t->Ht = Stn.Ht;         //Its height > sea level (Earth rads)
  t->Rng = Stn.Rng - RNG; //True transmission range
  t->Hdg = Stn.Hdg;       //runway or airway heading
  t->RadPat = Stn.RadPat; //antenna radiation pattern or gain
  t->Frq = Stn.Frq;       //Frequency
  if(*u++ = 'S')          //S = single, M = multiple callsign
    t->Single = TRUE;     //single callsign
  else t->Single = FALSE; //multiple callsign
  for(;*u > '\0';u++,v++) //copy all but S/M byte of callsign in
    *v = *u;              //to TxData callsign buffer CallSign[]
  *v = '\0';              //and terminate it with a null char
  t->pCallSign = Stn.CallSign; //set ptr to start of callsign
  t->MorseKey = TRUE;     //for first Morse element
  t->pMorse = t->Morse;   //set ptr to start of Morse Code
  if(t->Single == TRUE)   //if it is a single-part callsign
    SetUpCallSign(t);     //translate it to Morse once-only here
}
Stations are added to and deleted from the active stations list by the function:
BOOL SetStn(int StnNum,   //Stn's Record Nš in GS.DAT file
            BOOL flag)    //TRUE if new stn just come in range
{
  #define RNG 0.0157079632675  //100km in Great Circle radians
  BOOL result = FALSE;
  static struct TxData 
    *(*TX) = Tx + NumTx;  //ptr to last element in ptr array
    *t,                   //ptr to subject Tx Data structure
    **tx;                 // ptr to ptr to Tx Data structure

  if (flag == TRUE)       //IF A STN JUST COME INTO RANGE
  {
    for(tx = Tx;tx < TX;tx++) //find spare slot in ptr array
    {                         //then allocate memory
      if(*tx == NULL) {
  *tx = (struct TxData *)malloc(size_t sizeof(struct TxData));
        if(*tx != NULL)
          result = TRUE;
        break;        }
    }
    if(result == TRUE) {  //if enough memory could be allocated
      if(TxHi < tx)       //keep track of the 
        TxHi = tx;        //highest used element
      SetUpTxData(*tx);}  //copy stn data to TxData structure
  }
  else                    //IF A STN JUST GONE OUT OF RANGE
  {
    for (tx = Tx;tx < TX;tx++) //find its Tx Data
    {  
      if((*tx)->StnNum == StnNum) {
        free(*tx);        //free the memory used by this stn
        *tx = NULL;       //clear ptr in TxData ptr array
        if(TxHi == tx)    //keep track of the
          TxHi--;         //highest used element
        result = TRUE;
        break;                    }
    }
  }
  return(result);
}

Rough Distance Check

When the simulation program is first started up, part of the initialisation process must be to calculate accurately and fully the distances of all ground stations relative to the aircraft's starting position. This must also be done whenever the aircraft is artificially repositioned by the flight simulation instructor.

From then on, however, the distance of each station from the aircraft must be recalculated regularly to see if an out-of-range station has moved into range or an in-range station has moved out of range. Calculation of distance by spherical geometry is time-consuming. So we must find a method of checking stations which resorts to recalculating their distances fully only when it absolutely has to.

If the aircraft has a maximum speed VMAX and the last calculated distance to a given station is d and the effective transmitting range of the station is Stn.Rng, then the aircraft cannot possibly get into range of the station within a time less than (d - Stn.Rng)/VMAX no matter what direction the aircraft is travelling in. This formula provides a quick means of checking when we need to re-compute the station's true distance from the aircraft. This principle may be visualised as follows. Whenever a station's true distance is recalculated, the station emits a ghost which travels towards the aircraft at a constant speed VMAX irrespective of the aircraft's own speed or heading:

It is not necessary to re-calculate the station's distance until a time Stn.TimeOut has elapsed. The ghost behaves a bit like a photon which will move towards an observer at the speed of light 'c' irrespective of the relative speed of the Earth and the observed star which emitted it. We therefore cycle through the GS.DAT file continually decrementing each station's Stn.TimeOut each pass by the amount of time that has elapsed since the previous time we checked that station.

Great-Circle Distance

When a station's Stn.TimeOut expires we call the function GetDst() to compute its true accurate great-circle distance as follows:

double GetDst() {               //COMPUTE THE DISTANCE OF AN
  #define Pi 3.1415926535       //OBSERVED FROM THE OBSERVER
  double 
    dlong = Stn.Lng - Air.Lng,  //Longitudinal difference
    SinAirLat = sin(Air.Lat),   //Sine of latitude of observer
    CosAirLat = cos(Air.Lat),   //Cosine latitude of observer
    SinStnLat = sin(Stn.Lat),   //Sine of latitude of observed
    CosStnLat = cos(Stn.Lat);   //Cosine latitude of observed
  if(dlong >  Pi) dlong -= Pi;  //change the range of dlong 
  if(dlong < -Pi) dlong += Pi;  //from 0 - 2p to -p - +p 
  CosDst = CosAirLat * CosStnLat * cos(dlong)
         + SinAirLat * SinStnLat;
  if(CosDst > +1) CosDst = +1;  //avoid acos() domain errors
  if(CosDst < -1) CosDst = -1;
  return(acos(CosDst));         //return distance in radians
}
If as a result, the station is now found to be in range of the aircraft, its details are copied into a TxData structure if it is not already in one. If as a result, the station is now out of range of the aircraft, its TxData structure is deleted if it exists.

Distance-Checking Cycle

The following 'C' function determines whether or not a ground station is currently receivable at the aircraft. It is called about every 200 milliseconds.
StnPoll() {
  #define VMAX 0.4363323129861111E-4 //1000km/hr in radians/sec
  static int StnNum = 0; //Station No preserved from prev pass
  double d;              //distance of station from aircraft
  if (StnNum == NumStn)  //Station's record number in the
    StnNum = 0;          //Ground Station Data file GS.DAT
  GetStn(++StnNum);      //get data of next stn in GS.DAT file
  if (Stn.TimeOut > 0)   //if not yet time to recalc distance
  {
    /*Decrement the station's distance re-calculation timeout,
    and advance the station's ghost towards the aircraft*/
    Stn.TimeOut -= SysTime - Stn.OldTime; Stn.OldTime = SysTime;
  }
  else  //Else station's ghost now in range of aircraft, so...
  {
    /*If aircraft is within station's signal range, and station 
    is not currently on the 'receivable stations' list then..*/

    if(((d = GetDst()) < Stn.Rng) && (Stn.Receivable == FALSE))
    {
      /*Attempt to put the station on the 'receivable' list and
      if successful store its updated status to the GS.DAT file*/

      if((Stn.Receivable = SetStn(StnNum, TRUE)) == TRUE)
        PutStn(StnNum);
    }
    else  //Else the station is out of receivable range, so...
    {
      /*If station currently on 'receivable' list, delete it
      from 'active stations' list and set it as unreceivable*/

      if (Stn.Receivable == TRUE) {
        SetStn(StnNum, FALSE);
        Stn.Receivable = FALSE;
      } 
    /*Set the timeout to next distance check and then
    store the updated station data to the GS.DAT file */

    Stn.TimeOut = (d - Stn.Rng) / VMAX; PutStn(StnNum);
    }
  }
}
Stn.Rng as given in GS.DAT is actually 100km more than the maximum range at which the station be received. This gives StationPolling() time to get the station on to the active list since an aircraft doing 1000km/hr can get 100km closer to a station in less time it takes to check 2,000 stations at 200ms per station.
This page's parent within this Web Site. About this Web Site. Its home page. Email its Author.