/* * Program: Global Navigator II * Programmer: Robert John Morton UK-YE572246C * Programming Language: 'C' * Date: Thu 30 Dec 2019 - Belo Horizonte-MG, Brazil This program is exclusively the inetllectual property of the programmer. Project: Date Started: Thu 30 Dec 2019 Date Finished: Wed 28 Feb 2020 - 2 months 8-tab conversion 15 Aug 2020 to 28 Aug 2020 - 2 weeks Latest modification 08 July 2021 Tabs 4 & 5 implemented 05 to 14 April 2022 This is a monolithic program, which operates through an Xwindows GUI. Code for the 'estoninho' attack avoidance algorithm is not included with this version. From the directory in which 'nav.c' resides: location of the X-windows X11 graphics library | name of the executable output file | | to compile: gcc nav.c -L/usr/X11R6/lib -o nav -lX11 -lm | | link to X-windows X11 graphics objects library | link to mathematics objects library for sin, cos etc. to run: ./nav [runs program as a self-contained unit] ./nav -o [runs program sending flight commands to stdout] ./nav -io [also accepting input of real-world lat/lng from stdin] The file routes.txt and its corresponding waypoints data files routes00.txt must be present in the same directory as the 'nav' executable. NOTE: For real-world navigation, this program must be run embedded within a dedicated computer under a minimal operating system. On a normal operating system, processing can be interrupted in a way that can cause any iteration of the program to remain unfinished. This can cause output commands to be missing from the output stream and the map or data display to blank out momentarily. Discourse on this program at: https://robmorton.website/navigation/nav.html */ #include // contains the X11 grapical drawing functions #include // input output to/from the console and keyboard #include // standard functions for the 'C' language #include // for initialising strings as character arrays #include // contains the function usleep() #include // sin() cos() asin() atan() atan2() etc #include // to measure elapsed time since last pass #define WHITE 0xFFFFFF // colour white for 'selected item' lettering #define GREY 0x999999 // colour grey for 'unselected item' lettering #define BLEY 0xBBCCDD // colour blue-grey for 'instructions' lettering #define DARK 0x333333 // colour for menu item background #define BLACK 0x000000 // colour black for clearing graph areas #define YELLOW 0xFFFF00 // colour yellow for menu titles and GPS guide bars #define YELL 0xAAAA00 // duller yellow for scroll bars #define GREEN 0x00FF00 // colour green for graphical traces and messages #define RED 0xFF0000 // colour red for graphical traces and messages #define LM 17 // left margin of application #define DM 110 // LM + 105 left margin of waypoint and aircraft data display #define BW 59 // button width + inter-button space [pixels] #define BY 362 // vertical datum of the tops of the row of control buttons #define BH 21 // button height [pixels] #define TL 63 // base height for the top line of the text within a Tab #define IL 18 // inter-line drop for text #define pi 3.14159265358979323846264338327950288 // circular constant π #define TwoPi 6.28318530717958647692528676655900576 // 2π [360 degrees] #define HalfPi 1.57079632679489661923132169163975144 // π/2 [90 degs] // The 'PolarLimit' is 1 second of arc less than a right-angle. #define PolarLimit 1.57050543858623089763516774317834 #define MPR 6366197.723857773 // metres per mean Great Circle radian [Erad] #define DPR 57.2957795141996 // number of degrees per radian #define RPD 0.01745329252 // number of radians per degree #define SPR 206264.806247 // seconds of arc per radian 648000/π #define WPR 100000 // max range of waypoint influence [metres] #define StlHgt 100 // stealth height above terrain [in metres]. #define MaxSpd 270 // metres/second; 527 knots 1806 kph 1122 mph #define TopLim 53 // top map limit for displaying radials at turns #define BotLim 348 // bottom map limit for displaying radials at turns #define RgtLim 480 // max right where permitted to write on map #define LftLim 184 // max left where permitted to write on map #define LndDst 20000 // allow a 20 km approach and landing run #define MWP 50 // maximum possible number of waypoints per route #define TrnRad 10000 /* radius of the aircraft's ideal turning circle of 10 kilometres [expressed in metres] */ #define MinSpd 80 /* Aircraft's take-off and landing speed: 80 metres/second = 288 kilometres per hour = 179 miles per hour = 155.51 knots. [1 m/s = 3.6 kph = 2.23694 mph = 1.94384 kts. */ /* One second of arc represents a Great Circle distance of 30.864197531 metres. A 32-bit integer can express a bipolar longitude to a precision of 0.009313226 metre [about 1cm]. The old 24-bit integer could express a bipolar longitude to a precision of 2.384185791 metres. */ Display *XD; // pointer to an X11 display device Window XW; // reference for the window created for this program GC XG; // X11 graphics context within which to write and draw FILE *FI, *FO; // handles for input.txt & output.txt int ICS[6] = { // SELECTABLE ITERATION COUNTS FOR MAP UPDATES 49, // for update every 5 seconds 50 19, // for update every 2 seconds 20 9, // for update every second 10 4, // for update every 500 milliseconds 5 1, // for update every 200 milliseconds 2 0 // for update every 100 milliseconds 1 }, IC = 0, // Iteration Counter IP = 4, // iteration period index number [default: 200 milliseconds] JC = 0, // Fixed 1-second iteration counter // MANAGEMENT OF GEOGRAPHIC FEATURES AND HEIGHT PLOTS Hidx[100], // index numbers of geographic features for Hoare Sort Hdst[100], // distances of geographic features for Hoare Sort gf[16], // refs to current waypoint's associated geographic features T4P[436], // to hold 436 plots [extent of x-axis in pixels] // TAB AND CONTROL BUTTON MANAGEMENT TB = 0, // number of the currently displayed Tab [0 to 7] T3BS, // Tab 0 control button states T0BS, // Tab 1 control button states T6BS = 8, // Tab 4 control button states [default = SIMUlated input] T7BS, // Tab 5 control button states T4BS = 2, // Tab 6 control button state [default AUTO is bright] T4PC = 0, // Tab 6's plot counter // LOGIC FLAGS [This lot could be done as single-bit switches] InFlg = 0, // 1:input of Lat Lng from 'stdin' enabled; 0:disabled StdOut = 0, // 1; output of flight commands to 'stdout' enabled FileOut = 0, // 1; output of flight commands to 'output.txt' enabled Flag = 0, // 0:suppress display of a map item; 1:permit display ToFlag = 0, // TO/FROM flag for the waypoint encounter Landing = 0, // 1:when aircraft is in final landing phase RIGHT = 0, // 1: right turn; 0: left turn at current waypoint ORD = 0, // 1: first in route; 2: last in route; 0: mid route RunState = 0, // 0: STOP, 1: GO Ready = 0, // 0: LOADING, 1: LOADED [READY] Gauto = 1, // 1:geoid selection on automatic; 0:manual Wind = 0, // 0:wind off; 1:wind on GFL = 0, // 'all geographic features loaded' flag: 1:all loaded // ROUTES AND WAYPOINTS Route = 1, // List Number of selected route TNR = 0, // current total number of routes TGF = 0, // total number of features in current route cgf = 0, // index number of current geographic feature rgf = 0, // number of associated geographic features for current waypnt TWP = 0, // total number of waypoints loaded LWP = 0, // index number of the last waypoint in the route pwp = 0, // index number of previous waypoint cwp = 0, // index number of current waypoint nwp = 0, // index number of next waypoint // MOUSE AND GRAPHICS mX = 0, // x-coord of mouse click mY = 0, // y-coord of mouse click MW = 0, // mouse wheel variable: 1:scroll up, 2:scroll down MB = 0, // vertical bias for routes list in Tab 1 LB = 0, // vertical bias for waypoints list in Tab 2 NL = 0, // line number of input commands LN = 0, // line number of output commands // MISCELLANEOUS i = 0, j = 0, // universal loop variables VinErr = 0, // Vincenty functions: error code. MapSize = 3, // 0:500 1:400 2:300 3:200 4:100 5:50 [km square] Geoid = 0, // List Number of the selected geoidic ellipsoid CM = 0, // index number of current message MC = 0; // colour of current message double MS[6] = { // FOR TIMING & ELAPSED TIME COMPUTATIONS 0.0006, // 500 x 500 Kilometres per metre (3/5)/1000 0.00075, // 400 x 400 Kilometres per metre (3/4)/1000 0.001, // 300 x 300 Kilometres per metre 1/1000 0.0015, // 200 x 200 Kilometres per metre (3/2)/1000 0.003, // 100 x 100 Kilometres per metre 3/1000 0.006 // 50 x 50 Kilometres per metre 6/1000 }, ms = 0, // variable to hold the selected one of the above ETS[6] = { // SELECTABLE UPDATE PERIODS [IN SECONDS]. Map updated every: 5.00, // 5 seconds 2.00, // 2 seconds 1.00, // 1 second 0.5, // half second 0.2, // 200 milliseconds 0.1 // 100 milliseconds }, ET = 0, // elapsed time [seconds] since last pass of updtTrack() RH[100][2], // for up to 100 origin & destination runway headings // VINCENTY FORWARD AND INVERSE DISTANCE AND BEARING METHODS FwdAzi = 0.0, // forward azimuth: bearing of waypnt as viewed from aircraft BakAzi = 0.0, // back-azimuth: bearing of aircraft as viewed from waypoint VinDst = 0.0, // ellipsoidal distance observer to observed [metres] VinLat = 0.0, // returned latitude of the observed [radians] VinLng = 0.0, // returned longitude of the observed [radians] EquRad = 0.0, // equitorial radius of the Earth in metres FlatF = 0.0, // flattening factor [depends on which geoid is selected] EquPol = 0.0, // equitorial radius x EquPol = polar radius PolDst = 0.0, // maximum permitted distance short of antipodal FwdBrg = 0.0, // bearing of current waypoint from aircraft [radians] BakBrg = 0.0, // bearing of aircraft from current waypoint [radians] WptDst = 0.0, // distance between aircraft and current waypoint [in metres] // AIRCRAFT'S REQUIRED POSITION AND ATTITUDE InRad = 0.0, // current waypoint's inbound radial [radians] OutRad = 0.0, // current waypoint's outbound radial [radians] PreDst = 0.0, // previous distance from current waypoint ReqHdg = 0.0, // aircraft's required heading [in radians] ReqSpd = 0.0, // aircraft's required air speed [metres per second] // AIRCRAFT'S COMMANDED POSITION AND ATTITUDE CmdHdg = 0.0, // heading command to be sent to autopilot CmdHgt = 0.0, // height at which the aircraft is commanded to fly CmdSpd = 0.0, // commanded air speed in metres per second CmdROT = 0.0, // aircraft's required change in heading this iteration CmdROC = 0.0, // aircraft's commanded rate of climb CmdAtt = 0.0, // Pitch Command CmdBnk = 0.0, // Roll Command // AIRCRAFT'S ACTUAL POSITION AND ATTITUDE AirLat = 0.0, // aircraft's latitude AirLng = 0.0, // aircraft's longitude AirHdg = 0.0, // aircraft's actual heading AirHgt = 0.0, // aircraft height [metres] AirAtt = 0.0, // Actual Pitch AirBnk = 0.0, // Actual Roll // RELATIONSHIP BETWEEN AIRCRAFT AND PREVIOUS, CURRENT & NEXT WAYPOINTS MapFwd = 0.0, // bearing of next or prev waypoint from aircraft cwpLat = 0.0, // latitude of current waypoint cwpLng = 0.0, // longitude of current waypoint pwpHgt = 0.0, // height of previous waypoint cwpHgt = 0.0, // height of current waypoint nwpHgt = 0.0, // height of next waypoint PFD = 0.0, // full distance between current & previous waypoint PHD = 0.0, // half-distance between current & previous waypoint NFD = 0.0, // full distance between current & previous waypoint NHD = 0.0, // half-distance between current and next wayppoint HHDP = 0.0, // half-height-diff between current & previous waypoints HHDN = 0.0, // half-height-difference between current & next waypoints DesDst = 0.0, // dist from touchdown at which to begin glide slope descent CtrDst = 0.0, // distance of centre of turning circle from waypoint CtrBrg = 0.0, // bearing of centre of turning circle from waypoint TanDst = 0.0, // dist from waypnt at which InRad/OutRad touch turning circle CtrLat = 0.0, // latitude of centre of turning circle CtrLng = 0.0, // longitude of centre of turning circle HgtH1 = 0.0, // height [metres above sea level] of receding waypoint HgtH2 = 0.0, // height [metres above sea level] of approaching waypoint Hgt_D = 0.0, // distance [km] between receding & approaching waypoints HgtBD = 0.0, // distance from touchdown at which to begin descent // WIND WndBrg = 0.872664626, // default wind blowing TO not FROM brg 50 degrees WndSpd = 5.0, // default wind speed 5 metres/second [9.72 knots] ReqOff = 0.0; // heading offset required to compensate for wind /* NAMES AND OTHER DATA FOR THE CURRENTLY AVAILABLE ROUTES Runway headings are needed only for the first and last waypoints in a route; namely, the origin and destination airfields. These are therefore stored [in radians clockwise from true North] in a 2-dimentional array RH[][], here together with the route names. */ char RN[100][80], // up to 100 route names of up to 80 chars each UO[16][80], // to hold screen full of intput commands OU[16][80]; // to hold screen full of output commands /* The following structure contains the basic specification of a Geographic Feature [GF]. A geographic feature is simply some recognisable feature in the navigational landscape. It could be a city, mountain, estuary and so on. The data file for an air route contains details of many geographic features that are situated on or close to the route. */ #define MGF 100 // maximum possible number of geographic features per route struct feature { // to contain information for a Geographic Feature: char Name[21]; // name [up to 20 characters only allowed] int wpi, // index number of corresponding waypoint data Hgt; // waypoint's elevation above sea level in metres double Lat, // waypoint's latitude in radians Lng; // waypoint's longitude in radians } GF[MGF]; // array of up to MGF geographic feature data strutures /* Some of the geographic features contained in a route's data file are de- signated as waypoints. These are the points on the ground [usually loc- ations of navigational aids] through which the route passes, and hence, over which the aircraft flies. The waypoints are, conceptually, joined together by the legs of the route. The following structure contains the extra information that needs to be known about a geographic feature used as a waypoint. */ struct waypoint { int pgf, // GF index number of previous waypoint cgf, // GF index number of current waypoint ngf, // GF index number of next waypoint ORD, // 1: first in route; 2: last in route; 0: mid route RIGHT, // 1: right turn at current waypoint; 0: left turn rgf, // number of associated geographic features gf[16]; // references to up to 7 associated geographic features double // BEARINGS IN RADIANS, DISTANCES IN METRES: InRad, // bearing from this waypoint to previous waypoint OutRad, // bearing from this waypoint to next waypoint PFD, // full distance between this and previous waypoint PHD, // half-distance between this and previous waypoint NFD, // full distance between this and next waypoint NHD, // half-distance between this and next waypoint CtrDst, // Distance of centre of turning circle from waypoint CtrBrg, // Bearing of centre of turning circle from waypoint TanDst, // Dist from wpt at which inRad/OutRad touch turning circle CtrLat, // latitude of centre of turning circle CtrLng; // longitude of centre of turning circle } WP[MWP]; // array of up to MWP waypoint data strutures void helpPage(char *S) { // DISPLAY THE HELP PAGE IN THE DEFAULT WEB BROWSER int i, s = strlen(S); // length of 'nav.html' file path char C[64] = "xdg-open https://robmorton.website/"; // 35 characters for(i = 0; i < s; i++) // add the file path for 'nav.html' C[i+35] = S[i]; // onto the end of the base URL C[i+35] = ' '; // space after the end of the command C[i+36] = '&'; // place command in 'background' mode C[i+37] = 0; // string-terminating NULL character printf("%s\n",C); // verification print in terminal window system(C); // issue the command to the system } void showMsg(int m, int colour) { // DISPLAY ADVICE AND ERROR MESSAGES CM = m; MC = colour; // preserve current message number if(TB != 0) return; // messages only displayed in Tab 0 char *MSG[] = { "", "01 GDP observer's lat too close to north pole.", "02 GDP observer's lat too close to south pole.", "03 GDP observer's longitude out of range.", "04 GDP observed is at observer's antipode.", "05 GDP Didn't converge within 100 iterations.", "06 GIP aircraft's latitude > 89°59'00N.", "07 GIP aircraft's latitude > 89°59'00S.", "08 GIP observed's latitude > 89°59'00N.", "09 GIP observed is at observer's antipode.", "10 GIP Reference longitude > 180° E or W.", "11 GIP Observed's longitude > 180° E or W.", "12 GIP Didn't converge within 100 iterations.", "13 GIP Observer and observed are antipodal.", "", "15 The aircraft has reached its destination.", "16 Loading route names...", "17 Loading waypoints...", "18 Flight ready to depart.", "19 Flight in progress.", "20 Flight paused.", "21 Can't open route names file 'routes.txt'.", "22 Can't open waypoints data file for this route." }; XSetForeground(XD,XG,DARK); // clear the message area XFillRectangle(XD,XW,XG,LM+48,331,418,BH); XSetForeground(XD,XG,colour); XDrawString(XD,XW,XG,LM+54,345,MSG[m],strlen(MSG[m])); } // ---------------------GENERAL ANCILLARY FUNCTIONS--------------------------- void clearTabArea() { // CLEAR TAB AREA XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,LM,38,484,383); } // CLEAR MAP AREA. Called from 1 place each in T3show(), T3updt() & T4updt() void clearMapArea() { XSetForeground(XD,XG,0x182448); // map background colour dark blue XFillRectangle(XD,XW,XG,182,50,301,301); } // MAKE ANGLE LIE BETWEEN 0 AND 2π [Called generally throughout the program] double RatAng(double a) { while(a >= TwoPi) a -= TwoPi; while(a < 0) a += TwoPi; return a; } // MAKE ANGLE LIE BETWEEN -π AND +π [Called generally throughout the program] double BipAng(double a) { while(a > pi) a -= TwoPi; while(a <= -pi) a += TwoPi; return a; } /* Formats the latitude or longitude of the current Geographic Feature, supplied as an integral number of seconds of arc, within a 10-character array ready for display by the T3updt() function. */ char *RadToDMS(double d, int flag) { // convert radians to DEG:MIN:SEC format int x = rint(d * SPR); // convert radians to integral seconds static char C[11] = "000:00:00N"; // template for display string if(flag == 0) // If formatting a latitude if(x < 0) { C[9] = 'S'; x = -x; } else C[9] = 'N'; else // else formatting a longitude if(x < 0) { C[9] = 'W'; x = -x; } else C[9] = 'E'; int y = x / 60, // integral minutes z = x % 60; // remaining seconds C[7] = (char)(z / 10 + 48); // integral 10s of seconds C[8] = (char)(z % 10 + 48); // integral remaining seconds x = y / 60; // integral degrees z = y % 60; // remaining minutes C[4] = (char)(z / 10 + 48); // integral 10s of minutes C[5] = (char)(z % 10 + 48); // integral remaining minutes y = x / 10; // 10s of degrees C[2] = (char)(x % 10 + 48); // integral remaining degrees C[1] = (char)(y % 10 + 48); // integral remaining 10s of degrees if(flag == 0) // if a latitude, C[0] = ' '; // prefix it with a space else // else it's a longitude, so show the C[0] = (char)(y / 10 + 48); // integral remaining 100s of degrees return &C[0]; // address of the output string } /* CONVERT AN 'int x' TO AN l-DIGIT NUMERIC STRING WITH LEADING ZEROS IN ARRAYS[]: Called from 1 place each in editSI() and openDoc() and from 2 places each in editDT() and ixtotxt(). */ char *showInt(int x, int l) { static char S[33]; // to hold the string version of the number if(l > 11) // limit length to 11 characters l = 11; // as a safety precaution for(int i = 0; i < l; i++) S[i] = '0'; // fill the whole field with zeros S[l] = (char)0; // terminating NULL character if(x > 0) { // if the number is non-zero: int i = l; // total of y possible digits in the number while(x > 0 && // while there are more digita to process i > 0) { // work backwards from [8] to [0] S[--i] = (char) // Store remainder after dividing the number (x % 10 + 48); // by 10 as a numeric character in the array. x /= 10; // divide the number by 10 [unrounded] } // and loop back } // containing the formatted number. return S; // Address of start of char array number } /* Places the HEIGHT of the current Geographic Feature, supplied as an integer, right-justified with leading spaces, within a 10-character array ready for display by the T3updt() function. */ char *showHeight(int x) { static char // use the same character array for each call C[11] = " 0+"; // template for display format if(x < 0) { // if the number is negative C[9] = '-'; // install the minus-sign character x = -x; // and make the number positive } else // otherwise C[9] = '+'; // install the plus-sign character for(i = 0; i < 9; i++) // set all 9 characters C[i] = ' '; // as spaces initially if(x == 0) // if the number is simply zero C[8] = '0'; // just set final numeric charcter as a zero else { // else, if the number is non-zero: i = 9; // total of 9 possible digits in the number while(x > 0 && // while there are more digita to process i > 0) { // work backwards from [8] to [0] C[--i] = (char) // Store what remains after dividing the number by (x % 10 + 48); // 10 as a numeric character in the array. x /= 10; // divide the number by 10 [unrounded] } // and loop back } return &C[0]; // return the address of the output string } /* Places the BEARING or RADIAL of the current Geographic Feature, supplied as a double in radians, right-justified with leading spaces, within a 3- character array ready for display by the T3updt() function. The supplied angle is usually bipolar, so it is first rationalised into the range 0-360 degrees. */ char *showRat(double d) { int x = rint(RatAng(d) * DPR); // convert to rounded integral degrees static char C[4] = "000"; // 3-digit display string template for(i = 0; i < 3; i++) // make all 3 digits C[i] = '0'; // into '0' characters i = 3; // possibe maximum of 3 characters to deal with int y = 0; // assume 3 characters to start with if(x < 100) y = 1; // if number less than 100, 1 less char to deal with if(x < 10) y = 2; // if number less than 10, 2 less chars to deal with while(i > y) { // while there's yet [another] character to deal with C[--i] = (char) // Write to appropriate position in the 3-char array (x % 10 + 48); // the remainder augmented into an ASCII character. x /= 10; // divide the number by 10 [truncated] } return &C[0]; // return the address of the output string [char array] } /* Convert a number, supplied in radians, into a string that shows a signed angle of from 0 to 99 degrees to 3 decimal places. This function is used only for converting the aircraft's commanded rate of turn [CmdROT]. */ char *showBip(double d) { // Convert the supplied radians to rounded int x = rint(1000 * d * DPR); // integral thousandths of a degree. static char C[8] = "00.000+"; // decimal degrees data template for(i = 0; i < 6; i++) // Put a '0' character in all but the final C[i] = '0'; // position of the character array. C[2] = '.'; // put the decimal point in position '2' if(x < 0) { // if the number is negative C[6] = '-'; // put a minus sign in the final position x = -x; // and make the number positive } else // otherwise C[6] = '+'; // just put a plus sign in the final position i = 6; // maximum of 6 possible characters to deal with int y = 0; // at first assume that all 6 must be dealt with if(x < 10000) // if less than 10 degs, y = 1; // 1 less digit to deal with if(x < 1000) // if less than 1 deg, y = 2; // 2 less digits to deal with if(x < 100) // if less than .1 deg, y = 3; // 3 less digits to deal with if(x < 10) // if less than .01 deg, y = 4; // 4 less digits to deal with while(i > y) { // while there's yet another character to deal with if(--i == 2) // skip over the decimal --i; // point's position C[i] = (char) // Write to appropriate position in 7-char array (x % 10 + 48); // the remainder augmented into an ASCII character. x /= 10; // divide the number by 10 [truncated] } return &C[0]; // return the address of the output string [char array] } /* Places the name of a Geographic Feature right-justified within a 20-character array ready for display by the T3updt() function. */ char *showName(char *s) { static char C[21] = " "; // re-usable char. array for(i = 0; i < 20; i++) // clear character array C[i] = ' '; // to spaces int x = 20 - strlen(s); // starting character for waypoint name for(i = 0; i < 20; i++) // for each character of the name C[i + x] = *(s + i); // put it in the C[] array right justified return &C[0]; // return the address of the output string [char array] } //------------------VINCENTY METHODS FOR DISTANCE & BEARING------------------- /* Solution to the Geodetic DIRECT Problem by T. Vincenty: a modified Rains- ford's Method with Helmert's elliptical terms. Effective in any azimuth and at any distance short of antipodal. Latitudes and longitudes in radians positive north and east. Azimuths in radians clockwise from north. Distance in metres. Transcoded by the author from a FORTRAN version by Lcdr L Pfeifer NGS Rockville MD 20FEB75. Error Code values: 0 no error 1 observer's latitude too close to north pole 2 observer's latitude too close to south pole 3 observer's longitude out of range 4 suspect that observed is at observer's antipode 5 Vincenty iterations excessive [equations didn't converge] */ int VSGDP( // VINCENTY SOLUTION TO THE GEODETIC DIRECT PROBLEM double RefLat, // latitude of observer double RefLng, // longitude of observer double Brg, // forward azimuth from observer to observed double Dst // distance between observer and observed ) { if(RefLat > PolarLimit) return 1; // RefLat > 89°59'00"N if(RefLat < -PolarLimit) return 2; // RefLat > 89°59'00"S if(RefLng > pi || RefLng < -pi) return 3; // RefLng > 180° E or W if(Dst > 19996296.296867833) return 4; // too close to antipodal double T = EquPol * tan(RefLat), // tangent of observer's reduced latitude S = sin(Brg), // sine of forward azimuth C = cos(Brg); // cosine of forward azimuth /* Zero the back-azimuth. Then, provided the forward azimuth is not exactly east or west: */ BakAzi = 0; if(C != 0) BakAzi = 2 * atan2(T,C); double A = 1 / sqrt(T * T + 1), // cosine of observer's reduced lat B = T * A, // sine of observer's reduced lat D = A * S, // sine of 'a' F = 1 - D * D, // cos-squared-a x = sqrt((1 / EquPol / EquPol - 1) * F + 1) + 1; x = (x - 2) / x; double X = x * x, d = (0.375 * X - 1) * x; T = Dst / (EquPol * EquRad * (X / 4 + 1) / (1 - x)); double y = T, H, // sin(y) I, // cos(y) J, // cos(z) e, Y; /* DO UNTIL NO FURTHER SIGNIFICANT CHANGE IN y. Note that 'ic' is an itera- tion counter, which cuts off the loop after 100 iterations in case y does not converge. 0.5e-13 is the convergence limit for a distance accuracy to within 1 mm on the Earth's surface. */ int ic = 0; // zero the iteration counter do { H = sin(y); I = cos(y); J = cos(BakAzi + y); e = J * J * 2 - 1; Y = y; y = T + (((H * H * 4 - 3) * (e + e - 1) * J * d / 6 + e * I) * d / 4 - J) * H * d; } while(fabs(y - Y) > 0.5e-13 && ++ic < 100); if(ic >= 100) // if 'y' didn't converge sufficiently within return 5; // 100 iterations, exit with error code 5 BakAzi = A * I * C - B * H; VinLat = atan2( B * I + A * H * C, EquPol * sqrt(D * D + BakAzi * BakAzi) ); x = atan2(H * S, A * I - B * H * C); double c = ((4 - 3 * F) * FlatF + 4) * F * FlatF / 16; VinLng = RefLng + x - (1 - c) * ((e * I * c + J) * H * c + y) * D * FlatF; BakAzi = atan2(D, BakAzi) + pi; return 0; // success } /* Solution to the Geodetic INVERSE Problem by T. Vincenty: a modified Rains- ford's Method with Helmert's elliptical terms. Effective in any azimuth and at any distance short of antipodal. Latitudes and longitudes in radians positive north and east. Azimuths in radians clockwise from north. Distance in metres. Transcoded by the author from a FORTRAN version by Lcdr L Pfeifer NGS Rockville MD 20FEB75. Error Code values: 0 no error 6 aircraft's latitude > 89°59'00"N 7 aircraft's latitude > 89°59'00"S 8 observed's latitude > 89°59'00"N 9 suspect that observed is at observer's antipode 10 Reference longitude > 180° E or W 11 Observed's longitude > 180° E or W 12 More than 100 iterations without converging 13 Observer and observed are antipodal */ int VSGIP( // VINCENTY SOLUTION TO THE GEODETIC INVERSE PROBLEM double RefLat, // latitude of the observer double RefLng, // longitude of the observer double ObsLat, // latitude of the observed double ObsLng // longitude of the observed ) { if(RefLat > PolarLimit) return 6; //aircraft's latitude > 89°59'00"N else if(RefLat < -PolarLimit) return 7; //aircraft's latitude > 89°59'00"S else if(ObsLat > PolarLimit) return 8; //observed's latitude > 89°59'00"N else if(ObsLat < -PolarLimit) return 9; //observed's latitude > 89°59'00"S // Reference longitude > 180° E or W else if(RefLng > pi || RefLng < -pi) return 10; // Observed's longitude > 180° E or W else if(ObsLng > pi || ObsLng < -pi) return 11; double A = ObsLng - RefLng, // longitude difference B = EquPol * tan(RefLat), // tan(reduced latitude of observer) C = EquPol * tan(ObsLat), // tan(reduced latitude of observed) /* Compute the sine and cosine of the reduced latitude of the ob- server and the cosine of the reduced latitude of the observed. */ D = 1 / sqrt(1 + B * B), E = D * B, F = 1 / sqrt(1 + C * C), T = A, // prime T before entering the loop O, // holds previous value of T for each iteration G, H, I, J, // sine(T) and cos(T) K, L, // sin(S) and cos(S) M, e, y, c; VinDst = D * F; // prime ellipsoidal dist (observer to observed) BakAzi = VinDst * C; // prime the back-azimuth FwdAzi = BakAzi * B; // prime the forward azimuth /* DO UNTIL NO FURTHER SIGNIFICANT CHANGE IN T. Note that 'ic' is an iteration counter, which cuts off the loop after 100 iterations in case T does not converge. 0.5e-13 is the convergence limit for a distance accuracy to within 1 mm on the Earth's surface. */ int ic = 0; // zero the iteration counter do { I = sin(T); J = cos(T); B = F * I; C = BakAzi - E * F * J; G = sqrt(B * B + C * C); H = VinDst * J + FwdAzi; y = atan2(G, H); K = VinDst * I / G; L = 1 - K * K; M = FwdAzi + FwdAzi; if(L > 0) M = H - M / L; e = M * M * 2 - 1; c = ((4 - 3 * L) * FlatF + 4) * L * FlatF / 16; O = T; // save old T before computing its new value T = (1 - c) * ((e * H * c + M) * G * c + y) * K * FlatF + A; } while(fabs(O - T) > 0.5e-13 && ++ic < 100 ); if(ic >= 100) return 12; // T converged OK, so now compute the distance 'VinDst'. T = sqrt((1 / EquPol / EquPol - 1) * L + 1) + 1; T = (T - 2) / T; c = (T * T / 4 + 1) / (1 - T); T = (0.375 * T * T - 1) * T; VinDst = ((((G * G * 4 - 3) * (1 - e - e) * M * T / 6 - e * H) * T / 4 + M ) * G * T + y ) * c * EquRad * EquPol; /* If distance calculation be indeterminate then the locations of the observer and observed are either antipodal or coincident; else, compute the forward and back azimuths. */ if(isnan(VinDst) > 0) { // if the distance between the observer // and the observed be indeterminate, then if(fabs(A) > 1) // if the observer and observed are antipodal return 13; // return errpr code 13 [invalid] VinDst = 0; // the points must therefore be coincident } else { // otherwise the distance was determined FwdAzi = atan2(B, C); // so compute forward and back azimuths BakAzi = atan2(D * I, BakAzi * J - E * F) + pi; } if(VinDst > MPR) // Rob's addition: not interested in return 0; // in features beyond one Earth-radian. if(VinDst < 0) // Ensure that distances are always positive, VinDst = -VinDst; // just to be on the safe side. FwdAzi = RatAng(FwdAzi); BakAzi = RatAng(BakAzi); return 0; // success } // Wrapper for Vincenty functions to automatically display any error messages int VinChk(int VinErr){ if(VinErr > 0 && VinErr < 15) showMsg(VinErr,RED); } // --------GENERAL FLIGHT FUNCTIONS CALLED FROM MORE THAN ONE TAB------------- /* SET UP HEIGHT COMPUTATION PARAMETERS WHEN THE ToFlag CHANGES STATE This only need computing when the landing flag or the to-flag changes state. Called only from 3 places in updtTrack() */ void ToFrom() { if(Landing) { // if in landing phase [beyond final waypoint] HgtH1 = cwpHgt + StlHgt; // height above sea level of final waypoint cwp HgtH2 = cwpHgt; // height above sea level of touchdown point /* Standard glide slope angle is 3 degrees. Need to know the distance 'Hgt_D' from touchdown at which to start the sigmoidal descent profile. Hgt_D = (12 / (5 * tan(3deg))) * h = 45.7947280505h [Hgt_D & h are in metres] */ Hgt_D = 45.7947280505 * (HgtH1 - HgtH2); HgtBD = LndDst - Hgt_D; } else if(ToFlag) { // if going 'to' the current waypoint [cwp], Hgt_D = PFD; // dist [in km] between previous & current waypoints HgtH1 = pwpHgt; // height above sea level of the receding waypoint pwp HgtH2 = cwpHgt; // height above sea level of approaching waypoint cwp } else { // if receding 'from' current waypoint [cwp], Hgt_D = NFD; // dist [in km] between current & next waypoints HgtH1 = cwpHgt; // height above sea level of receding waypoint cwp HgtH2 = nwpHgt; // height above sea level of approaching waypoint nwp } } /* INITIALISE THE AIRCRAFT'S ENCOUNTER WITH A NEW WAYPOINT. Sets up the rela- tionship between the aircraft and the waypoint with reference to which it is about to navigate. This is done just before the start of a flight and each time it passes the half-way point between the waypoint from which it is receding and the next waypoint in the route. Called from one place each in the functions setAircraftAt() and updtTrack(). */ void initEncounter() { if(cwp == 0) // if this is the first waypoint in the route pwp = 0; // the previous waypoint does not exist else // otherwise the previous waypoint's index number pwp = cwp - 1; // is 1 less than this waypoint's index number. if(cwp == LWP) // if this is the last waypoint in the route nwp = LWP; // the next waypoint is itself else // otherwise, the index number of the next waypoint nwp = cwp + 1; // is 1 + this waypoint's index number int i = WP[pwp].cgf, // geographic feature number of current waypoint j = WP[cwp].cgf, // geographic feature number of current waypoint k = WP[nwp].cgf; // geographic feature number of current waypoint pwpHgt = GF[i].Hgt; // set height of previous waypoint cwpLat = GF[j].Lat; // set latitude of current waypoint cwpLng = GF[j].Lng; // set longitude of current waypoint cwpHgt = GF[j].Hgt; // set height of current waypoint nwpHgt = GF[k].Hgt; // set height of next waypoint InRad = WP[cwp].InRad; // get its inbound radial from previous waypoint OutRad = WP[cwp].OutRad; // get its outbound radial to next waypoint PFD = WP[cwp].PFD; // full distance to previous waypoint PHD = WP[cwp].PHD; // half-distance to previous waypoint NFD = WP[cwp].NFD; // full distance to next waypoint NHD = WP[cwp].NHD; // half-distance to next waypoint ORD = WP[cwp].ORD; // waypoints: 1:first; 2:last; 0:all others PreDst = 0; // prime previous distance CtrDst = WP[cwp].CtrDst; // dist to ctr of this waypoint's turning circle CtrBrg = WP[cwp].CtrBrg; // brg of ctr of this waypoint's turning circle TanDst = WP[cwp].TanDst; // dist to where radials touch turning circle CtrLat = WP[cwp].CtrLat; // lat of centre of this wpt's turning circle CtrLng = WP[cwp].CtrLng; // lat of centre of this wpt's turning circle RIGHT = WP[j].RIGHT; // route makes right/left turn at this wpt. Landing = 0; // we don't start in the landing phase! ToFlag = 0; // aircraft initially recedes FROM 1st waypoint ToFrom(); // This block was added 08 July 2021. rgf = WP[cwp].rgf; // waypoint's number of associated features for(int i = 0; i < rgf; i++) { // Copy the references to the 7 geographic gf[i] = WP[cwp].gf[i]; // features associated with this waypoint to } // current waypoint's features data in gf[]. /* INITIALIZE THE STEALTH HEIGHT COMMAND CHANNEL FOR THIS ENCOUNTER For HEIGHT update, need to establish the half-distances between previous & current waypoints 'PHD' and current & next waypoint 'NHD'. Also the half- height-difference between the current & previous waypoints 'HHDP' and the current & next waypoints 'HHDN'. I also need the BASE height for the calcu- lation, which is the absolute height of the lower of the two waypoints in- volved in this encounter. Must do by comparing the heights of the current waypoint with that of the next or previous waypoint as appropriate. */ CmdHgt = cwpHgt; // Initialise the command height. HHDP = (double) // Half height-difference between ((cwpHgt - pwpHgt) / 2); // current & previous waypoints. if(ORD != 2) // Provided this isn't last way- HHDN = (double) // point, compute half height diff- ((cwpHgt - nwpHgt) / 2); // erence between current & next wpts. switch(ORD) { // If aircraft has just been reset to: case 0: // -any en-route waypoint, set both CmdHgt = cwpHgt + StlHgt; // aircraft height & command height to AirHgt = cwpHgt + StlHgt; // waypoint height + stealth height break; case 1: // -the first waypoint in the route, CmdHgt = cwpHgt + StlHgt; // waypoint height & command height to AirHgt = cwpHgt; // set aircraft's initial height to break; // waypoint height + stealth height. case 2: // -the last waypoint in the route, CmdHgt = cwpHgt; // set both aircraft height & command AirHgt = cwpHgt; // set both aircraft height & command } } /* SET THE AIRCRAFT TO A SPECIFIED WAYPOINT. Called from one place initFlightParams(), T0CL() and T1CL() and from 3 places in T3click(). */ void setAircraftAt() { initEncounter(); // init. an encounter between aircraft and this waypoint AirLat = cwpLat; // set aircraft's initial latitude AirLng = cwpLng; // set aircraft's initial longitude AirHgt = cwpHgt; // set aircraft's initial height CmdHgt = AirHgt; // command height: dynamic attractor for AirHgt CmdROT = 0; // zero the commanded rate of turn CmdROC = 0; // zero the commanded rate of climb AirBnk = 0; // zero the actual roll AirAtt = 0; // zero the actual pitch for(int i = 0; i < 436; i++) T4P[i] = 0; // clear Tab6's heights plotter T4PC = 0; // reset to first plot on screen if(cwp > 0 && cwp < LWP) { // if not first or last waypoint in route AirHgt += StlHgt; // add 100 metres to aircraft altitude CmdHgt = AirHgt; // command height: dynamic attractor for AirHgt } WptDst = 0; // zero aircraft's dist from current waypoint ReqHdg = OutRad; // set aircraft heading along outbound radial CmdHdg = OutRad; // set required heading along outbound radial AirHdg = OutRad; // set the actual heading of the aircraft if(cwp == 0) // if this is the first waypoint CmdHdg = RH[Route][0]; // runway heading of origin airport else if(cwp == LWP) // else if it's the destination waypoint CmdHdg = RH[Route][1]; // runway heading of destination airport CmdSpd = MinSpd; // set aircraft stationary } // --------------FUNCTIONS PERTAINING TO TAB 0 [ROUTES SELECTOR]-------------- /* Sorts distance array Hdst[] & corresponding index array Hidx[] into ascend- ing order of distance. Called only from one place in T0associatedFeatures() This function was added 08 July 2021. */ void T0HoareSort( int LOW, // lowest occupied element of the array to be sorted int HIGH) { // highest occupied element of the array to be sorted int low = LOW, // set moving low to LOW end of partition high = HIGH; // set moving high to HIGH end of partition if(HIGH > LOW) { // if the partition contains anything int Mid = Hdst[(LOW + HIGH) >> 1]; // get the content of its mid element while(low <= high) { // loop through the array until the indices cross /* While distance held in lowest element of Hdst[] < distance held in midway element of Hdst[], push lower sort boundary up by one element. */ while(low < HIGH && Hdst[low] < Mid) low++; /* While distance held in highest element of Hdst[] > distance held in midway element of Hdst[], pull upper sort boundary down by 1 element. */ while(high > LOW && Hdst[high] > Mid) high--; if(low <= high) { // if low index <= high index, swap the contents of int // the high & low elements of each of the 2 arrays x = Hdst[low]; Hdst[low] = Hdst[high]; Hdst[high] = x; x = Hidx[low]; Hidx[low] = Hidx[high]; Hidx[high] = x; low++; // push lower sort boundary up by one element high--; // pull upper sort boundary down by one element } } if(LOW < high) T0HoareSort(LOW,high); // sort lower partition if(low < HIGH) T0HoareSort(low,HIGH); // sort upper partition } } /* For all the waypoints in the current route, find its 7 nearest geographic features and place their reference numbers in the array gf[] of the way- point's data structure [see line 140 in struct waypoint]. One of the 7 features will] be the waypoint itself [distance zero]. This is done once when a new route is loaded. It avoids the map display function T3updt() having to re-compute the distances of ALL the waypoints in the route every time the aircraft is advanced. Instead, it only has to compute the dist- ances of the up to 7 geographic features associated with the current way- point. This function was added 08 July 2021. Called from only one place in 'T0getWayPts()' every time a new route is loaded. */ void T0associatedFeatures() { int i, k; for(i = 0; i < TWP; i++) { // for each waypoint in the route: for(k = 0; k < TGF; k++) { // For each geographic feature in this route: /* Compute the distance of this [the 'k'th] geographic feature from the 'i'th waypoint. VSGIP() places the answer [in metres] in the global variable 'VinDst'. */ VinChk(VinErr = VSGIP( GF[WP[i].cgf].Lat, // latitude of the 'i'th waypoint GF[WP[i].cgf].Lng, // longitude of the 'i'th waypoint GF[k].Lat, // latitude of the 'k'th geographic feature GF[k].Lng // longitude of the 'k'th geographic feature )); Hidx[k] = k; // index num within struct GF[] of 'k'th feature Hdst[k] = (int)VinDst; // distance of 'k'th feature from 'i'th waypoint } // end of the for(k) loop T0HoareSort(0,TGF-1); // sort above arrays into ascending order of distance for(k = 0; k < 7; k++) if(Hdst[k] < 200000) // if this feature is within 200 km of waypoint WP[i].gf[k] = Hidx[k]; // put waypoint's index number 'k' in shortlist else break; WP[i].rgf = k; // total number of associated features acquired } // end of the for(i) loop } int T0getLatLngHgt(int N, FILE *fp) { // Called only by T0getWayPts() below int x = 0, n = 0; for(i = 0; i < N; i++) { // For the N characters of the lat, lng or hgt char c = fgetc(fp); // input the [next] character from the file if(c == '-') n = 1; // set negative flag if '-' sign encountered else if(c > 47 && c < 58) // if it's within the ASCII range of a numeric x = x * 10 // accumulate it character by character to + (c - '0'); // form an integer } if(n == 1) x = -x; // reverse sign if '-' sign was encountered return x; // return lat, long or height as an integer } /* Loads the data for all the Geographic Features, pertaining to a newly sel- ected route, from its respective route00.txt file into the Geographic Feat- ures structure array. Line format of a routes00.txt file [Each line is terminated by a 'new line' character.]: "* 186780 780 106 Stansted". "*" indicates that this geographic feature is a waypoint " " absence of a * indicates that this geographic feature is not a waypoint " 186780" latitude as an integral number of seconds of arc " 780" longitude as an integral number of seconds of arc " 106" elevation in metres " Stansted" name of geographic feature. NOTE: do not put leading zeros in latitudes, longitudes or heights. Put the sign [if present] immediately in front of the first digit. Right-justify each figure in its field using leading spaces, e.g. " -1234". Useful ref: https://worldaerodata.com/nav/ for VORs DMEs etc.. */ void T0getWayPts() { // Called by T0CL() & initFlightParams() showMsg(17,WHITE); // show "Loading waypoint data" message char S[24] = "route00.txt"; // file name for default route: route00 S[5] = (char)((Route / 10) + 48); // 1st char of route number S[6] = (char)((Route % 10) + 48); // 2nd char of route number FILE *fp = fopen(S,"r"); // open selected route file for reading if(fp == NULL) { // if can't open waypoints data file showMsg(22,RED); return; // display error message and exit } LWP = -1; // prime the 'last waypoint' number for(TGF = 0; TGF < MGF; TGF++) { // For all features in the route... char c; // for each character input from file int x = 0; // clear the 'is waypoint' flag if((c = fgetc(fp)) == '*') // if this feature is a waypoint x = 1; // set the 'is waypoint' flag if(feof(fp)) break; // terminate loop at end of file if(x == 1) { // if this feature is a waypoint: WP[++LWP].cgf = TGF; // add feature to waypoints list GF[TGF].wpi = LWP; // and the waypoint index number of } // this feature's waypoint data else GF[TGF].wpi = -1; // else give it a 'null' index number /* Input the latitude, longitude [in integral seconds of arc] and height [in metres] as strings from the rout00.txt file, convert them all to integers then convert the latitude and longitude to (double)radians, and store all 3 in the Geographic Features structure. */ GF[TGF].Lat = T0getLatLngHgt(7, fp) / SPR; GF[TGF].Lng = T0getLatLngHgt(8, fp) / SPR; GF[TGF].Hgt = T0getLatLngHgt(6, fp); for(i = 0; i < 20; i++) { // For each of the upto 20 possible characters if((c = fgetc(fp)) == '\n') { // if it's a new-line character, add a GF[TGF].Name[i] = (char)0; // string-terminator null char instead break; // break out of loop if it's a 'CR' } GF[TGF].Name[i] = c; // store char in name of Geographic Feature } } fclose(fp); // Close the route file. TWP = LWP + 1; // total number of waypoints in this route if(LWP < 2) // If less than 2 waypoints, return; // can't make a route, so exit. /* SET UP THE FIRST WAYPOINT: In the main for() loop below, the following items do not get set for the first waypoint. */ WP[0].InRad = RH[Route][0]; // Inbound radial is runway heading WP[0].PFD = 0; // this waypoint's previous waypoint is itself WP[0].PHD = 0; // this waypoint's previous waypoint is itself WP[0].pgf = 0; // this waypoint's previous waypoint is itself WP[0].ORD = 1; // this waypoint is the first in the route /* LINK THE WAYPOINTS TOGETHER TO FORM A ROUTE Set up data from the second to the last in the route inclusive. The way- point being set up during each pass of the loop is the current one. The previous waypoint is the reference from which measurements are taken. */ for(i = 1 ; i < TWP; i++) { // For each waypoint except the first ... j = i - 1; // waypoint number to previous waypoint int I = WP[i].cgf, // index number of current waypoint's feature data J = WP[j].cgf; // index number of previous waypoint's feature data /* Compute the distance VinDst between the current and previous way- points and the forward FwdAzi and back BakAzi azimuths between them Note: FwdAzi & BackAzi are returned by VSGIP() as angles 0 to 2π. */ VinChk(VSGIP(GF[J].Lat, GF[J].Lng, GF[I].Lat, GF[I].Lng)); WP[i].InRad = BakAzi; // this waypoint's inbound radial WP[j].OutRad = FwdAzi; // previous waypoint's outbound radial double x = VinDst / 2; // half-distance between waypoints [in metres] WP[i].PFD = VinDst; // this waypoint's distance from prev waypoint WP[i].PHD = x; // half this waypoint's dist from prev waypoint WP[j].NFD = VinDst; // prev waypoint's distance to current waypoint WP[j].NHD = x; // half prev waypoint's dist to current waypoint WP[i].pgf = j; // previous wp is current wp's previous wp WP[j].ngf = i; // current 'wp' is previous 'wp's' next 'wp' WP[i].ORD = 0; // this is neither first nor last waypoint } /* SET UP THE LAST WAYPOINT: the following items do NOT get set up by the loop for the last waypoint. */ WP[LWP].OutRad = RH[Route][1]; // Outbound radial is dest. runway heading WP[LWP].NHD = 20000; // landing run [not actually used] WP[LWP].ngf = i; // this waypoint's next waypoint is itself WP[LWP].ORD = 2; // set it as the destination waypoint cwp = 0; // select the first waypoint in the route // [REF:TURNCTR] METHODOLOGY FOR WAYPOINT ENCOUNTER: see diagram turn0.png for(i = 0; i < TWP; i++) { // For each waypoint except the first: int I = WP[i].cgf; // index number of Geographic Feature double // Note T is θ in diagram turn0.png OutRad = WP[i].OutRad, // get this waypoit's inbound radial InRad = WP[i].InRad, // get this waypoit's outbound radial A = BipAng(InRad - OutRad), // -ve if left turn T = 0.5 * A, // +ve if right turn CtrDst = fabs(TrnRad / sin(T)), // must be always positive CtrBrg = RatAng(InRad - T), // puts CtrBrg on correct side Lat = GF[I].Lat, // latitude of waypoint Lng = GF[I].Lng; // longitude of waypoint /* Compute and store the state of the RIGHT flag, which indicates whether this waypoint is a right turn or a left turn within the route. */ if(A > 0) RIGHT = 1; else RIGHT = 0; WP[i].RIGHT = RIGHT; WP[i].CtrDst = CtrDst; // Distance and bearing of the centre of way- WP[i].CtrBrg = CtrBrg; // point's turning circle from the waypoint. /* Compute & store the distance from the waypoint, along the inbound & outbound radials respectively, of the beginning & end of the turn. */ WP[i].TanDst = sqrt(CtrDst * CtrDst - 100000000); // square of TrnRad /* Compute & store the latitude & longitude of the centre of the way- point's turning circle arc using the Vicenty Geodetic Direct function. */ VinChk(VSGDP(Lat,Lng,CtrBrg,CtrDst)); WP[i].CtrLat = VinLat; // latitude of centre of turning circle WP[i].CtrLng = VinLng; // longitude of centre of turning circle } WP[0].TanDst = 1000; // The origin waypoint does not have a normal TanDst. GFL = 1; // Set the 'Geographic Features loaded' flag. T0associatedFeatures(); // load each waypoint's associated features Ready = 1; // indicates that the route is loaded and ready showMsg(18,GREEN); // "Flight ready to depart." } /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF ROUTE TAB Called from only 1 place in T0show(). */ void T0buts() { if(TB != 0) return; // bail out if ROUTE tab not currently visible int h = LM; XSetForeground(XD,XG,DARK); // shade for buttons background for(int i = 0; i < 8; i++) { // shade the background area for each button XFillRectangle(XD,XW,XG,h,BY,53,BH); h += BW; // move on to the next button } const char // annotate horizontal row of control buttons *A[] = {"WIND"," "," "," "," "," "," ","HELP"}; const int a[] = {15,0,0,0,0,0,0,15}, // horiz offsets for word starts b[] = { 4,0,0,0,0,0,0, 4}; // number of letters in each word h = LM; int v = BY + 15; XSetForeground(XD,XG,GREY); // colour for the lettering for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(T0BS & 1 << i) XSetForeground(XD,XG,WHITE); // colour for the STOP button else XSetForeground(XD,XG,GREY); // colour for the lettering XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]); h += BW; } } /* DISPLAY THE ROUTE TAB [TAB0]. Shows Refresh Rate, Map Size, WIND data and Routes List. Highlights the currently selected value for each menu. Called only from 1 place each in T0CL(), showTab(), T0click(), MT4() & MT5(). */ void T0show() { if(TB != 0) return; // bail out if the Route tab is not currently visible clearTabArea(); XSetForeground(XD,XG,YELLOW); // Display the Refresh Rate Selector XDrawString(XD,XW,XG,LM,TL,"REFRESH RATE",12); char *S[] = { "5 seconds","2 seconds","1 second", "500 millisecs","200 millisecs","100 millisecs" }; int L[] = {9,9,8,13,13,13}, // lengths of the above lines of text v = TL + 18; // y-coord of first line for(int i = 0; i < 6; i++) { // for each of the iteration periods if(i == IP) // if this is the currently selected one XSetForeground(XD,XG,WHITE); // highlight it else // otherwise XSetForeground(XD,XG,GREY); // grey it XDrawString(XD,XW,XG,LM,v,*(S + i),*(L + i)); v += 16; // inter-line drop } XSetForeground(XD,XG,YELLOW); // Display the Map Size Selector XDrawString(XD,XW,XG,LM,191,"MAP SIZE [km]",13); char *S2[] = { "500 x 500","400 x 400","300 x 300","200 x 200","100 x 100"," 50 x 50" }; v = 209; // y-coord of first map size for(int i = 0; i < 6; i++) { // for each map size if(i == MapSize) // if it is the currently selected one XSetForeground(XD,XG,WHITE); // display it in white else // otherwise XSetForeground(XD,XG,GREY); // display it in grey XDrawString(XD,XW,XG,LM,v,*(S2 + i),9); v += 16; // drop down to next item } if(T0BS & 1) // display wind data if wind is active XSetForeground(XD,XG,WHITE); // if active, display wind data in white else // otherwise, display it in black XSetForeground(XD,XG,BLACK); // i.e. erase it XDrawString(XD,XW,XG,LM,318,"WIND: 230 @ 5m/s",16); XSetForeground(XD,XG,YELLOW); // Display MESSAGE annotation in yellow XDrawString(XD,XW,XG,LM,345,"MESSAGE",7); showMsg(CM,MC); // display the current message // DISPLAY THE ROUTES LIST int q = LM + 120, // left margin for the routes list x = TL + 40, y = TL + 15, z = TL - 5, w = TL + 48, u = q + 32, t = q + 56, s = q + 72, r = q + 74; XSetForeground(XD,XG,YELLOW); // show titles and headings in yellow XDrawLine(XD,XW,XG,u,z,u,w); // vertical XDrawLine(XD,XW,XG,t,y,t,w); // vertical XDrawLine(XD,XW,XG,u,z,s,z); // horizontal XDrawLine(XD,XW,XG,t,y,s,y); // horizontal XDrawString(XD,XW,XG,r,TL,"Origin Runway Heading [ORH] degrees 0-360",41); XDrawString(XD,XW,XG,r,TL+20, "Destination Runway Heading [DRH] deg 0-360",42); XDrawString(XD,XW,XG,q-6,TL+27,"Route",5); XDrawString(XD,XW,XG,q,x,"No.",3); XDrawString(XD,XW,XG,r,x,"ROUTE NAME",10); // UPDATE MOUSE SCROLL POSITION IF MOUSE WHEEL HAS BEEN MOVED if(MW == 1 && MB < TNR - 12) // if Mouse Wheel 1, MB++; // scroll down the list if(MW == 2 && MB > 0) // if Mouse Wheel 2, MB--; // scroll back up list MW = 0; // Mouse wheel event now dealt with v = TL + 60; // y-coord of top of routes list int M = TNR; // display all waypoints in the route, but if(TNR > 12) // limit to a maximum of 12 at a time M = 12; // DISPLAY THE APPROPRIATE SECTION OF UP TO 12 ROUTES for(int i = 0; i < M; i++) { // display runway headings and route name int x = MB + i; // route number is bias + display line number if(Route == x) // if displaying selected route XSetForeground(XD,XG,WHITE); // show selected route in white else // otherwise XSetForeground(XD,XG,GREY); // show all the other routes in grey XDrawString(XD,XW,XG,q,v,showInt(x,3),3); // route number XDrawString(XD,XW,XG,q+24,v,showRat(RH[x][0]),3); // origin rw heading XDrawString(XD,XW,XG,q+48,v,showRat(RH[x][1]),3); // dest. rw heading XDrawString(XD,XW,XG,s,v,RN[x],strlen(RN[x])); // route name v += 17; // drop down to the next line } // DISPLAY THE SCROLL BAR if(TNR > 12) { // if not more than 12 routes on file XSetForeground(XD,XG,DARK); // display whole scroll bar in dark grey XFillRectangle(XD,XW,XG,475,w,8,204); int h = 2248 / TNR; // extent of highlighted scroll bar v = MB * (204-h) / (TNR-12); // dull extent above highlighted part XSetForeground(XD,XG,YELL); // display highlighted part of scroll bar XFillRectangle(XD,XW,XG,475,w+v,8,h); } T0buts(); // display the 8 control buttons along the bottom } int T0CLsub(int h, int N) { // called only from T0CL() below int n = -1; // number of clicked name int L = 17; if(N == 6) // adjust the line drop L = 16; for(int i = 0; i < N; i++) { // for each Route name if(mY > h && mY < h + 15) { // if click was on this name n = i; break; // exit with i = route index number } h += L; // drop down to next line } return n; // return the selection number or -1 } /* MOUSE CLICKED ON A NON-BUTTON PART OF THE ROUTE TAB. Called only from 1 place in MEH(). */ void T0CL() { if(TB != 0) return; // bail out if ROUTE tab isn't currently visible int x = -1, y = TNR; if(mX > 134 && mX < 483) { // if within x-limits of Route names if(y > 12) // limit the display to 12 items y = 12; x = T0CLsub(111,y); // list number of clicked item if(x != -1) { // if list number is valid Route = x + MB; // set selected route T0getWayPts(); // load the waypoints for the default route RunState = 0; // set the flight tp 'stopped' if running cwp = 0; // set current waypoint as first in the route cgf = WP[cwp].cgf; // set reference to this waypoint's data setAircraftAt(); // set aircraft at start of current route } } // click was within the left menus column else if(mX > 15 && mX < 99) { // if a Refresh Rate was clicked if(mY < 170 && (x = T0CLsub(69,6)) != -1) { IP = x; // set selected Refresh Rate ET = ETS[IP]; // set elapsed time [in μs] } // if a Map Size clicked else if((x = T0CLsub(197,6)) != -1) { MapSize = x; // set the selected Map Size ms = MS[MapSize]; // set the corresponding map scale } } T0show(); // re-display the modified Routes Tab } /* THE ROUTE TAB'S 'WIND' OR 'HELP' BUTTON WAS PRESSED. Button numbers: 0:WIND, 7:HELP. Called from only 1 place in MEH(). */ void T0click(int x) { int y = 1 << x; // bit mask for the clicked button switch(x) { // switch on button number case 0: // WIND BUTTON PRESSED if(Wind) { // if wind is currently active Wind = 0; // switch wind off T0BS &= ~ 1; // dim the WIND button } else { // otherwise Wind = 1; // switch wind on T0BS |= 1; // brighten the WIND button } break; case 7: // ROUTE Tab HELP helpPage("navigation/nav.html#route"); } T0show(); // show the button } // -------------FUNCTIONS PERTAINING TO TAB 1 [WAYPOINTS SELECTOR]------------- /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF WAYPOINTS TAB Called from only one place in T1show(). */ void showT1buts() { if(TB != 1) return; // bail out if WAYPTS tab not currently visible int h = LM; // set h to left side of first button XSetForeground(XD,XG,DARK); // shade for buttons background // shade the background area for each button for(int i = 0; i < 8; i++) { XFillRectangle(XD,XW,XG,h,BY,53,BH); h += BW; // move across to the next button } char // annotate horizontal row of control buttons *A[] = {"","","","","","","","HELP"}; int a[] = {0,0,0,0,0,0,0,15}, // horiz offsets for word starts b[] = {0,0,0,0,0,0,0, 4}; // number of letters in each word h = LM; int v = BY + 15; // x & y coords of button block XSetForeground(XD,XG,GREY); // colour for the lettering for(int i = 0; i < 8; i++) { // for each of the 8 buttons: XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]); h += BW; // move across [rightwards] to next button } } /* DISPLAY THE STATIC CONTENT OF THE WAYPOINTS TAB. Called from only 1 place each in T1CL(), showTab(), updtTrack(), MT4() & MT5(). */ void T1show() { if(TB != 1) return; // bail out is WAYPTS tab not currently visible clearTabArea(); char *S[] = { "Waypoints of","the currently","active route.","The aircraft", "is currently","locked on to","highlighted","waypoint.","", "List may be","scrolled by","mouse wheel." }; int L[] = {12,13,13,12,12,12,11,9,0,11,11,12}; // line lengths XSetForeground(XD,XG,BLEY); // colour in which to display instructions int v = TL + 20, // y-coord of first line of instructions h = LM; // x-coord of instruction text block // display the instruction lines for(int i = 0; i < 12; i++) { XDrawString(XD,XW,XG,h,v,*(S + i),*(L + i)); v += 17; // drop to next lower line } int Q = LM + 95; // y-coord of column headings XSetForeground(XD,XG,YELLOW); // show column headings in yellow XDrawString(XD,XW,XG,Q,TL,"No.",3); XDrawString(XD,XW,XG,Q+28,TL,"WAYPOINT NAME",13); XDrawString(XD,XW,XG,Q+162,TL,"LATITUDE",8); XDrawString(XD,XW,XG,Q+228,TL,"LONGITUDE",9); XDrawString(XD,XW,XG,Q+318,TL,"HEIGHT",6); if(MW == 1 && LB < TWP-16) // if Mouse Wheel 1 LB++; // scroll down the list if(MW == 2 && LB > 0) // if Mouse Wheel 2 LB--; // scroll back up list MW = 0; // Mouse wheel event now dealt with v = TL + 20; // y-coord for first waypoint shown int M = TWP; // number of lines to be displayed if(TWP > 16) // only got space for 16 lines max M = 16; for(int i = 0; i < M; i++) { // for each line to be displayed int x = LB + i; // waypoint number for current line if(cwp == x) // if this is the current waypoint XSetForeground(XD,XG,WHITE); // highlight it in bright white else // otherwise XSetForeground(XD,XG,GREY); // display it in grey // display waypoint's list number, name, latitude, longitude and height XDrawString(XD,XW,XG,Q,v,showInt(x,3),3); XDrawString(XD,XW,XG,Q+28,v,GF[WP[x].cgf].Name,strlen(GF[WP[x].cgf].Name)); XDrawString(XD,XW,XG,Q+150,v,RadToDMS(GF[WP[x].cgf].Lat,0),10); XDrawString(XD,XW,XG,Q+222,v,RadToDMS(GF[WP[x].cgf].Lng,1),10); XDrawString(XD,XW,XG,Q+294,v,showHeight(GF[WP[x].cgf].Hgt),10); v += 17; // drop to next lower line } // If more than 16 waypoints in the route we need to display a scroll bar. if(TWP > 16) { // if more than 16 waypoints in route h = 4352 / TWP; // height of highlighted scroll bar v = LB * (272 - h) / (TWP - 16); // dull above the highlighted part XSetForeground(XD,XG,DARK); // show full length scroll bar in grey XFillRectangle(XD,XW,XG,475,TL+8,8,272); XSetForeground(XD,XG,YELL); // show highlighted part of scroll bar XFillRectangle(XD,XW,XG,475,TL+8+v,8,h); } showT1buts(); // show the 8 control buttons along the bottom } /* THE WAYPOINT TAB'S HELP BUTTON WAS PRESSED. Called from only one place in MEH(). */ void T1click(int x) { switch(x) { // switch on button number case 7: helpPage("navigation/nav.html#waypts"); } } /* Loads the names of all the available routes into the Routes Menu array ready for display by the showMenu() function. For each of the route names in the route names file, gets each of its characters in turn and puts it in this route name's character array. However, when it hits a 'new line' character, it break out of the inner loop. When, in the outer loop, it hits the 'end of file', it notes the number of named routes and the breaks the outer loop. Line format of routes.txt "224 062 Stansted to Edinburgh". "224 " runway heading at origin [degrees 0 to 360] "062 " runway heading at destination [degrees 0 to 360] "Stansted to Edinburgh" name of route Each line is terminated by a 'new line' character. */ void T1routes() { // Called only by initFlightParams() showMsg(16,WHITE); // display the "Loading Route Names" message FILE *fp = fopen("routes.txt", "r"); // open route names file for reading if(fp == NULL) { // if could not find or open the file showMsg(21,RED); return; // show eror message in red and exit } char c; // for characters got from the route names file int x; // for runway headings for(i = 0; i < 100; i++) { // for each of upto 100 possible routes: if(feof(fp)) { // break when 'end of file' encountered TNR = --i; // number of routes break; } x = 0; // clear the integer accumulator for(j = 0; j < 3; j++) { // for each of up to 3 numeric characters c = fgetc(fp); // input the next character if(c > 47 && c < 58) // if it is within numeric character range x = x * 10 // multiply previous value by 10 + (c - '0'); // and add this characters integer value } // integer value = ASCII value - '0' [48] /* convert origin runway heading from degrees to radians; store in runway headings array */ RH[i][0] = (double)x * RPD; c = fgetc(fp); // skip the space character x = 0; // clear the integer accumulator for(j = 0; j < 3; j++) { // for each of up to 3 numeric characters c = fgetc(fp); // input the next character /* if it is within numeric character range multiply prev- ious value by 10 and add this character's integer value. Integer value = ASCII value - '0' [48] */ if(c > 47 && c < 58) x = x * 10 + (c - '0'); } RH[i][1] = (double) // convert destination runway heading from ° x * RPD; // to radians; store in runway headings array c = fgetc(fp); // skip the space character for(int j = 0; j < 80; j++) { // For each of up to 80 characters if((c = fgetc(fp)) == '\n') { // if character is a 'new line' RN[i][j] = (char)0; // terminate the route name string break; // terminate the loop } RN[i][j] = c; // store character in route name array } } // end of 'for each of upto 100 possible routes:' fclose(fp); // Close the route names file. } /* RELOCATE THE AIRCRAFT TO THE WAYPOINT CLICKED WITHIN THE WAYPTS TAB'S WAYPOINTS LIST. Called from only 1 place in MEH(). */ void T1CL() { if(mX > 111 || mX < 465) { // click within horiz limits of waypoints list int v = 71, i; // vertical coord of top of list for(i = 0; i < 16; i++) { // for each of the 16 visible waypoints if(mY > v && mY < v+15) // if the click was on this waypoint break; // break out: i is its list number v += 17; // drop down to next waypoint name } if(i < 16) { // click was on a waypoint entry cwp = LB + i; // set current waypoint = clicked waypoint RunState = 0; // stop the flight if it is running cgf = WP[cwp].cgf; // reference to waypoint data setAircraftAt(); // set aircraft to current waypoint T1show(); // re-display the waypoints list } } } // ---------------FUNCTIONS PERTAINING TO TAB 2 [GEOID SELECTOR]--------------- /* INSTALL THE METRICS FOR THE SELECTED GEIOD. Called from only 1 place each in initFlightParams(), T2auto() and T2click(). */ void T2setGeoid() { const double A[] = { // SEMI-MAJOR AXES OF AVAILABLE GEOIDS 6378137.000, // 0 GRS80 / WGS84 (NAD83) * 6378206.400, // 1 Clark 1866 (NAD27) 6377563.396, // 2 Airy 1858 6377340.189, // 3 Airy Modified 6378160.000, // 4 Australian National 6377397.155, // 5 Bessel 1841 6378249.145, // 6 Clark 1880 6377276.345, // 7 Everest 1830 6377304.063, // 8 Everest Modified 6378166.000, // 9 Fisher 1960 6378150.000, // 10 Fisher 1968 6378270.000, // 11 Hough 1956 6378388.000, // 12 International (Hayford) 6378245.000, // 13 Krassovsky 1938 6378145.000, // 14 NWL-9D (WGS 66) 6378160.000, // 15 South American 1969 6378136.000, // 16 Soviet Geod. System 1985 6378135.000, // 17 WGS 72 }, F[] = { // FLATTENING FACTORS OF GEOIDS 0.00335281068118366789616980869833, // 0 GRS80 / WGS84 (NAD83) * 0.00339007530392991937343144432063, // 1 Clark 1866 (NAD27) 0.00334085064149707745426055919427, // 2 Airy 1858 0.00334085064149707745426055919427, // 3 Airy Modified 0.00335289186923721709974853310981, // 4 Australian National 0.00334277318217480587901060845382, // 5 Bessel 1841 0.00340756137869933382175046428024, // 6 Clark 1880 0.00332444929666288455151682985834, // 7 Everest 1830 0.00332444929666288455151682985834, // 8 Everest Modified 0.00335232986925913509889373114314, // 9 Fisher 1960 0.00335232986925913509889373114314, // 10 Fisher 1968 0.00336700336700336700336700336700, // 11 Hough 1956 0.00336700336700336700336700336700, // 12 International (Hayford) 0.00335232986925913509889373114314, // 13 Krassovsky 1938 0.00335289186923721709974853310981, // 14 NWL-9D (WGS 66) 0.00335289186923721709974853310981, // 15 South American 1969 0.00335281317789691440603238146967, // 16 Soviet Geod. System 1985 0.00335277945416750486153020854288, // 17 WGS 72 }; EquRad = A[Geoid]; // semi-major axis of selected geoid [metres] FlatF = F[Geoid]; // flattening factor of selected geoid EquPol = 1 - FlatF; // ratio between polar and equitorial radii of Earth } /* DISPLAYS THE CONTENT OF THE GEOID TAB. Highlights the currently selected selection mode and geoid. Called from only 1 place each in T2click(), showTab() and T2auto(). */ void T2show() { if(TB != 2) return; const char *S1[] = { "Geoid selection can be MANUAL","or AUTOMATIC. The selection", "mode currently effective is","the highlighted [bright] one.", "You can change selection mode","by clicking on MANUAL or AUTO-", "MATIC. The GEOID currently in","use by the Vincenty distance", "& bearing computations is the","one highlighted on the right.", "When in MANUAL selection mode,","a different geoid can be sel-", "ected by clickling on its name." }, *S2[] = { // Geoid Select Menu "GRS80/WGS84 (NAD83)","Clark 1866 (NAD27)","Airy 1858","Airy Modified", "Australian National", "Bessel 1841", "Clark 1880", "Everest 1830", "Everest Modified","Fisher 1960","Fisher 1968","Hough 1956", "International (Hayford)","Krassovsky 1938","NWL-9D (WGS 66)", "South American 1969","Soviet Geod System 1985","WGS 72" }; const int L1[] = {29,27,27,29,29,30,29,28,29,29,30,29,31}, L2[] = {19,18,9,13,19,11,10,12,16,11,11,10,23,15,16,19,23,6}; int c = GREY, d = GREY; if(Gauto == 1) // if in AUTOMATIC mode d = WHITE; // highlight the word AUTOMATIC else // else, must be in MANUAL mode c = WHITE; // so highlight the word MANUAL XSetForeground(XD,XG,c); XDrawString(XD,XW,XG,LM,TL + 20,"MANUAL",6); XSetForeground(XD,XG,d); XDrawString(XD,XW,XG,LM,TL + 38,"AUTOMATIC",9); XSetForeground(XD,XG,DARK); XFillRectangle(XD,XW,XG,145,TL+23,53,BH); // background for HELP button XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,160,TL + 38,"HELP",4); // annotation for HELP button // DISPLAY THE INSTRUCTION TEXT IN GREY XSetForeground(XD,XG,BLEY); int v = TL + 90; // vertical coord of first line of instructions text for(int i = 0; i < 13; i++) { XDrawString(XD,XW,XG,LM,v,*(S1 + i),*(L1 + i)); v += 18; } // DISPLAY THE LIST OF GEOIDS, HIGHLIGHTING THE SELECTED ONE v = TL; // vertical coord of first line of geoids list for(int i = 0; i < 18; i++) { if(i == Geoid) XSetForeground(XD,XG,WHITE); else XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,255,v,*(S2 + i),*(L2 + i)); v += 18; } // SHOW THE TWO LIST [MENU] TITLES XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,145,TL,"AVAILABLE GEOIDS:",17); XDrawString(XD,XW,XG,LM,TL,"GEOID SELECTION",15); } /* MOUSE WAS CLICKED WITHIN THE GEOID TAB. Called from 2 places in MEH(). */ void T2click() { int h = 51, // top of first click area n = -1; // number of clicked name if(mX > 144 && mX < 198 && mY > 85 && mY < 107) // HELP buton clicked helpPage("navigation/nav_full.html#geoid"); else if(mX > 253 && mX < 483) { // if within x-limits of Geoid names for(int i = 0; i < 18; i++) { // for each geoid name if(mY > h && mY < h + 15) { // if click was on this name n = i; break; // exit with i = geoid index number } h += 18; // drop down to next line } if(n != -1) { // click was on a geoid name Geoid = n; // set number of newly selected geoid T2setGeoid(); // set up the geoid parameters } } else if(mX > 14 && mX < 75) { // if within x-limits of mode selector if(mY > 71 && mY < 86) // if click was on MANUAL Gauto = 0; // set to manual else if(mY > 89 && mY < 104) // if click was on AUTOMATIC Gauto = 1; // set to automatic } T2show(); // re-display the Geoid Tab content } /* Proposed function to automatically select the best geoid to use according to the aircraft's current position on the planet. This current implement- ation is very rudimentary. The applicable zones for each geoid need to be properly researched and constructed. */ void T2auto() { // called only from updtTrack() below if(Gauto == 0) return; // return if geoid selection is set to manual int x = 0; // for the geiod index number if(AirLat < 0) { // If aircraft is in the southern hemisphere: /* if the aircraft's longitude is between 30°W and 150°W use the South American geoid else use the Australian geoid */ if(AirLng < -0.523598776 && AirLng < -2.617993878) x = 4; else x = 15; } else { // Else aircraft is in the northern hemisphere: /* if the aircraft's longitude is between 30°W and 150°W, use the North American geoid else if its longitude is between 45°E and 170°E use the Soviet geoid. */ if(AirLng < -0.523598776 && AirLng < -2.967059728) x = 0; else if(AirLng > 0.785398163 && AirLng < 0.296705973) x = 16; } if(Geoid != x) { // if the selected geoided has been automatically changed Geoid = x; // set the global geoid index number T2setGeoid(); // set up the parameters of the new geoid T2show(); // re-display the geiod selector menu } } // -----------------FUNCTIONS PERTAINING TO TAB 3 [MOVING MAP]----------------- // DRAW THE CURRENT WAYPOINT'S GUIDE ARC AND ARC RADII void T3guideArc(int mapx, int mapy) { double a = CtrDst * ms, // Distance from waypoint to the radial // centre of its turning circle arc. b = CtrBrg - CmdHdg; // Bearing, relative to screen vertical, // from the waypoint to the radial // centre of its turning circle arc. if(RIGHT == 0) // if it is a left turn, rotate the b += pi; // bearing to the opposite direction b = RatAng(b); // re-rationalise it 0-360 int S = rint(a * sin(b)), // x-component of dist to centre of arc C = rint(a * cos(b)); // y-component of dist to centre of arc if(RIGHT == 0) { // If this waypoint is a left turn, S = -S; // transpose the arc to the other side. C = -C; } double Cx = mapx + S, // x-coord of centre of turning circle Cy = mapy - C, // y-coord of centre of turning circle A = HalfPi - CtrBrg + OutRad; // Half the angle subtended by the // arc: π/2 - θ in diagram turn0.png a = TrnRad * ms; // Need to make the arc's radials the if(CtrDst < 0) // same sign as the distance between a = -a; // the waypoint and the centre of the arc. // DRAW THE TWO BOUNDING RADII OF THE TURNING ARC IN GREY. XSetForeground(XD,XG,GREY); double K = RatAng(b - A + pi); int x = rint(a * sin(K)), y = rint(a * cos(K)), z = Cy - y; if(z < BotLim && z > TopLim) { XDrawLine(XD,XW,XG,Cx,Cy,Cx + x,Cy - y); Flag = 1; // permit display of radius } double L = RatAng(b + A + pi); x = rint(a * sin(L)); y = rint(a * cos(L)); z = Cy - y; if(z < BotLim && z > TopLim) { XDrawLine(XD,XW,XG,Cx,Cy,Cx + x,Cy - y); Flag = 1; // permit display of radius } // DRAW THE ARC ITSELF: A RIGHT TURN IN GREEN AND A LEFT TURN IN RED. if(Flag == 1) { // if display is permitted: double c, q; if(RIGHT == 1) { // for right turn: go clockwise from K to L XSetForeground(XD, XG, GREEN); } else { // for left turn: go clockwise from L to K XSetForeground(XD, XG, RED); c = K; // transpose L and K for left turn K = L; L = c; } q = L - K; // angle swept by the arc. if(q < 0) // if the sweep angle is negative q = RatAng(q); // rationalise it to range from 0 to 2π L = K + q; // L is greater than K [rationalised] for(c = K; c < L; c += RPD) // for each degree from K to L XDrawPoint( XD,XW,XG, // plot a point in RED or GREEN Cx + rint(a * sin(c)), // x-coord of plot Cy - rint(a * cos(c)) // y-coord of plot ); } } // DRAW THE CURRENT WAYPOINT'S INBOUND AND OUTBOUND RADIALS void T3drawRads(int mapx, int mapy) { int // SET UP DATA FOR DRAWING THE WAYPOINT'S RADIALS X1 = RgtLim - mapx, // positive limit of x (rightwards is +ve) X2 = LftLim - mapx, // negative limit of x (leftwards is -ve) Y2 = TopLim - mapy, // negative limit of y (upwards is -ve) Y1 = BotLim - mapy; // positive limit of y (downwards is +ve) // DRAW THE CURRENT WAYPOINT'S OUTBOUND RADIAL if(RIGHT == 1) // radial is GREEN for a right turn XSetForeground(XD,XG,GREEN); else // and RED for a left turn XSetForeground(XD,XG,RED); double a = OutRad - CmdHdg, // screen direction for outbound radial b = TanDst * ms, // distance from waypoint of start of radial c = NHD; // distance from waypoint of end of radial if(c > WPR) // If radial would extend beyond radio range, c = WPR; // limit it to extend only up to radio range. c *= ms; // scale the real-world range to map pixels // [REF:CHOP1] See diagram radialChop.png 'a' is θ in the diagram double // NOTE: screen Y coords are -ve upwards and +ve downwards S = sin(a), // sine of the screen bearing of the outbound radial C = cos(a), // cosine of the screen bearing of the outbound radial X = c * S, // X is +ve or -ve according to a's quadrant Y = -c * C, // Y is -ve or +ve according to a's quadrant R1, R2; // lengths of X and Y generated chopped radials Flag = 0; // 1:overshoot occurred; 0:didn't occur if(X > X1) { // if radial has overshot to the right of map area X = X1; // set x limit to the right extremity Flag = 1; // indicate that an overshoot has occurred } else if(X < X2) { // if radial has overshot to the left of map area X = X2; // set x limit to the left extremity Flag = 1; // indicate that an overshoot has occurred } if(Y > Y1) { // if radial has overshot the bottom of map area Y = Y1; // set y limit to the bottom extremity † Flag = 1; // indicate that an overshoot has occurred } else if(Y < Y2) { // if radial has overshot the top of map area Y = Y2; // set y limit to the top extremity Flag = 1; // indicate that an overshoot has occurred } if(Flag == 1) { // if an overshoot has occurred: if(a == 0) c = -Y2; // Avoid division by zero anomalies when a radial else // is aligned exactly with one of the axes. if(a == pi) c = Y1; else if(a == HalfPi) c = X1; else if(a == HalfPi + pi) c = -X2; else { // otherwise: R1 = fabs(X / S); // compute x-component of chopped radial R2 = fabs(Y / C); // compute y-component of chopped radial if(R1 > R2) // If radial based on x limit is greater than c = R2; // the radial based on y-limit then use else c = R1; // the smaller of the two; and vice versa. } } if(b < c) { // if there is at least some of the XDrawLine( // radial to show, then dosplay it. XD,XW,XG, mapx + rint(b * S), mapy - rint(b * C), mapx + rint(c * S), mapy - rint(c * C) ); } // DRAW THE CURRENT WAYPOINT'S INBOUND RADIAL a = InRad - CmdHdg; // screen direction for inbound radial c = PHD; // half the distance to the previous waypoint if(PHD > WPR) // if this is greater than the waypoint's radio c = WPR; // range, limit it to the waypoint's radio range. c *= ms; // scale real-world distance to map pixels /* In the case of the originating waypoint, the inbound radial is the runway heading; so make it yellow, starting at the waypoint itself and extending for 8 kilometres beyond the waypoint. */ if(cwp == 0) { XSetForeground(XD,XG,YELLOW); b = 0; c = 8000 * ms; // scale 8km to map pixels } // [REF:CHOP2] See diagram radialChop.png 'a' is θ in the diagram S = sin(a); // sine of the screen bearing of the inbound radial C = cos(a); // cosine of the screen bearing of the inbound radial X = c * S; // +ve or -ve according to a's quadrant Y = -c * C; // -ve or +ve according to a's quadrant Flag = 0; // indicates whether or not the radial overshoots if(X > X1) { // if radial has overshot to the right of map area X = X1; // set x limit to the right extremity Flag = 1; // indicate that an overshoot has occurred } else if(X < X2) { // if radial has overshot to the left of map area X = X2; // set x limit to the left extremity Flag = 1; // indicate that an overshoot has occurred } if(Y > Y1) { // if radial has overshot the bottom of map area Y = Y1; // set y limit to the bottom extremity † Flag = 1; // indicate that an overshoot has occurred } else if(Y < Y2) { // if radial has overshot the top of map area Y = Y2; // set y limit to the top extremity Flag = 1; // indicate that an overshoot has occurred } if(Flag == 1) { // if an overshoot has occurred: if(a == 0) c = -Y2; // Avoid division by zero anomalies when a radial else if(a == pi) // is aligned exactly with one of the axes. c = Y1; else if(a == HalfPi) c = X1; else if(a == HalfPi + pi) c = -X2; else { // otherwise: R1 = fabs(X / S); // compute x-component of chopped radial R2 = fabs(Y / C); // compute y-component of chopped radial if(R1 > R2) // if radial based on the x limit is greater c = R2; // than the radial based the on y-limit then else c = R1; // use the smaller of the two; and vice versa. } } if(b < c) { // if there is at least some of the XDrawLine( // radial to show, then display it. XD,XW,XG, mapx + rint(b * S), mapy - rint(b * C), mapx + rint(c * S), mapy - rint(c * C) ); } } /* To display the map. This function is executed from the 'run' loop. It updates local variables containing all the data needed to draw the map. Called from 1 place in T3show(), 2 places in updtTrack(), and 5 places in T3click(). */ void T3updt() { if(TB != 3) return; // the map only appears on Tab 3 /* The map can be painted only after the route names and the waypoints of the default route have all been loaded. Painting before this time would paint data from variables that had not yet been created. The GFL flag is set true once waypoint data is available. If it goes false later during the loading of another route's waypoints, repainting of the map can still take place without any problems. */ if(GFL == 0) return; // exit if routes & default waypoints not yet loaded /* Display current waypoint's Name, Latitude, Longitude, Height, Distance, Bearing + Selected Radial; and the aircraft's Latitude, Longitude, Alti- tude, Speed, required and commanded Headings + Rate of Turn. */ XSetForeground(XD,XG,BLACK); // clear all the writing areas to black XFillRectangle(XD,XW,XG,83,50,90,301); // position correct 16/08/2020 XFillRectangle(XD,XW,XG,42,68,48,17); // extra space for waypoint name XSetForeground(XD,XG,WHITE); // write all data items in white XDrawString(XD,XW,XG,DM-60,TL+18,showName(GF[WP[cwp].cgf].Name),20); XDrawString(XD,XW,XG,DM,TL+36,RadToDMS(cwpLat,0),10); XDrawString(XD,XW,XG,DM,TL+54,RadToDMS(cwpLng,1),10); XDrawString(XD,XW,XG,DM,TL+72,showHeight(cwpHgt),10); int x = WptDst, // distance of the waypoint from the aircraft y; if(ToFlag == 1) // Distance is positive when travelling FROM a x = -x; // waypoint and negative when travelling TO it. XDrawString(XD,XW,XG,DM,TL+90,showHeight(x),10); XDrawString(XD,XW,XG,DM+36,TL+108,showRat(FwdBrg),3); XDrawString(XD,XW,XG,DM+36,TL+126,showRat(OutRad),3); XDrawString(XD,XW,XG,DM,TL+172,RadToDMS(AirLat, 0),10); XDrawString(XD,XW,XG,DM,TL+190,RadToDMS(AirLng, 1),10); XDrawString(XD,XW,XG,DM,TL+208,showHeight(AirHgt),10); XDrawString(XD,XW,XG,DM,TL+226,showHeight(CmdSpd),10); XDrawString(XD,XW,XG,DM+36,TL+244,showRat(ReqHdg),3); XDrawString(XD,XW,XG,DM+36,TL+262,showRat(CmdHdg),3); XDrawString(XD,XW,XG,DM+18,TL+280,showBip(CmdROT),7); #define MapCtrX 332 // window x-coordinate of the centre of the map #define MapCtrY 200 // window y-coordinate of the centre of the map #define WinLimX 482 // left window limit for waypoint names #define MNVW 100 // max number of visible waypoints allowed on map #define MaxX 150 // maximum x from centre of map #define MaxY 150 // maximum y from centre of map #define MapLft 182 // = MapCtrX - MaxX; #define MapRgt 482 // = MapCtrX + MaxX; #define MapTop 50 // = MapCtrY - MaxY; #define MapBot 350 // = MapCtrY + MaxY; int VWC = -1, // for index number of current waypoint vw = 0, // set total number of visible waypoints to zero mapx = 0, // pixel x-coordinate of current geographic feature mapy = 0, // pixel y-coordinate of current geographic feature MAPX[MNVW], // array for 'x' of all visible features MAPY[MNVW]; // array for 'y' of all visible features char *WN[MNVW]; // holds all the feature names for the current route Flag = 0; // 0:suppress display of an item; 1:permit display /* FOR EACH GEOGRAPHIC FEATURE ASSOCIATED WITH THE CURRENT WAYPOINT, IN- CLUDING ITSELF, COMPUTE ITS SCREEN COORDINATES FROM ITS RANGE & BEARING. NOTE: all geographic features associated with a given waypoint have been filtered by the function T0associatedFeatures() to be within 200km of that waypoint. This loop was last modified 08 July 2021. */ for(int k = 0; k < rgf; k++) { // new int g = gf[k]; // index number, within struct GF[], of current feature // get distance & bearing from aircraft of current geographic feature. VinChk(VinErr = VSGIP(AirLat,AirLng,GF[g].Lat,GF[g].Lng)); int inMapRange = 0; // initially assume it is not within Map Range if(VinDst == 0) { // if the aircraft is at this feature [waypoint] mapx = MapCtrX; // set 'int' x co-ordinate to map centre mapy = MapCtrY; // set 'int' y co-ordinate to map centre inMapRange = 1; // and return that it is in range } else { // else the 'g'th geographic feature is some double // distance away from the current waypoint d = VinDst * ms, // waypoint distance in map pixels b = FwdAzi - CmdHdg; // waypoint brg rel to map orientation mapx = rint(d * sin(b)); // rounded interger screen x-pixel mapy = rint(d * cos(b)); // rounded interger screen y-pixel if(fabs(mapx) <= 145 && // if this waypoint is within range fabs(mapy) <= 142) { // of the map display area: mapx += MapCtrX; // bias its co-ordinates to mapy = MapCtrY - mapy; // the centre of the map inMapRange = 1; // return that it is within map range } } if(inMapRange > 0) { // if feature within map window range WN[vw] = GF[g].Name; // get its name MAPX[vw] = mapx; // save its x & y screen coordinates MAPY[vw] = mapy; if(GF[g].wpi == cwp) // if geographic feature is current waypoint VWC = vw; // set index number of this waypoint vw++; // increment the number of visible waypoints } } // end of 'FOR EACH GEOGRAPHIC FEATURE ASSOCIATED WITH CURRENT WAYPOINT' int inRng = 1; // indicates that the waypoint is within radio range if(WptDst > WPR) // if it is beyond radio range inRng = 0; // indicate that it is not within radio range clearMapArea(); // clear map area // RE-DRAW THE MAP IN THE EVENT OF IT BEING UPDATED OR ECLIPSED for(int k = 0; k < vw; k++) { // FOR EACH VISIBLE GEOGRAPHIC FEATURE: mapx = MAPX[k]; // get its x screen coordinate and mapy = MAPY[k]; // its y screen coordinate /* Draw the white circle to represent the visible waypoint and annotate it with its geographic name. */ XSetForeground(XD, XG, WHITE); // white circle XDrawArc(XD,XW,XG,mapx - 5,mapy - 5,10,10,0,23040); char *S = WN[k]; // waypoint name int LS = strlen(S), // number of characters in waypoint name ls = LS * 6, // length [in pixels] of waypoint name PS = ls + mapx + 9; // window x position of end of name /* if this waypoint's name overshoots the right margin or if the route makes a left turn at this waypoint: */ if(PS > WinLimX || (RIGHT == 1 && k == VWC)) x = mapx - ls - 7; // shift it the name length + 7 pixels left else // else, if it wouldn't overshoot x = mapx + 9; // space it 9 pixels to right of the waypoint XDrawString(XD,XW,XG,x,mapy + 5,S,LS); if(k == VWC) { // if this is the current feature: if(inRng == 1) { // if this waypoint is within radio range: if(cwp > 0) // If this is not the first waypoint in the route T3guideArc(mapx,mapy); T3drawRads(mapx,mapy); } } } // end of 'FOR EACH VISIBLE GEOGRAPHIC FEATURE:' /* DRAW THE GPS/INERTIAL GUIDE BARS. Displays a yellow bar pointing from the aircraft to the waypoint it is currently approaching and a yellow bar point- ing from the aircraft back towards the waypoint it has just left. */ if(inRng == 0) { // if beyond radio range of current waypoint: double bf = 0, bb = 0; if(ToFlag == 1) { // if aircraft approaching current waypoint bb = MapFwd - CmdHdg; // brg to prev waypoint (behind aircraft) bf = FwdBrg - CmdHdg; // brg to this waypoint (ahead of aircraft) } else { // else if receding from current waypoint bb = MapFwd - CmdHdg; // brg to next waypoint (ahead of aircraft) bf = FwdBrg - CmdHdg; // brg to this waypoint (behind aircraft) } XSetForeground(XD, XG, YELLOW); XDrawLine(XD,XW,XG,MapCtrX,MapCtrY, // waypoint approach pointer MapCtrX + rint(100 * sin(bf)), MapCtrY - rint(100 * cos(bf))); XDrawLine(XD,XW,XG,MapCtrX,MapCtrY, // pointer to receding waypoint MapCtrX + rint(100 * sin(bb)), MapCtrY - rint(100 * cos(bb))); } // DISPLAY MAP'S CENTRAL CROSS-HAIRS [POSITION OF THE AIRCRAFT] XSetForeground(XD,XG,GREY); XDrawLine(XD,XW,XG,MapCtrX,MapCtrY-10,MapCtrX,MapCtrY+10); XDrawLine(XD,XW,XG,MapCtrX-10,MapCtrY,MapCtrX+10,MapCtrY); // SHOW THE LITTLE COMPASS CROSS AT THE BOTTOM RIGHT OF THE MAP double a = sin(CmdHdg), // sine and cosine of aircraft's heading b = cos(CmdHdg); x = MapCtrX + 120; // x coordinates of centre of compass y = MapCtrY + 120; // y coordinates of centre of compass // Draw the letter 'N' to indicate 'North'. XDrawString(XD,XW,XG,x - rint(23 * a) - 3,y - rint(23 * b) + 3,"N",1); // Draw the North-South and East-West lines of the compass. int cc = rint(15 * a), // vertical & horizontal off-sets of the dd = rint(15 * b); // points of the compass cross XDrawLine(XD,XW,XG,x-cc,y-dd,x+cc,y+dd); XDrawLine(XD,XW,XG,x+dd,y-cc,x-dd,y+cc); } /* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE MAP TAB. Called from 1 place each in T3show() and T3click(). */ void T3buts() { if(TB != 3) return; int h = LM; XSetForeground(XD,XG,DARK); // shade for buttons background for(int i = 0; i < 8; i++) { // shade the background area for each button XFillRectangle(XD,XW,XG,h,BY,53,BH); h += BW; } const char // annotate horizontal row of control buttons *A[] = {"GO","STOP","NEXT","PREV","RESET"," "," ","HELP"}; const int a[] = {21,15,15,15,12,15,15,15}, // horiz offsets for word starts b[] = { 2, 4, 4, 4, 5, 4, 4, 4}; // number of letters in each word h = LM; int v = BY + 15; XSetForeground(XD,XG,GREY); // colour for the lettering for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(T3BS & 1 << i) XSetForeground(XD,XG,WHITE); // colour for the STOP button else XSetForeground(XD,XG,GREY); // colour for the lettering XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]); h += BW; } } /* DISPLAY THE STATIC CONTENT OF THE MAP TAB. Called only from 1 place in showTab(). */ void T3show() { if(TB != 3) return; // Draw the map graticule: the corner and mid-edge marker lines. XSetForeground(XD, XG, GREY); XDrawLine(XD,XW,XG,181, 49,190, 49); // left top corner horizontal XDrawLine(XD,XW,XG,181, 50,181, 58); // left top corner vertical XDrawLine(XD,XW,XG,181,351,190,351); // left bottom corner horizontal XDrawLine(XD,XW,XG,181,342,181,350); // left bottom corner vertical XDrawLine(XD,XW,XG,474, 49,483, 49); // right top corner horizontal XDrawLine(XD,XW,XG,483, 50,483, 58); // right top corner vertical XDrawLine(XD,XW,XG,474,351,483,351); // right bottom corner horizontal XDrawLine(XD,XW,XG,483,342,483,351); // right bottom corner vertical XDrawLine(XD,XW,XG,173,200,180,200); // centre left horizontal XDrawLine(XD,XW,XG,181,191,181,209); // centre left vertical XDrawLine(XD,XW,XG,484,200,491,200); // centre right horizontal XDrawLine(XD,XW,XG,483,191,483,209); // centre right vertical XDrawLine(XD,XW,XG,323, 49,341, 49); // centre top horizontal XDrawLine(XD,XW,XG,332, 41,332, 48); // centre top vertical XDrawLine(XD,XW,XG,323,351,341,351); // centre bottom horizontal XDrawLine(XD,XW,XG,332,350,332,359); // centre bottom vertical clearMapArea(); // Draw the flight data annotations char *S[] = { "WAYPOINT","Name","Latitude","Longitude","Altitude", "Distance","Bearing","Radial","AIRCRAFT","Latitude", "Longitude","Altitude","Speed m/s","Req Hdg","Cmd Hdg","Cmd ROT" }; int v = TL; // base height of the top line of the text within a tab for(int i = 0; i < 16; i++) { // for the 16 waypoint & aircraft data lines if(i == 8) v += 10; // extra gap above 'AIRCRAFT' title if(i == 0 || i == 8) // if it's a title line XSetForeground(XD,XG,YELLOW); // menu titles in yellow else XSetForeground(XD,XG,GREY); // annotations in white XDrawString(XD,XW,XG,LM,v,S[i],strlen(S[i])); v += 18; // drop to next line } // ANNOTATE THE STOP BUTTON ACCORDING TO CURRENT FLIGHT STATE if(RunState) { // if the aircraft is currently in flight T3BS |= 1; // highlight the MAP tab's GO button T3BS &= ~2; // dim the MAP tab's STOP button T6BS |= 1; // highlight the INPUT tab's GO button T6BS &= ~2; // dim the INPUT tab's STOP button T7BS |= 1; // highlight the OUTPUT tab's GO button T7BS &= ~2; // dim the OUTPUT tab's STOP button } else { // else, the aircraft is NOT currently in flight, so T3BS |= 2; // highlight the MAP tab's STOP button T3BS &= ~1; // dim the MAP tab's GO button T6BS |= 2; // highlight the INPUT tab's STOP button T6BS &= ~1; // dim the INPUT tab's GO button T7BS |= 2; // highlight the OUTPUT tab's STOP button T7BS &= ~1; // dim the OUTPUT tab's GO button } T3buts(); // show control buttons for the MAP Tab if(Ready) // provided route & waypoint data is loaded and initialised T3updt(); // display the map } /* ONE OF THE MAP TAB'S CONTROL BUTTONS WAS PRESSED. Button numbers: 0:start, 1:stop, 2:next, 3:prev, 4:reset. Called from 2 places in T7CL() and from one place each in updtTrack() and MEH(). */ void T3click(int x) { switch(x) { // switch on button number case 0: // GO THE CURRENT FLIGHT if(RunState == 0 // provided a flight is not currently in progress && Ready) { // if the aircraft is not in flight T3BS |= 1; // highlight the GO button T3BS &= ~2; // dim the STOP button T6BS |= 1; // highlight the input tab's GO button T6BS &= ~2; // dim the input tab's STOP button T7BS |= 1; // highlight the output tab's GO button T7BS &= ~2; // dim the output tab's STOP button CM = 19; // message No. 19 "Flight in progress." MC = GREEN; // colour in which message is displayed CmdSpd = MinSpd; // set initial take-off speed AirBnk = 0; // zero the aircraft actual roll [bank] angle CmdBnk = 0; // zero the roll [bank] command AirAtt = 0; // zero the actual pitch CmdAtt = 0; // zero the pitch command RunState = 1; // set flight active [running] LN = 0; // reset output tab's line number T3updt(); // update the map to show this } break; case 1: // STOP THE CURRENT FLIGHT if(RunState) { // if the aircraft is currently in flight: T3BS |= 2; // highlight the STOP button T3BS &= ~1; // dim the GO button T6BS |= 2; // highlight the input tab's STOP button T6BS &= ~1; // dim the input tab's GO button T7BS |= 2; // highlight the output tab's STOP button T7BS &= ~1; // dim the output tab's GO button CM = 20; // message No. 20 "Flight paused." MC = YELLOW; // colour in which message is displayed RunState = 0; // stop the flight T3updt(); // update the map to show this } break; case 2: // MOVE AIRCRAFT TO THE NEXT WAYPOINT if(cwp < LWP) // provided aircraft is not already at cwp++; // the last waypoint, increment waypoint number RunState = 0; // stop the flight if it is running cgf = WP[cwp].cgf; // reference to waypoint data setAircraftAt(); // set aircraft to start of current route T3updt(); // update the map to show this break; case 3: //MOVE AIRCRAFT TO THE PREVIOUS WAYPOINT if(cwp > 0) // So provided the aircraft is not at the first cwp--; // waypoint, decrement the waypoint number RunState = 0; // stop the flight if it is running cgf = WP[cwp].cgf; // reference to waypoint data setAircraftAt(); // set aircraft to start of current route T3updt(); // update the map to show this break; case 4: // RESET AIRCRAFT TO BEGINNING OF CURRENT ROUTE cwp = 0; // set current waypoint as first in the route RunState = 0; // stop the flight if it is running cgf = WP[cwp].cgf; // reference to waypoint data setAircraftAt(); // set aircraft to start of current route T3updt(); // update the map to show this break; case 7: helpPage("navigation/nav.html#map"); } T3buts(); // re-display the buttons } // -------FUNCTIONS PERTAINING TO TAB 4 [ROLL & PITCH COMMAND INDICATOR]------- /* UPDATE THE COMMAND HEIGHT PROFILE GRAPH. Called only from 1 place each in T4plot() and T4show(). */ void T4graph() { if(TB == 4 && T4BS & 64) { // if in Tab 6 and the PLOT button is lit XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,LM+30,70,436,280); // clear the graph panel XSetForeground(XD,XG,0x553300); int j = 100; for(int i = 0; i < 6; i++) { // display brown horizontal graticule XDrawLine(XD,XW,XG,LM+30,j,LM+465,j); j += 50; } XSetForeground(XD,XG,GREEN); for(int i = 0; i < 436; i++) { // display all plots int y = T4P[i]; if(y > 280) y = 280; // still show a plot out of range heights if(y > 0) XDrawPoint(XD,XW,XG,LM + 30 + i,350 - T4P[i]); } } } /* DISPLAY THE NEXT DOT OF THE GRAPH. The graph starts from the left of the screen. When it reached the right side of the graph, the trace is pushed backwards each time to make way for the new plot. Called from the 1-second clock loop in main() irrespectively of whether or not the PLOT graph is on screen.*/ void T4plot() { int y = CmdHgt; // height of reference waypoint if(T4PC < 436) // if current plot is not at end of x-axis T4P[T4PC++] = y; // put the current plot in this position else { // otherwise for(int i = 0; i < 435; i++) T4P[i] = T4P[i+1]; // shift all plots one back in the array and T4P[435] = y; // put the new plot in the vacated last position } T4graph(); } /* DISPLAY THE ROLL AND PITCH INDICATOR BARS Attitude Command Indicator is a horizontal green bar that moves up and down from -150 pixels to + 150 pixels representing a real range -30 to +30 deg- rees. Each vertical pixel thus represents a fifth of a degree. The integer 'CmdAtt' runs from -150 to +150 in fifths of a degree. For presented values of 'CmdAtt' < -30 and > +30 degrees, the pitch bar stays at full scale but turns red. The Bank Command Indicator is a yellow bar that rotates around the centre of the blue indicator panel. The ends of the two arcs indicate a 45 degree bank command. Beyond a bank command angle of 1 radian, the bar turns red and there is an enforced upper bank command limit of 1.5 radians. Called from T4show() and updtTrack(). */ void T4updt() { AirBnk += 0.2 * (CmdBnk - AirBnk) * (CmdSpd / MaxSpd); AirAtt += 0.2 * (CmdAtt - AirAtt) * (CmdSpd / MaxSpd); if(TB != 4 || T4BS & 64) return; clearMapArea(); XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,110,50,60,300); // clear the figures panel XSetForeground(XD,XG,GREY); // graticule colour XDrawLine(XD,XW,XG,333,50,333,351); // vertical centre line XDrawLine(XD,XW,XG,185,201,480,201); // horizontal centre line XDrawArc(XD,XW,XG,182,50,301,301,-2880,5760); // right arc XDrawArc(XD,XW,XG,182,50,301,301, 8640,5760); // left arc // COMPUTE PITCH & ROLL COMMANDS AND APPLY IN-FLIGHT LIMITS TO THEM if(RunState == 1) { // if in flight [test buttons are ineffective] CmdBnk = CmdROT * -8.0; /* compute the Roll Command | These constants need to be adjusted to match the flight characteristics of the particular air vehicle concerned. | */ CmdAtt = CmdROC * 0.045; // compute the Pitch Command if(CmdAtt > +0.5) // In-flight command pitch limits CmdAtt = +0.5; // are plus or minus half a radian else if(CmdAtt < -0.5) CmdAtt = -0.5; } // INDICATOR'S ABSOLUTE MAXIMUM ROLL COMMAND LIMIT = 1.5 RADIANS if(CmdBnk > +1.5) CmdBnk = +1.5; // limit the rightwards roll else if(CmdBnk < -1.5) CmdBnk = -1.5; // limit the leftwards roll // INDICATOR'S MAXIMUM SAFE ROLL COMMAND LIMIT = 1 RADIAN int c = YELLOW; if(CmdBnk > 1 || CmdBnk < -1) c = RED; XSetForeground(XD,XG,c); // colour of indicator bars // DISPLAY THE CURRENT POSITION OF THE ROLL COMMAND BAR int b = 145 * sin(CmdBnk), // x-dimension of containing rectangle a = 145 * cos(CmdBnk); /* y-dimension of containing rectangle | half-diagonal of containing rectangle [a constant] x-coord of the centre of the graph | y-coord of the centre of the graph | | */ XDrawLine(XD,XW,XG,333 + a,201 + b,333 - a,201 - b); // DISPLAY THE AIRCRAFT'S CURRENT ACTUAL ROLL [ROLL] ANGLE if(T4BS & 2) { // provided the AUTO button is bright XSetForeground(XD,XG,0x00FFFF); b = 145 * sin(AirBnk), // x-dimension of containing rectangle a = 145 * cos(AirBnk); /* y-dimension of containing rectangle | half-diagonal of containing rectangle [a constant] x-coord of the centre of the graph | y-coord of the centre of the graph | | */ XDrawLine(XD,XW,XG,333 + a,201 + b,333 - a,201 - b); } // DISPLAY THE CURRENT POSITION OF THE PITCH COMMAND BAR a = CmdAtt * 4 * DPR; // pitch angle from degrees to pixels c = YELLOW; // default colour for pitch bar if(a > +149) { // positive excusion limit a = +149; c = RED; } else if(a < -149) { // negative excursion limit a = -149; c = RED; } a += 201; // add y-coordinate of graphical origin XSetForeground(XD,XG,c); // colour of pitch indicator bar XDrawLine(XD,XW,XG,222,a,444,a); // pitch indicator // DISPLAY THE POSITION OF THE ARICRAFT'S ACTUAL PITCH if(T4BS & 2) { // provided the AUTO button is bright a = 201 + AirAtt * 5 * DPR; // pitch angle from degrees to pixels XSetForeground(XD,XG,0x00FFFF); XDrawLine(XD,XW,XG,222,a,444,a); // pitch indicator } // DISPLAY THIS INFORMATION IN FIGURES ON THE LEFT OF THE INDICATOR int v = TL; XSetForeground(XD,XG,WHITE); // write all data items in white XDrawString(XD,XW,XG,LM+33,v,showName(GF[WP[cwp].cgf].Name),20); // Wpt Name v += IL; XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(WptDst),10); // Waypoint Dist. XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(CmdSpd),10); // Aircraft Speed v += IL; XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdBnk),7); // Roll Command XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdAtt),7); // Pitch Command v += IL; if(T4BS & 2) { XSetForeground(XD,XG,0x00FFFF); XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(AirBnk),7); // Actual Roll XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(AirAtt),7); // Actual Pitch } else { v += IL; v += IL; } v += IL; XSetForeground(XD,XG,WHITE); XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(CmdHgt),10); // Command Height XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(AirHgt),10); // Actual Height v += IL; XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdROT),7); // Rate of Turn XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdROC),7); // Rate of Climb } /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF ATAV TAB Called from only one place in T1show(). */ void T4buts() { if(TB != 4) return; // bail out if ROUTE tab not currently visible int h = LM; // set h to left side of first button XSetForeground(XD,XG,DARK); // shade for buttons background for(int i = 0; i < 8; i++) { // shade the background area for each button XFillRectangle(XD,XW,XG,h,BY,53,BH); h += BW; // move across to next button } char // annotate horizontal row of control buttons *A[] = {"MAN","AUTO","LEFT","RIGHT","UP","DOWN","PLOT","HELP"}; int a[] = {18,15,15,12,21,15,15,15}, // horiz offsets for word starts b[] = { 3, 4, 4, 5, 2, 4, 4, 4}; // number of letters in each word h = LM; int v = BY + 15; // x & y coords of button block for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(T4BS & 1 << i) XSetForeground(XD,XG,WHITE); // colour for the lettering else XSetForeground(XD,XG,GREY); // colour for the lettering XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]); h += BW; // move across [rightwards] to next button } } /* DISPLAY AND UPDATE THE WAYPOINT NAME ON THE HEIGHT GRAPH Called from 1 place near the beginning of updtTrack() and also from 1 place in T4show(). */ void T4name() { // show name of current waypoint on PLOT height graph if(TB == 4 && T4BS & 64) { // if in Tab 6 and the PLOT button is lit XSetForeground(XD,XG,BLACK); // clear the waypoint name field XFillRectangle(XD,XW,XG,LM+346,45,120,12); XSetForeground(XD,XG,WHITE); // display the name of the next waypoint XDrawString(XD,XW,XG,LM+346,55,showName(GF[WP[cwp].cgf].Name),20); } } /* DISPLAY THE STATIC CONTENT OF THE PITCH & ROLL COMMANDS INDICATOR TAB OR THE COMMAND HEIGHT GRAPH TAB. Called from only 1 place in showTab(). */ void T4show() { char *S[7] = {"300","250","200","150","100","050","000"}; clearTabArea(); if(T4BS & 64) { // if the PLOT button is bright // DISPLAY THE STATIC CONTENT OF THE COMMAND HEIGHT GRAPH XSetForeground(XD,XG,GREY); XDrawLine(XD,XW,XG,LM+27,50,LM+27,350); // draw the height scale int j = 50; for(int i = 0; i < 31; i++) { XDrawLine(XD,XW,XG,LM+22,j,LM+27,j); j += 10; } j = 55; for(int i = 0; i < 7; i++) { XDrawString(XD,XW,XG,LM,j,S[i],3); j += 50; } XDrawString(XD,XW,XG,LM+33,55, "CMD HGT [metres above sea level] Waypoint:",44); T4name(); T4graph(); } else { // else the PLOT button is dim, so: // DISPLAY THE STATIC CONTENT OF THE ATTITUDE [PITCH & ROLL] INDICATOR int v = TL; XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,LM,v,"WAYPOINT",8); v += IL; XDrawString(XD,XW,XG,LM,v+=IL,"DISTANCE:",9); XDrawString(XD,XW,XG,LM,v+=IL,"SPEED:",6); v += IL; XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,LM,v+=IL,"ROLL COMMAND:",13); XDrawString(XD,XW,XG,LM,v+=IL,"PITCH COMMAND:",14); v += IL; XSetForeground(XD,XG,0x00FFFF); XDrawString(XD,XW,XG,LM,v+=IL,"ROLL ACTUAL:",12); XDrawString(XD,XW,XG,LM,v+=IL,"PITCH ACTUAL:",13); v += IL; XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,LM,v+=IL,"COMMAND HEIGHT:",15); XDrawString(XD,XW,XG,LM,v+=IL,"ACTUAL HEIGHT:",14); v += IL; XDrawString(XD,XW,XG,LM,v+=IL,"RATE OF TURN:",13); XDrawString(XD,XW,XG,LM,v+=IL,"RATE OF CLIMB:",14); } T4buts(); // re-display the control buttons at the bottom T4updt(); // initialise the Tab 4's variable content } /* HANDLES MOUSE CLICKS ON TAB 4'S BOTTOM BUTTONS. Called from only one place in MEH [the Mouse Event Handler]. */ void T4click(int x) { // switch(x) { // switch on button number case 0: // the MAN button was clicked if(!(T4BS & 1)) { // if the MAN button is off T4BS |= 1; // switch it on T4BS &= ~2; // switch off the AUTO button } break; case 1: // the AUTO button was clicked if(!(T4BS & 2)) { // if the AUTO button is off T4BS |= 2; // switch it on T4BS &= ~1; // switch off the MAN button } break; } if(RunState == 0) { // provided the aitcraft is not currently in flight switch(x) { // switch on button number case 2: CmdBnk += RPD; break; // bank left case 3: CmdBnk -= RPD; break; // bank right case 4: CmdAtt += RPD; break; // nose up case 5: CmdAtt -= RPD; break; // nose down } } switch(x) { case 6: // the PLOT button was pressed if(!(T4BS & 64)) // if the PLOT button is off T4BS |= 64; // switch it on else T4BS &= ~64; // switch it off break; case 7: helpPage("navigation/nav.html#att"); return; } T4show(); // re-display the tab content } // --------------------FUNCTIONS PERTAINING TO TAB 5--------------------------- // UPDATE THE AZIMUTH COMMAND DISPLAY. Called only from 1 place in main(). void T5updt() { AirHdg += 0.02 * (CmdHdg - AirHdg) * (CmdSpd / MaxSpd); if(TB != 5) return; // the map only appears on Tab 0 clearMapArea(); XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,110,50,60,180); // clear the figures panel XSetForeground(XD,XG,GREY); // graticule colour XDrawArc(XD,XW,XG,182,50,301,301,0,23040); // compass circle XDrawLine(XD,XW,XG,334,50,334,351); // vertical centre line XDrawLine(XD,XW,XG,185,202,480,202); // horizontal centre line // DISPLAY THE CURRENT POSITION OF THE HEADING COMMAND BAR XSetForeground(XD,XG,YELLOW); int a,b; double c = CmdHdg - AirHdg; a = 145 * sin(c); // x-dimension of containing rectangle b = 145 * cos(c); /* y-dimension of containing rectangle | half-diagonal of containing rectangle [a constant] x-coord of the centre of the graph | y-coord of the centre of the graph | | */ XDrawLine(XD,XW,XG,334 - a/3,202 + b/3,334 + a,202 - b); // DISPLAY THE CURRENT POSITION OF THE REQUIRED HEADING BAR XSetForeground(XD,XG,0x00FFFF); c = ReqHdg - AirHdg; a = 145 * sin(c); // x-dimension of containing rectangle b = 145 * cos(c); /* y-dimension of containing rectangle | half-diagonal of containing rectangle [a constant] x-coord of the centre of the graph | y-coord of the centre of the graph | | */ XDrawLine(XD,XW,XG,334 - a/3,202 + b/3,334 + a,202 - b); // DISPLAY THE NORTH POINTER XSetForeground(XD,XG,GREEN); a = 100 * sin(-AirHdg); // x-dimension of containing rectangle b = 100 * cos(-AirHdg); /* y-dimension of containing rectangle | half-diagonal of containing rectangle [a constant] x-coord of the centre of the graph | y-coord of the centre of the graph | | */ XDrawLine(XD,XW,XG,334 - a,202 + b,334 + a,202 - b); a *= 1.1; b *= 1.1; XDrawString(XD,XW,XG,332 + a,206 - b,"N",1); // N for North XDrawString(XD,XW,XG,332 - a,206 + b,"S",1); // S for South a = 100 * sin(HalfPi-AirHdg); // x-dimension of containing rectangle b = 100 * cos(HalfPi-AirHdg); /* y-dimension of containing rectangle | half-diagonal of containing rectangle [a constant] x-coord of the centre of the graph | y-coord of the centre of the graph | | */ XDrawLine(XD,XW,XG,334 - a,202 + b,334 + a,202 - b); a *= 1.1; b *= 1.1; XDrawString(XD,XW,XG,332 + a,206 - b,"E",1); // N for North XDrawString(XD,XW,XG,332 - a,206 + b,"W",1); // N for North int v = TL; XSetForeground(XD,XG,WHITE); // write all data items in white XDrawString(XD,XW,XG,LM+33,v,showName(GF[WP[cwp].cgf].Name),20); // Wpt Name v += IL; XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(WptDst),10); // Waypoint Dist. XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(CmdSpd),10); // Aircraft Speed v += IL; XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdROT),7); // Rate of Turn v += IL; XSetForeground(XD,XG,0x00FFFF); XDrawString(XD,XW,XG,LM+128,v+=IL,showRat(ReqHdg),3); // Required Heading XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,LM+128,v+=IL,showRat(CmdHdg),3); // Heading Command XSetForeground(XD,XG,WHITE); XDrawString(XD,XW,XG,LM+128,v+=IL,showRat(AirHdg),3); // Aircraft Speed } /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF AZImuth TAB Called from only one place in T5show(). */ void T5buts() { if(TB != 5) return; // bail out if ROUTE tab not currently visible int h = LM; // set h to left side of first button XSetForeground(XD,XG,DARK); // shade for buttons background // shade the background area for each button for(int i = 0; i < 8; i++) { XFillRectangle(XD,XW,XG,h,BY,53,BH); h += BW; // move across to next button } char // annotate horizontal row of control buttons *A[] = {"","","","","","","ATAV","HELP"}; int a[] = {0,0,0,0,0,0,15,15}, // horiz offsets for word starts b[] = {0,0,0,0,0,0, 4, 4}; // number of letters in each word h = LM; int v = BY + 15; // x & y coords of button block XSetForeground(XD,XG,GREY); // colour for the lettering for(int i = 0; i < 8; i++) { // for each of the 8 buttons: XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]); h += BW; // move across [rightwards] to next button } } /* DISPLAY THE STATIC CONTENT OF THE AZIMUTH COMMANDS INDICATOR TAB. Not implemented in this demonstration version of the Global Navigator II. Called from only 1 place in showTab(). */ void T5show() { int v = TL; XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,LM,v,"WAYPOINT",8); v += IL; XDrawString(XD,XW,XG,LM,v+=IL,"DISTANCE:",9); XDrawString(XD,XW,XG,LM,v+=IL,"SPEED:",6); v += IL; XDrawString(XD,XW,XG,LM,v+=IL,"RATE-OF-TURN:",13); v += IL; XSetForeground(XD,XG,0x00FFFF); XDrawString(XD,XW,XG,LM,v+=IL,"REQD HDG:",9); XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,LM,v+=IL,"CMND HDG:",9); XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,LM,v+=IL,"REAL HDG:",9); T5buts(); T5updt(); } /* THE AZImuth TAB'S HELP BUTTON WAS PRESSED. Called from only one place in MEH(). */ void T5click(int x) { switch(x) { // switch on button number case 7: helpPage("navigation/nav.html#azi"); } } // --------------FUNCTIONS PERTAINING TO TAB 6 [COMMAND INPUTS]-------------- /* DISPLAYS THE INPUTS FOR LATITUDE, LONGITUDE & HEIGHT. Starts filling the panel from the top. Once the panel is full, shunts all lines upwards one place to make room for each new command line as it arrives. Called only from 1 place each in updtTrack() and T6show(). */ void T6updt() { // INPUT TAB int v = TL + 20, // set y-coord of first line of output M = 16, // default panel-line number of the most recent input Q = NL - 16, // line number bias for shunted lines P = 1; // shunt flag is set by default if(TB == 6) { // clear panel if this tab is currently visible XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,LM,TL+6,466,288); } if(NL < 17) { // if the panel's display area is not yet full M = NL; // loop only as far as the most recent input line Q = 0; // line number is simply the panel's line index P = 0; // set the lines shunt-up flag to 'don't shunt' } for(int i = 0; i < M; i++) { // for each visible input line if(P) // if the shunt flag is set for(int j = 0; j < 71; j++) // for each character of input line UO[i][j] = UO[i + 1][j]; // shift it up to previous line position XSetForeground(XD,XG,GREY); // colour for past inputs if(i == M - 1) { // if this is the latest input XSetForeground(XD,XG,WHITE); // set colour for latest input char *S = showInt(i+Q,5); // line number as 5-char string sprintf(UO[i],"%21.18lf %22.15lf %26.19lf ",AirLat,AirLng,AirHgt); } if(TB == 6) { // if this tab is currently visible XDrawString(XD,XW,XG,LM,v,showInt(i+Q,5),5); // show line number XDrawString(XD,XW,XG,LM+36,v,UO[i],71); // show commands } v += 17; // drop down to next line } if(RunState) // provided the flight is in progress NL++; // advance command line number ready for next pass } /* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE INPUT TAB. Called only from 1 place in T6show(). */ void T6buts() { int h = LM; // x-coordinate of the left side of a button XSetForeground(XD,XG,DARK); // shade for buttons background // shade the background area for each button for(int i = 0; i < 8; i++) { XFillRectangle(XD,XW,XG,h,BY,53,BH); h += BW; } const char // annotate horizontal row of control buttons *A[] = {"GO","STOP","LIVE","SIMU",""," "," ","HELP"}; const int a[] = {21,15,15,15,0,0,0,15}, // horiz offsets for word starts b[] = { 2, 4, 4, 4,0,0,0, 4}; // number of letters in each word h = LM; int v = BY + 15; XSetForeground(XD,XG,GREY); // colour for the lettering for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(T6BS & 1 << i) { if(i == 2) XSetForeground(XD,XG,RED); // LIVE button not implemented else XSetForeground(XD,XG,WHITE); // colour for the button } else XSetForeground(XD,XG,GREY); // colour for the lettering XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]); h += BW; } } /* SETS UP THE HEADINGS AND BUTTONS FOR THE INPUT TAB. Re-displays current command lines for a flight that is still active. Called only from 1 place in showTab(). */ void T6show() { if(TB != 6) return; XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,LM+3,TL,"LINE",4); XDrawString(XD,XW,XG,LM+42,TL,"LATITUDE (IN RADIANS)",21); XDrawString(XD,XW,XG,LM+192,TL,"LONGITUDE (IN RADIANS)",22); XDrawString(XD,XW,XG,LM+348,TL,"HEIGHT (IN METRES)",18); T6buts(); T6updt(); } /* INPUT TAB'S BUTTONS CONTROL LOGIC [Tab4CL]: The first 2 buttons are mutually exclusive. The next 2 buttons second 2 are also mutually ex- clusive. The states of the INPUT TAB'S control buttons are kept in the variable T6BS [Tab 4 Button States] as the states of the lower 8 bits, the least significant bit representing the state of the first button [GO]. Called from 4 places in T7click(). T6BS: 000001 [0X01] = GO on [bits going from right to left repres- 000010 [0X02] = STOP on ent buttons going from left to right] 000100 [0X04] = LIVE on 001000 [0X08] = SIMU on */ void Tab4CL(int x) { // x = button number from left to right int y = 1 << x; // set the bit for button 'x' if(!(T6BS & y)) { // if button 'x' is dim switch(x) { // switch on button number case 0: // if the GO button is dim T3click(0); // start the current flight break; case 1: // if the STOP button is dim T3click(1); // stop the current flight break; case 2: // if the LIVE button is dim InFlg = 0; // should set to 1 [LIVE] but this isn't implemented T6BS &= ~8; // dim the SIMU button break; case 3: // if the SIMU button is bright InFlg = 0; // set for simulated input T6BS &= ~4; // dim the LIVE button break; } T6BS |= y; // brighten button 'x' } T6buts(); // re-display the buttons } /* ONE OF THE ROW OF 5 BUTTONS AT THE BOTTOM OF THE INPUT TAB WAS CLICKED The values in the if() statements are the left and right edges of the buttons in pixels. Called from only one place in MEH() case 2: case 4: */ void T6click(int x) { if(mX > 16 && mX < 70) // if clicked on 'GO' button Tab4CL(0); // start the currently selected flight else if(mX > 75 && mX < 129) // if clicked on 'STOP' button Tab4CL(1); // stop the currently selected flight else if(mX > 134 && mX < 188) // if LIVE button was clicked Tab4CL(2); // enable/disable live INPUT else if(mX > 193 && mX < 247) // if the SIMU button was clicked Tab4CL(3); // enable/disable SIMUlated input else if(mX > 429 && mX < 483) // if the HELP button was clicked helpPage("navigation/nav.html#input"); } // --------------FUNCTIONS PERTAINING TO TAB 7 [COMMAND OUTPUTS]-------------- /* DISPLAYS THE ACTUAL COMMAND OUTPUTS FOR HEADING, HEIGHT AND SPEED. Starts filling the panel from the top. Once the panel is full, shunts all lines upwards 1 place to make room for each new command line as it arrives. Called only from 1 place each in updtTrack() & T7show(). */ void T7updt() { // OUTPUT TAB int v = TL + 20, // set y-coord of first line of output M = 16, // default panel-line number of the most recent command Q = LN - 16, // line number bias for shunted lines P = 1; // shunt flag is set by default if(TB == 7) { // clear panel if this tab is currently visible XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,LM,TL+6,466,288); } if(LN < 17) { // if the panel's display area is not yet full M = LN; // loop only as far as the most recent command line Q = 0; // line number is simply the panel's line index P = 0; // set the lines shunt-up flag to 'don't shunt' } for(int i = 0; i < M; i++) { // for each visible command line if(P) // if the shunt flag is set for(int j = 0; j < 71; j++) // for each character of command line OU[i][j] = OU[i + 1][j]; // shift it up to previous line position XSetForeground(XD,XG,GREY); // colour for past commands if(i == M - 1) { // if this is the latest command XSetForeground(XD,XG,WHITE); // set colour for latest command char *S = showInt(i+Q,5); // line number as 5-char string sprintf(OU[i],"%21.18lf %22.15lf %26.19lf ",CmdHdg,CmdHgt,CmdSpd); if(FileOut) { // if file output is active for(int j = 0; j < 5; j++) // for each character of line number fputc(S[j],FO); // put the 5 characters to the file fputc(' ',FO); // space character [separator] for(int j = 0; j < 71; j++) // for each character of command line fputc(OU[i][j],FO); // output it to the file 'output.txt' fputc('\n',FO); // terminate the line with a 'new line' } if(StdOut) { // if output to terminal is active for(int j = 0; j < 5; j++) // for each character of line number fputc(S[j],stdout); // put the 5 characters to the file fputc(' ',stdout); // space character [separator] for(int j = 0; j < 71; j++) // for each character of command line fputc(OU[i][j],stdout); // output it to the file 'output.txt' fputc('\n',stdout); // terminate the line with a 'new line' } } if(TB == 7) { // if this tab is currently visible XDrawString(XD,XW,XG,LM,v,showInt(i+Q,5),5); // show line number XDrawString(XD,XW,XG,LM+36,v,OU[i],71); // show commands } v += 17; // drop down to next line } if(RunState) // if flight is in progress LN++; // advance command line number ready for next pass } /* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE OUTPUT TAB. Called from only 1 place each in T7show() and T7CL(). */ void T7buts() { int h = LM; // x-coordinate of the left side of a button XSetForeground(XD,XG,DARK); // shade for buttons background // shade the background area of each button for(int i = 0; i < 8; i++) { XFillRectangle(XD,XW,XG,h,BY,53,BH); h += BW; } const char // annotate horizontal row of control buttons *A[] = {"GO","STOP","STDOUT","FILE"," "," "," ","HELP"}; const int a[] = {21,15, 9,15,15,15,15,15}, // horiz offsets for word starts b[] = { 2, 4, 6, 4, 4, 4, 4, 4}; // number of letters in each word h = LM; int v = BY + 15; XSetForeground(XD,XG,GREY); // colour for the lettering for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(T7BS & 1 << i) XSetForeground(XD,XG,WHITE); // colour for the STOP button else XSetForeground(XD,XG,GREY); // colour for the lettering XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]); h += BW; } } /* SETS UP THE HEADINGS AND BUTTONS FOR THE OUTPUT TAB. Re-displays current command lines for a flight that is still active. Called only from 1 place in showTab(). */ void T7show() { if(TB != 7) return; XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,LM+3,TL,"LINE",4); XDrawString(XD,XW,XG,LM+36,TL,"COMMAND HEADING(RADS)",21); XDrawString(XD,XW,XG,LM+174,TL,"COMMAND HEIGHT(METRES)",22); XDrawString(XD,XW,XG,LM+318,TL,"COMMAND SPEED(METRES/SEC)",25); T7buts(); T7updt(); } /* OUTPUT TAB'S BUTTONS CONTROL LOGIC [T7CL]: The first 2 buttons are mutually exclusive. The next 2 buttons operate independently; each is respectively set to its 'on' state when clicked. The states of the OUTPUT TAB'S control buttons are kept in the variable T7BS [Tab 5 Button States] as the states of the lower 8 bits, the least significant bit representing the state of the first button [GO]. Called from 4 places in T7click(). T7BS: 000001 [0X01] = GO on [bits going from right to left repres- 000010 [0X02] = STOP on ent buttons going from left to right] 000100 [0X04] = STDOUT on 001000 [0X08] = FILE on */ void T7CL(int x) { // x = button number from left to right int y = 1 << x; // set the bit for button 'x' if(T7BS & y) { // if button 'x' is bright switch(x) { // switch on button number case 0: return; // if the GO button is already bright, do nothing case 1: return; // if the STOP button is already bright, do nothing case 2: // if the STDOUT button is bright StdOut = 0; // disable output to 'stdout' break; case 3: // if FILE button is bright FileOut = 0; // disable output to 'output.txt' fclose(FO); // close the file 'output.txt' break; } T7BS &= ~y; // dim button 'x' } else { // else, if button 'x' is dim switch(x) { // switch on button number case 0: // if the GO button is dim T3click(0); // start the current flight break; case 1: // if the STOP button is dim T3click(1); // stop the current flight break; case 2: // if the STDOUT button is dim StdOut = 1; // enable output to 'stdout' break; case 3: // if the FILE button is bright FO = fopen("output.txt","wb"); FileOut = 1; // enable output to 'output.txt' break; } T7BS |= y; // brighten button 'x' } T7buts(); // re-display the buttons } /* ONE OF THE ROW OF 5 BUTTONS AT THE BOTTOM OF THE OUTPUT TAB WAS CLICKED The values in the if() statements are the left and right edges of the buttons in pixels. Called from only one place in MEH() case 2: case 5: */ void T7click(int x) { if(mX > 16 && mX < 70) // if clicked on 'GO' button T7CL(0); // start the currently selected flight else if(mX > 75 && mX < 129) // if clicked on 'STOP' button T7CL(1); // stop the currently selected flight else if(mX > 134 && mX < 188) // if STDOUT button was clicked T7CL(2); // enable/disable OUTPUT to 'stdout' else if(mX > 193 && mX < 247) // if the FILE button was clicked T7CL(3); // enable/disable OUTPUT to 'output.txt' else if(mX > 429 && mX < 483) // if the HELP button was clicked helpPage("navigation/nav.html#output"); } //-------------------FUNCTIONS CALLED DIRECTLY BY MAIN()--------------------- /* DISPLAY ALL THE TITLES, BUTTONS AND DEFAULT IN-FILL FOR CURRENT TAB which is specified by the numeric value of the global variable 'TB': 1=ADDRESS Tab, 2=PROFILE Tab, 3=DIARY Tab etc.. Called from only 1 place each in main() and MEH(). */ void showTab() { XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,24,55,453,380); // clear Tab area const char *BA[8] = { "ROUTE","WAYPTS","GEOID","MAP","ATT","AZI","INPUT","OUTPUT" }; const int NC[8] = { 5,6, 5, 3, 3, 3, 5,6}, // number of chars in each annotation NB[8] = {12,9,12,18,18,18,12,9}; // x-bias for each annotation XSetForeground(XD,XG, DARK); // shade button background int h = LM; for(int i = 0; i < 8; i++) { // draw each button XFillRectangle(XD,XW,XG,h,17,53,21); h += BW; } h = LM; for(int i = 0; i < 8; i++) { // annotate each button if(TB == i) XSetForeground(XD,XG,WHITE); else XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,h + *(NB +i),32,*(BA + i),*(NC + i)); h += BW; } clearTabArea(); switch(TB) { // switch on tab number case 0: T0show(); break; // paint the ROUTES tab case 1: T1show(); break; // paint the WAYPTS tab case 2: T2show(); break; // paint the GEOID tab case 3: T3show(); break; // paint the MAP tab case 4: T4show(); break; // paint the ATTITUDE tab case 5: T5show(); break; // paint the AZIMUTH tab case 6: T6show(); break; // paint the INTPUT tab case 7: T7show(); break; // paint the OUTPUT tab } } /* ADVANCE THE AIRCRAFT ALONG THE CURRENTLY SELECTED ROUTE, UPDATING ALL ENCOUNTER VARIABLES. This function is called only from main() run loop. */ void updtTrack() { double dW = 0; // distance the aircraft is blown by the wind /* If receding from current waypoint | AND more than half way to the next one | | | */ if(ToFlag == 0 && WptDst > NHD) { if(cwp < LWP) { // if not last waypoint in route [destination] cwp++; // increment to the next waypoint in the route initEncounter(); // initialise variables for waypoint changeover T1show(); // to update the highlighting of waypoint name T4name(); // update the name of the current waypoint on graph ToFlag = 1; // show that a waypoint changeover has occurred. ToFrom(); // update the to/from status for command height } else { // Else aircraft has reached its destination: T3click(1); // so hit the STOP button showMsg(15,GREEN); // display the "Reached Destination" message CmdSpd = 0; // stop the aircraft T3updt(); // show the final speed etc. return; // end-of-flight exit } } // Preserve last time's waypoint distance for PreDst = WptDst; // comparison after distance update [see below]. /* If this program is running in a pipe, that is, it was started with the command 'nav -io', the 'InFlg' is set to '1'. This causes the program here to read in the actual latitudes and longitudes of the aircraft as supplied by such sources as GPS, an inertial platform or ground-based radio aids. Latitude & longitude are expected as a single line of input comprising a pair of E-format strings of 25 characters each, separated by a space char- acter: 9.0553499357681432880e-01 3.7815467126561474152e-03 representing the latitude & longitude of the aircraft in radians. */ if(InFlg) { // if Lat/Lng from stdin is active char *S = NULL; // pointer to getline()'s input string size_t L = 0; // length of getline()'s input string getline(&S, &L, stdin); // get lat/lng input line from stdin double x = atof(strtok(S, " ")), // latitude from stdin y = atof(strtok(NULL, " ")); // longitude from stdin free(S); // de-alloc memory used for input string /* if latitude between -90° and + 90° | | AND longitude between -180° and +180° | | | | | */ if(x > -HalfPi && x < HalfPi && y > -pi && y < pi) { /* Use the Vinceny Inverse function to get the distance and bearing of the real position of the aircraft from the point of view of where it was predicted to be at the end of the last pass through this function. */ VinChk(VSGIP(AirLat,AirLng,x,y)); double X = dW * sin(WndBrg) + VinDst * sin(FwdAzi), // latitudinal error Y = dW * cos(WndBrg) + VinDst * cos(FwdAzi); // longitudinal error /* It is assumed that the error is due to displacement of the aircraft by the wind. The corrected distance blown by the wind since last time through is therefore: D = sqrt(X * X + Y * Y) - see wind.png */ WndSpd = sqrt(X * X + Y * Y) * MPR / ET; // corrected wind speed m/s WndBrg = RatAng(HalfPi - atan2(Y,X)); // corrected wind bearing /* Compute the wind offset: see diagram ReqOff.png NOTE: the atan2() function takes care of all the +/- signs for the 4 quadrants. */ ReqOff = atan2(WndSpd * cos(WndBrg) + CmdSpd * cos(CmdHdg), // 'Y' WndSpd * sin(WndBrg) + CmdSpd * sin(CmdHdg)) // 'X' - CmdHdg; // last time's command heading AirLat = x; // use aircraft's real-world latitude & AirLng = y; // longitude for the rest of this pass. } } // COMPUTE THE DISTANCE & BEARINGS FOR THE CURRENT WAYPOINT i = WP[cwp].cgf; // Geographic Feature index number of current waypoint VinChk(VSGIP(AirLat,AirLng,GF[i].Lat,GF[i].Lng)); WptDst = VinDst; // distance from aircraft to current waypoint FwdBrg = FwdAzi; // bearing from aircraft to current waypoint BakBrg = BakAzi; // bearing of aircraft from current waypoint double // ESTABLISH THE CURRENT TO/FROM SITUATION dDst = WptDst - PreDst; // distance travelled since last pass if(ToFlag == 0) { // if travelling FROM the current waypoint if(dDst < 0) { // if the distance travelled is negative ToFlag = 1; // aircraft has just started to travel TO ToFrom(); // the next waypoint } } else // else if(dDst > 0) { // if the distance travelled is positive ToFlag = 0; // aircraft has just started to travel FROM ToFrom(); // the current waypoint. } if(cwp == 0 && WptDst < 1000) // Don't begin turn onto outbound radial goto LABEL; // until 1000 metres after take-off. // [REF:OCEANIC] COMPUTE AIRCRAFT'S NEW REQUIRED HEADING if(WptDst >= WPR) { // if aircraft beyond waypoint's range it is if(ToFlag == 1) { // transoceanic. Please see diagram transTo.png i = WP[pwp].cgf; // index number of previous Geographic Feature VinChk(VSGIP(AirLat,AirLng,GF[i].Lat,GF[i].Lng)); /* The required heading is | twice the bearing of current waypoint from the aircraft | | | */ ReqHdg = FwdBrg + FwdBrg - FwdAzi - pi; /* | | minus the bearing from the backwards-extended line of the previous waypoint from aircraft */ MapFwd = FwdAzi; // Bearing of previous waypoint from aircraft } // when current waypoint is ahead of aircraft. else { // Please see diagram transFrom.png i = WP[nwp].cgf; // index number of next Geographic Feature VinChk(VSGIP(AirLat,AirLng,GF[i].Lat,GF[i].Lng)); ReqHdg = FwdAzi // The required heading is twice the bearing + FwdAzi // of next waypoint from aircraft, minus the - FwdBrg // bearing from the backwards-extended line to - pi; // the current waypoint from the aircraft. MapFwd = FwdAzi; // Bearing of next waypoint from aircraft } // when current waypoint is behind aircraft. } /* [REF:TO/FROM] NOTE: the functions required to recover from severe dis- placements from the established route, in the following TO and FROM situa- tions, are not included with this version. The aircraft must always be with- in the limits of radial capture, which are, nonetheless, quite generous. if this waypoint is the route origin | OR the aircraft has not yet reached | | the waypoint's turning arc | | */ else if(cwp == 0 || WptDst > TanDst) { if(ToFlag == 1) { // TO: please see diagram to.png & nav.html double RadDev = BipAng(BakBrg - InRad), // compute the radial deviation a = RadDev; if (a < 0) // create a positive-only version a = -a; // of the radial deviation /* This formula is safe only where the aircraft is on a back bearing of less than 90° from the inbound radial. If back bearing is beyond 90°, simply leave ReqHdg with the value it had last time through. */ if(a < HalfPi) ReqHdg = FwdBrg + 0.49 * RadDev; /* else { Additional code could be placed here for handling extremely abnormal situations where the angular deviation of the aircraft from the in- bound radial is greater than 0.49(π/2) [88°]. In this situation, the best strategy is to try immediately to get a lock onto the next way- point along the route and so on until the deviation of the aircraft from the inbound radial is less than 0.49(π/2) radians [88°]. } */ } else { // FROM: please see diagram from.png and the text in nav.html double // RadDev is θ in the diagram RadDev = BipAng(BakBrg - OutRad), // compute the radial deviation a = RadDev; if (a < 0) // make a positive-only version a = -a; // of the radial deviation /* This formula is safe only where the aircraft is on a forward bearing of up to 3/2 radians [86°] from the outbound radial. Notice the non- linear Radial Deviation compensation multiplier. If beyond 3/2 radians [86°] simply leave ReqHdg with the value it had last time through. */ if(a < 3) ReqHdg = OutRad - (1 - a / 6) * RadDev; /* else { Additional code could be placed here for handling extremely abnormal situations where the angular deviation of the aircraft from the out- bound radial can actually reach +/- π radians [+/- 180°]. In this sit- uation, the best strategy is to try immediately to get a lock onto the next waypoint along the route and so on until the aircraft's deviation from the INbound radial thereof is < 0.49(π/2) radians [88°]. } */ } } /* [REF:TURN] Else the aircraft is on the turning circle arc: so first get the distance & bearing of the centre of the turning circle arc. */ else { VinChk(VSGIP(AirLat,AirLng,CtrLat,CtrLng)); double dHdg; if(VinDst > TrnRad) // if aircraft outside turning circle: dHdg = asin(TrnRad/VinDst); // TrnRad & VinDst are always +ve so // dHdg always lies between 0 & π/2. // please see diagrams turn1.png else if(VinDst < TrnRad) // else if aircraft is inside the turning dHdg = HalfPi; // circle: please see diagram turn2.png if(RIGHT == 1) // dHdg runs anticlockwise for a right turn dHdg = -dHdg; ReqHdg = FwdAzi + dHdg; // FwdAzi is bearing of the centre of the } // turning circle arc from the aircraft. ReqHdg = RatAng(ReqHdg); // Express ReqHdg in range 0 to 2π. // [REF:DAMP] DAMP THE CHANGE IN THE REQUIRED HEADING CmdROT = 0.2 * BipAng(ReqHdg - CmdHdg); // heading error #define MaxErr 0.052359878 // 3° as a fraction of a radian if(CmdROT > +MaxErr) // limit the heading error CmdROT = +MaxErr; // to a prescribed maximum of +3° if(CmdROT < -MaxErr) // limit the heading error CmdROT = -MaxErr; // to a prescribed minimum of -3° CmdHdg += CmdROT * ET; // limited new command heading if(InFlg == 1) // if receiving live lat/lng input CmdHdg -= ReqOff; // compensate for heading error due to wind LABEL: // to skip turn onto outbound radial until 1000 metres after take-off // COMPUTE THE AIRCRAFT'S REQUIRED AIR SPEED #define PasSpd 150 // 150 m/s 292 knots 540 kph 336 mph if(ORD == 2 && // if this is the destination waypoint ToFlag == 0) { // AND aircraft is beyond final waypoint Landing = 1; // used by the height channel ToFrom(); double D = LndDst - WptDst; // compute distance to touch-down if(D > 0) // if there is still some distance to fly ReqSpd = MinSpd // required speed is the touch-down speed + PasSpd / D; // + waypoint over-fly speed / distance to fly else { // Else, if touch-down has occurred ReqSpd = 0; // set required speed to zero CmdSpd = 0; // set command speed to zero } } else { // else, aircraft is not landing, so if(WptDst < 2000) // if aircraft is within 2 km of waypoint, set ReqSpd = PasSpd; // required speed to waypoint over-fly speed else if(WptDst > WPR) // if aircraft beyond waypoint's radio range ReqSpd = MaxSpd; // set required speed to maximum speed else // if between 2km and maximum radio range /* Make the aircraft accelerate or decelerate between maximum speed and waypoint over-fly speed according to how far it is along the stretch bet- ween the waypoint's maximum radio range and 2 km from the waypoint. Dimen- sional analysis of following formula: LT^-1 = L * LT^-1 * L^-1 + LT^-1 distance the aircraft is from the waypoint | range over which its speed is allowed to vary | | */ ReqSpd = WptDst * (MaxSpd - PasSpd) / (WPR - 2000) + PasSpd; /* | | distance range over which variation occurs | base speed [minimum speed] */ } // GADUALLY PULL THE COMMAND SPEED TOWARDS THE REQUIRED SPEED CmdSpd += (ReqSpd - CmdSpd) * ET * 0.125; // speed increment this pass if(CmdSpd > MaxSpd) CmdSpd = MaxSpd; // cap the command speed // ANTICIPATE DISTANCE [IN METRES] TRAVELLED DURING THIS PASS #define RPM 1.57079633e-7 /* Earth-radians per metre commanded speed in metres per second | elapsed time in seconds since last pass | | convert metres to Earth-radians | | | */ double dD = CmdSpd * ET * RPM; // // distance travelled since last pass /* ANTICIPATE DISTANCE AIRCRAFT WAS BLOWN BY THE WIND DURING THIS PASS if the wind is switched on | AND the aircraft is off the ground | | | */ if(Wind == 1 && CmdSpd > MinSpd) { // if the aircraft is in flight /* wind speed in metres per second | elapsed time in seconds since last pass | | convert metres to Earth radians | | | */ dW = WndSpd * ET * RPM; // distance aircraft has been blown by the wind } // ANTICIPATE LATITUDINAL & LONGITUDINAL INCREMENTAL SHIFTS FOR THIS PASS double dLat, dLng, // lat and lng increments this pass K = cos(AirLat); // convert Erads to local longitude radians /* engine thrust component | wind component | | */ dLat = dD * cos(CmdHdg) + dW * cos(WndBrg); // increment in latitude AirLat += dLat; // increment the aircraft's latitude if((K) != 0) { /* avoid a possible division by zero engine thrust component wind component | | */ dLng = (dD * sin(CmdHdg) + dW * sin(WndBrg)) / K; /* | scale longitudinal degrees according to latitude */ AirLng += dLng; // increment the aircraft's longitude } } /* [REF:HEIGHT] UPDATE THE 'STEALTH' HEIGHT COMMAND The aircraft is commanded to cruise between consecutive waypoints at a predet- ermined height above the ground called the 'stealth height'. Its preset value [in metres above sea level] is represented by the label 'StlHgt'. The ground is deemed to 'undulate' according to a sigmoidal profile between the ground levels of each consecutive pair of waypoints. The ground height at any point between two waypoints is computed by the standard monopolar sigmoid function y = 1/(1+exp(-x)), where 'x' ranges from -6 to +6 and y ranges from 0 to +1. The value of 'x' is {12 times the ratio between the current distance 'WptDst' of the aircraft from the receding waypoint and the distance 'D' be- tween the two waypoints} minus 6: i.e. 12 * WptDst / D - 6. The Height Command 'CmdHgt' is obtained by re-scaling 'y' to metres. This is done by multiplying 'y' by the height difference between the two waypoints and then adding the height of the lower of the two waypoints. A rate of climb command 'CmdROC' is then calculated as a time-sensitive function of the diff- erence between the current value of the 'CmdHgt' and the current value of the actual aircraft height 'AirHgt'. The 'AirHgt' is then updated by adding to it the newly calculated rate of climb 'CmdROC'. See description at https://robmorton.website/navigation/tech.html#CmdHgt Called from only 1 place in main()'s clock loop. */ void updtHgt() { double d; if(Landing) // if in landing phase [beyond final waypoint] d = LndDst - WptDst; // distance of aircraft from final waypoint else if(ToFlag) // if going 'to' the current waypoint [cwp], d = Hgt_D - WptDst; // distance of aircraft from receding waypoint else // if receding 'from' current waypoint [cwp], d = WptDst; // distance of aircraft from receding waypoint if(Hgt_D > 0 && // avoid possible division by zero and d > 0) { // force an upper limit of +6 for 'x' */ if(d > Hgt_D) // force a lower limit of -6 for 'x' d = Hgt_D; double x = 6 - 12 * d / Hgt_D; if(Landing) x = -x; // glide slope run distance is measured backwards double z = 1 + exp(x); // divisor for Sigmoid Formula if(z < 1) z = 1; // avoid 'y' being > 1 CmdHgt = HgtH1 + (HgtH2 - HgtH1) / z; // base hgt + scaled hgt difference if(Landing == 0) // If aircraft not yet in final leg of route, add CmdHgt += StlHgt; // Stealth Height to calculated Height Command to // get Command Height above sea level. // If aircraft not yet reached 'HgtBD' where else if(WptDst < HgtBD) // it must begin sigmoidal glide slope descent, CmdHgt = HgtH1; // maintain the aircraft at its Stealth Height // above the 'current' waypoint. } // compute the commanded rate of climb [note: CmdROC is used in Tab6] CmdROC = 0.1 * (CmdSpd / MaxSpd) * (CmdHgt - AirHgt); AirHgt += CmdROC; // set new actual aircraft height } /* INITIALISE FLIGHT PARAMETERS. This must be done only once when the program is started. It must NOT be re-done upon re-exposure of the application window after the program returns from a minimized state or from another work space. This function is called only from one place within main(). */ void initFlightParams() { T2setGeoid(); // vital for the Vincenty functions T1routes(); // load in the details of all the available routes T0getWayPts(); // load the waypoints for the default route cgf = WP[cwp].cgf; // set reference to this waypoint's data setAircraftAt(); // set aircraft at start of current route ET = ETS[IP]; // set elapsed time [in μs] corresponding to the IC = ICS[IP]; // prime the Iteration Counter ms = MS[MapSize]; // set the corresponding map scale } /* MOUSE EVENT HANDLER. Handles clicks of the left mouse button [Button 1]. The x and y coordinates of the click point within the application window are already in global variables mX and mY. Called only from one place in main()'s event loop. */ void MEH() { int x = 1, i; // default click area is the Tab buttons if(mY > 16 && mY < 38) // if click on one of the Tab buttons x = 0; else if(mY > 361 && mY < 383) // if click on one of the Bottom buttons x = 2; for(i = 0; i < 8; i++) { // for each button int y = i * BW; // button offset in pixels if(mX > 16 + y && mX < 70 + y) // if click within button break; // break out of loop } switch(x) { case 0: // ONE OF THE TAB BUTTONS WAS CLICKED TB = i; // set Tab Number clearTabArea(); // clear the Tab's area showTab(); // display the Tab's content break; case 1: // CLICK OCCURRED IN SOME OTHER PART OF THE TAB AREA switch(TB) { case 0: T0CL(); break; // clicked another part of ROUTE Tab case 1: T1CL(); break; // clicked another part of WAYPTS Tab case 2: T2click(); // clicked another part of GEOID Tab } break; case 2: // CONTROL BUTTONS ALONG THE BOTTOM OF THE TAB switch(TB) { case 0: T0click(i); break; // ROUTE Tab's button click handler case 1: T1click(i); break; // WAYPOINT Tab's button click handler case 2: T2click(i); break; // GEOID Tab's button click handler case 3: T3click(i); break; // click was in MAP Tab case 4: T4click(i); break; // ATT Tab's button click handler case 5: T5click(i); break; // AZI Tab's button click handler case 6: T6click(i); break; // INPUT Tab's button click handler case 7: T7click(i); // OUTPUT Tab's button click handler } } } void MT4() { // MOUSE WHEEL DOWN switch(TB) { case 1: MW = 2; T3show(); break; case 2: MW = 2; T0show(); } } void MT5() { // MOUSE WHEEL UP switch(TB) { case 1: MW = 1; T3show(); break; case 2: MW = 1; T0show(); } } // MAIN FUNCTION FROM WHICH ALL THE ABOVE APPLICATION FUNCTIONS ARE CALLED int main(int argc, char *argv[]) { // ACCEPT POSSIBLE COMMAND-LINE ARGUMENTS if(argc > 2) { // command line argument usage: printf("%s\n","Use: nav"); // run 'nav.c' without input or output printf("%s\n","or: nav -o"); // send numeric commands to 'stdout' printf("%s\n","or: nav -io"); // also accept real lat/lng from stdin return 0; // exit because too many args entered } else if(argc > 1) { if(strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0) { printf("%s\n","Use: nav"); // normal -help output printf("%s\n","or: nav -o"); printf("%s\n","or: nav -io"); return 0; } // enable only output '-o' or both input and output '-io' else if(strcmp(argv[1], "-o") == 0) StdOut = 1; else if(strcmp(argv[1], "-io") == 0) { InFlg = 1; StdOut = 1; } } XEvent e; // reference to any event from the created window int s, // number of the current default screen mouseB; // number of the mouse button that was pressed 0, 1, 2, 3, 4 /* Check that you can actually open the X-display by getting a non-NULL reference to a display. Else send an error message to the terminal. */ XD = XOpenDisplay(NULL); if (XD == NULL) { fprintf(stderr, "Cannot open display\n"); exit(1); } s = DefaultScreen(XD); // get the number of the default screen XG = DefaultGC(XD, s); // set the graphics context to use XW = XCreateSimpleWindow( // create an application window XD, // within the default display RootWindow(XD,s), // within the root window [desktop] 10, 10, // position of top right corner 500, 400, // width and height of the window 1, // border width in pixels BlackPixel(XD,s), // colour of window's border BlackPixel(XD,s) // colour of window's background ); XStoreName(XD,XW, // set window title "GLOBAL NAVIGATOR II by Robert John Morton YE572246C"); /* Make input possible from the keyboard, window controls and mouse then display the new window on the screen. Not all these bit masks are needed so comment out those that are not needed. */ XSelectInput( XD,XW, ExposureMask | ButtonPressMask // this program uses only the mouse buttons // | KeyPressMask // | KeyReleaseMask // | PointerMotionMask // | ButtonReleaseMask // | StructureNotifyMask ); XMapWindow(XD,XW); XFlush(XD); /* Define a unique indentifier WM_DELETE_WINDOW (referred to as an atom) that will invoke the X11 protocol that closes windows upon the display 'XD' we have just created. [At least I think this is what it means] */ Atom WM_DELETE_WINDOW = XInternAtom(XD,"WM_DELETE_WINDOW",False); /* Make 'delete window' events from this application's window visible as a ClientMessage event.*/ XSetWMProtocols(XD,XW,&WM_DELETE_WINDOW,1); initFlightParams(); // initialise all flight parameters /* The event-handling [or 'run'] loop [a permanent loop] broken only by the 'break' function in response to an external event. */ while (1) { time_t st = clock(); // start time for execution of this pass if(XPending(XD) > 0) { // Provided there are events on the event queue, XNextEvent(XD,&e); // go fetch the first one. /* If it is the 'window ready' event, set the foreground [drawing] colour to white [hex value FFFFFF] and draw the static words and selector lists on the screen. */ if(e.type == Expose) // if an 'expose' event has occurred showTab(); // draw all the titles and menus in the window /* Whenever the mouse is clicked, get its window coordiates. If it was clicked within the area of one of the selector lists, re-draw that list with the clicked item highlishted. */ else if(e.type == ButtonPress) if(e.xbutton.button == Button1) { // if left mouse button clicked mX = e.xbutton.x; // set x-coord of click mY = e.xbutton.y; // set y-coord of click MEH(); // call Mouse Event Handler } if(e.xbutton.button == Button4) MT4(); // mouse wheel scroll up if(e.xbutton.button == Button5) MT5(); // mouse wheel scroll down } /* Else if any key has been pressed, or a box control clicked, break out of the permanent while() loop, close the window and exit. Should also check here for other client message types; however, as the only protocol registered above is WM_DELETE_WINDOW, it is safe for this program. */ else if (e.type == KeyPress || e.type == ClientMessage) break; /* No external event captured, so if the aircraft is in flight & it's time to do another flight update: */ else if(RunState) { // if a flight is in progress if(JC == 0) { // if the 1-second timer has expired T4plot(); // update the height plotter JC = 9; // and re-initialise the 1-second timer } else JC--; // else decrement the counter if(IC == 0) { // if the settable iteration timer has expired updtTrack(); // advance & display aircraft's position updtHgt(); // advance & display aircraft's height T3updt(); // Update and redisplay flight data and map T2auto(); // automatically check and select appropriate geiod T6updt(); // show inputs of AirLat, AirLng & AirHgt in input tab T7updt(); // update the output display T4updt(); // update the pitch and roll command display T5updt(); // update the azimuth command display IC = ICS[IP]; // reset the Iteration Counter } else IC--; // decrement the counter } usleep(100000 - clock() + st); // Pause thread for remainder of 100ms } // end of outer permanent while() loop XCloseDisplay(XD); // clear this window from the X11 display 'XD' return 0; // return that everything went OK } // end of main()