
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.

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.
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(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);
}
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.

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.
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.