%%HP: T(3)A(R)F(.);
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Maidenhead Locator System Calculator, A.Ryan 14/FEB/11
@ Version 1.0
@ Last Edit: 22/MAR/11 A.Ryan
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Version 1.1
@ Added illegal value check in GetLatLon. Although the program is well-behaved, if illegal values
@ were entered, then strange results would be returned.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Version 1.2
@ Added atan2(), and Bearing functions to compute the Great Circle bearing from two QRA locator
@ strings.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Version 1.3
@ Added the ToRads routine to convert the latitude/longitude pairs to radians. Initially this
@ function used compiled local variables, but the final version uses the elegance of stack
@ manipulation and a START-NEXT loop to quickly convert four angles from degrees to radians
@ and leave the converted values on the stack in the same order. This signifcantly reduced the
@ size of the code.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Version 1.4
@ Added various procedures that provide an integrated graphic user interface. Added code to
@ CnvLatLon to round the value to four decimal places. When using the returned result sometimes
@ there would be a 1 in the tenth decimal place as a result of the various conversions involving
@ recurring decimal fractions. The INFORM function would then attempt to display this value if it
@ was used as a default. This rounding also ensures that a returned angle of xx.xx149999999 or
@ xx.xx2999999999 was always returned as xx.xx15 or xx.xx30
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Version 1.5
@ This version contains the modifications needed to allow this program to run on the HP-48 series
@ of machines. Note the changes in 'Start', 'Loc2QRA', 'QRA2Loc', and 'QRA2DH'. The changes
@ required to convert the file to run on a HP-49/50 can be found by setting your text editor to
@ search for the string: HP50

@ Minor modification to DspLonLatQRA. Since all the other parameters were passed to local
@ variables, it was more consistent to pass the QRA locator text string as a local variable as
@ well. This slightly reduced the code size. Add spaces to the START choose box to centralise the
@ selections.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Version 1.6
@ Minor modification to the CnvLatLon routine to eliminate a superfluous division by 24.
@ Minor modification to the GetLatLon function to eliminate a duplicate operation, and collect
@ a repeated set of operations into a function, GetChr. 
@ Minor modification to the DspLonLatQRA procedure to collect a repeated set of operations into a
@ function, FmtStr.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Version 1.7
@ Major modifications to the DspLonLatQRA and QRA2DH procedures. Upon examination I found that I
@ was replicating a significant amount of code to merely format and display the various data
@ values. It made sense to combine all this formatting into a function that took an index and a
@ value and selected the necessary wording and padding and then returned the fully formatted
@ string to the calling routine for display. To this end the FmtStr function was greatly expanded
@ and the two calling routines considerably simplified. Major modification to the Start procedure
@ to optimise the operation by removing some inelegant code, and to make better use of the
@ CHOOSE function. The EXIT selection has been eliminated, and only the CANCEL key now exits.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This program calculates the QRA locator, otherwise known as the Maidenhead Locator System for a
@ given pair of latitude and longitude co-ordinates. This locator system was devised to give a
@ more compact representation of location than the Degree Minutes Second E/W-N/S method normally
@ employed. It is principally used by radio amateurs, and its 6-character representaion is
@ significantly shorter than the 14-character normal format, which is especially useful for CW,
@ RTTY, PSK31, as well as being more readable with SSB considering the relatively poor S/N ratios
@ experienced during normal contacts. For a full explanation, please refer to the PDF document
@ accompanying this program.

@ Briefly, the code uses alternating pairs of letters and figures in the format:

@ AB12CD

@ The first pair of characters, [AB] commonly called the field, encodes the longitude and
@ latiutude designators for a 20 degree longitude and 10 degree latitude rectangle. The second
@ pair of digits [12] commonly called a square, encode the longitude/latitude for a 2 degree
@ longitude and 1 degree of latitude rectangle within the field. The third pair of characters,
@ [CD] commonly called a sub-square, encodes a 5 minute of longitude and 2.5 minute of latitude
@ rectangle within the sub-square.

@ In order to avoid negative longitude/latitude values, latitude is measured from the south pole,
@ and longitude is measured from the 180 degree anti-meridian. This gives the equator a false
@ northing of 90 degrees, and the Prime Meridan a false easting of 180 degrees.

@ The world is therefore divided into 18 fields of longitude and 18 fields of latitude,
@ each represented by the letters A-R, with A = 0, B = 1, C = 2 etc.

@ There are 100 squares to a field, as a 10x10 matrix of 2 degrees of longitude and 1 degrees of
@ latitude, each represented by the digits 0-9.

@ Squares are divided into a 24x24 matrix of 5 minutes of longitude and 2.5 minutes of latitude,
@ represented by the letters A-X, with A = 0, B = 1, C = 2 etc.

@ There are a number of possible methods of calculating the code, but after examining several
@ and discovering some anomalies and occasional reciprocity errors, I decided on the following
@ logic:

@ With 24 sub-squares to a square, 100 squares to a field, and a total of 324 fields, this
@ gives 324 x 100 x 24 = 777,600 possible rectangles, from AA00AA to RR99XX, corresponding to
@ Latitude -89.5845 - Longitude -179.5730, to Latitude 89.5845 - Longitude 179.5730, the data
@ being in DD.MMSS format.

@ The input value is entered in the DD.MMSS format, with southern longitudes and western latitudes
@ negative, and northern longitudes and eastern latitudes positive. The value is first converted
@ to its decimal equivalent. For the latitude 90 degrees is added, and for the longitude 180 degrees.
@ The psuedo-latitude is converted to its number of 2.5 minute steps. The pseudo-longitude is
@ converted to its number of 5 minute steps.

@ In both cases, the step number is divided by 240, and the integer part is the index into the
@ alphabet list. The remainder is multiplied by 240 and then divided by 24, in other words 
@ multiplied by 10. The integer part is the digit of the square. The remainder is multipled by
@ 24 and its integer part is the index into the alphabet list for the last character. This method
@ automatically quantises the value and ensures that the functions QRA-> and ->QRA are reciprocal.

@ Each value is dealt with separately, and the resultant 3-character strings are merged to give
@ The final result.

@ The inverse function first splits the 6-character string into two 3-character strings, in
@ inverse order, extracts each character/digit and performs the inverse operations. That is, the
@ first character extracted has its position in the alphabet retrieved, and has 0.5 added. The
@ middle digit is multiplied by 24 and added to the previous result. The most significant
@ character is retrieved, and its position in the alphabet list calculated, then multiplied by
@ 240 and added to the previous values, giving the quantised number. If it is the latitude value,
@ then it is multiplied by the decimal equivalent of 2.5 minutes to get the pseudo-angle, from
@ which 90 degrees is subtracted to return the actual latitude in decimal form. This is then
@ converted to the DD.MMSS format. Similarly, if the quantised number is longitude, then it is
@ multiplied by the decimal equivalent of 5 minutes to return the pseudo-angle, 180 degrees is
@ subtracted to give the actual decimal longitude, which is then converted to its DD.MMSS form.

@ Note that in both cases when the least significant character is converted to its value, 0.5 is
@ added to ensure that the final value is in the centre of the sub-square, which ensures that if
@ these angles are then re-converted, the result is perfectly reciprocal.

@ The display should be set to 4 decimal places.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DIR
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This procedure displays a CHOOSE box and requests the user to select a function.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Start
  \<<
    RCLF                     @ Recall current machine state
    -40. CF                  @ Do not show clock
	4. FIX                   @ Fix display to 4 decimal places
	1.                       @ Place initial choose box selection on stack
	\-> flags select
    \<<
      WHILE select REPEAT
	    CLLCD
	    "SELECT FUNCTION"    @ HP50 version: "\127\127\127 SELECT FUNCTION \127\127\127"
	    { { "Lat/Long \-> QRA" { Loc2QRA 1. } } @ HP50 version: "   Lat / Long \-> QRA"
	      { "QRA \-> Lat/Long" { QRA2Loc 2. } } @ HP50 version: "   QRA \-> Lat / Long"
		  { "Dist/Heading" { QRA2DH 3. } }      @ HP50 version: "  Distance / Bearing"
	    } select
	    IF CHOOSE
        THEN  OBJ\-> DROP    @ Convert returned list to objects, and drop the count
		'select' STO         @ Save the current selection,
		EVAL                 @ and evaluate the function
        ELSE 0. 'select' STO @ Else simply save a nul in the selection.
		END
      END flags STOF         @ Restore the previous machine state
    \>>
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ List of example locators that may be used for example to demonstrate distance and bearing.
@ Note that in the INFORM function if a string is requested then pressing the VAR button will
@ display this list of variable names. Pressing one of the A-F softkeys will then enter the
@ variables name into the command line, and pressing ENTER will retrieve the value.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Ardara "IO54TS"
  Athens "KM18UA"
  Barcelona "JN11BK"
  Berlin "JO62QM"
  Brussels "JO20ET"
  Chicago "EN61DT"
  Donegal "IO54WP"
  Dublin "IO63VH"
  Fairbanks "BP64CT"
  Istambul "KN41LA"
  London "IO91WM"
  Melbourne "QF22LG"
  Miami "EL95VS"
  Moscow "KO85US"
  Munich "JN58TD"
  NewYork "FN30BQ"
  Nicosia "KM65QE"
  Ottawa "FN25DK"
  SanFrancisco "CM87SS"
  Souni "KM64KR"
  Vancouver "CN89KF"
  HomeQRA "KM64KR"
  Sydney "QF56OB"
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This procedure requests the user to enter a latitude and longitude in DD.MMSS format and will
@ then calculate its equivalent 6-character QRA locator and display the result.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Loc2QRA
  \<<
    Version                 @ Put the Version string on the stack as the title of the INFORM
    { { "Latitude :" "Enter data as DD.MMSS" 0. }
	  { "Longitude:" "Enter data as DD.MMSS" 0. }
	  { "Save as home QRA" "Enter a non-zero to save" }
    }
	{ }
	HomeQRA QRA\-> 0        @ Convert the Home QRA to lat/long and place on the stack
    3. \->LIST
	DUP

    IF INFORM
    THEN
	  LIST\->
	  DROP
	  3. ROLLD \->QRA SWAP
      IF
      THEN DUP 'HomeQRA' STO
      END
	  4. 3. 6.              @ HP50 version: 5. 4. 7.
      DspLonLatQRA
    END
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This procedure requests the user to enter a 6-character QRA locator, and will then calculate
@ its equivalent latitude and longitude and display the result.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  QRA2Loc
  \<<
    Version       @ Put the Version string on the stack as the title of the INFORM
    { { "QRA locator:" "Enter a QRA as AAnnAA" 2. }
	}
	{ }
	HomeQRA       @ Put the Home QRA on the stack
	1. \->LIST
	DUP

    IF INFORM
    THEN
	  LIST\->
	  DROP
	  6. 5. 3.    @ HP50 version: 7. 6. 4.
	  DspLonLatQRA
    END
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This procedure requests the user to enter two QRA locator strings and will then calculate the
@ Great Circle distance and bearing from the first to the second and display the result.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  QRA2DH
  \<<
    Version       @ Put the Version string on the stack as the title of the INFORM
    { { "QRA From:" "Enter QRA as AAnnAA" 2. }
	  { "QRA   To:" "Enter QRA as AAnnAA" 2. }
	}
	{ }
	HomeQRA       @ Use the Home QRA as the "From" point.
	DUP 2. \->LIST
	DUP

    IF INFORM
    THEN
	  PrtHdr
	  LIST\->     @ Convert the two QRAs from a list to 2 stack entries
	  DROP        @ and drop the count
	  DUP2 DUP2   @ duplicate the pairs twice, leaving 2 pairs of QRA strings on the stack
	  QRA\->D     @ Consume the first pair, and return with a distance
	  3. ROLLD    @ move the distance to level 3
	  QRA\->B     @ consume another pair of QRA strings and return with an angle
	  Int2Str     @ convert the angle to a string
	  \^o +       @ add the degree symbol
      3.
	  FmtStr
	  6. DISP     @ HP50 version: 8. DISP
	  Int2Str     @ now convert the distance to a string
	  "Kms" +     @ and add the Kms suffix
      4.
	  FmtStr
	  5. DISP     @ HP50 version: 7. DISP
	  "  To      :   " SWAP +    @ display the end point QRA
	  3. DISP     @ HP50 version: 5. DISP
	  "  From    :   " SWAP +    @ and the start point QRA
	  2. DISP     @ HP50 version: 4. DISP
	  Wait4OK     @ wait for the OK key.
    END
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This procedure takes four values on the stack, on level 4 is the 6-character QRA locator string
@ on levels 3, 2, and 1 are the display rows. It converts the QRA locator to its equivalent
@ latitude and longitude values, then each of these values are converted to formatted strings and
@ displayed at their designated rows.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  DspLonLatQRA
  \<<
    PrtHdr
    \-> qra r1 r2 r3
	\<<
	  qra QRA\->    @ Convert the QRA string to latitude and longitude
      2.
	  FmtStr        @ Convert longitude to its formatted string version
	  r1 DISP       @ and display,
	  1.
	  FmtStr        @ convert latitude to its formatted string version
	  r2 DISP       @ and display.
	  " QRA      :   " qra +
	  r3 DISP       @ Finally display the QRA locator value,
	  Wait4OK       @ and wait for the OK key to be pressed.
	\>>
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This procedure creates a temporary menu with just a single entry, the OK key (F6). It then
@ waits for the user to press a key. If the key is the F6 key, then it retrieves the previous
@ menu, and exits. Otherwise, it simply beeps, and waits for the F6 key.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Wait4OK
  \<<
    { "" "" "" "" "" "OK" } TMENU
    DO -1. WAIT
    UNTIL DUP
      IF 16.1 \=/
      THEN 1420. .074 BEEP
      END 16.1 ==
    END 0. MENU
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes a count value on the stack and returns a blank string whose length is equal
@ to the count. In the case of a count of zero, a zero-length string is returned.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  MakeSpc
  \<<
    "" SWAP DUP
    IF
    THEN 1. SWAP
      START " " +
      NEXT
    ELSE DROP
    END
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes two values on the stack. On level 2 is the latitude, on level 1 the
@ longitude both in DD.MMSS format. The locator string is returned on level 1 of the stack, and
@ is immediately usable by the inverse function.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  \->QRA
  \<<
    0. GetLatLon     @ Get the 3-character longitude string
    SWAP
    1. GetLatLon     @ Get the 3-character latitude string
	Merge
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes an upper-case locator string on level 1 of the stack, and returns the
@ longitude and latitude values. On level 2 is the latitude, on level 1 the longitude, both in
@ DD.MMSS format, and they are immediately usable by the inverse function.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  QRA\->
  \<<
    Split
    1. CnvLatLon     @ Get the latitude value in decimal degrees
	SWAP
	0. CnvLatLon     @ Get the longitude value in decimal degrees
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes two QRA locator strings on the stack and computes the Great Circle bearing
@ between the origin, on stack level 2, and the destination on stack level 1. The result is
@ returned on stack level 1 as a bearing from true north in degrees, rounded to the nearest
@ degree.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  QRA\->B
  \<<
    SWAP
    QRA\->
	ROT
	QRA\->
	Bearing
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes two QRA locator strings on the stack and computes the distance between
@ their centre points.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  QRA\->D
  \<<
    QRA\->
    ROT
	QRA\->
	Distance
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This computes the distance between two points on a sphere using the Haversine formula.
@ It assumes the earth is a perfect sphere, whereas it is an oblate spheroid, nevertheless
@ the errors are fairly small for reasonably large distances separating the two locations.
@ The mean radius of the earth is taken to be 6372.8kms, and the result is rounded to the
@ nearest integer.
@ The function takes four values on the stack. Level 4 is latitude 1, level 3 longitude 1
@ level 2 latitude 2, and level 1 longitude 2, all in decimal degrees. The result is returned
@ on level 1 as the distance in kms, rounded to the neares kilometre.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Distance
  \<<
    ToRads                     @ Convert the four values to radians
    \-> lat1 lon1 lat2 lon2
    \<<
	  lat1 lat2 - 2. / SIN SQ  @ Compute Sin^2( ( lat1 - lat2 ) / 2 ) = A
	  lon1 lon2 - 2. / SIN SQ  @ Compute Sin^2( ( lon1 - lon2 ) / 2 ) = B
	  lat1 COS lat2 COS *      @ Compute Cos( lat1 ) * Cos( Lat2 )    = C
	  *                        @ B * C
	  +                        @ ( B * C ) + A
	  \v/                      @ Sq-Root (( B * C ) + A )
	  ASIN 2. * 6372.8 *       @ ArcSin ( Sq-Root (( B * C )  + A )) * 2 * 6372.8
	  DUP FP .5 > + IP         @ Round to nearest integer.
    \>>
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes two values on the stack. On level 2 is the signed angular latitude or
@ longitude in DD.MMSS format, on level 1 is a flag. 1 = Latitude, 0 = Longitude. It returns on
@ level 1 of the stack a 3-character string representing the converted value.
@ 18/FEB/11
@ Added illegal value checks. The maximum latitude/longitude value gives a position number of
@ 4319.5, the minimum gives 0. If the position number is greater than 4319.5 or less than 0, then
@ the appropriate maximum or minimum values are substituted.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  GetLatLon
  \<<
      SWAP
      HMS\->                  @ Convert angle to its decimal form
	  SWAP                    @ Get the flag
      IF
      THEN 90. + .023         @ If it is a latitude, then add 90 and use 2.5' conversion factor
      ELSE 180. + .05         @ If it is a longitude then add 180 and use 5' conversion factor
      END
	  HMS\->                  @ convert conversion factor to decimal degrees
	  /                       @ and divide to get position number.
	  DUP 4319.5 >
	  IF THEN DROP 4319.5 END @ Now check for illegal values, i.e latitude > 90
	  DUP 0. <                @ or -90, longitude greater than 180 or -180 degrees.
	  IF THEN DROP 0. END
	  240. / DUP              @ Divide by 240 to get field value
	  FP 10. *                @ FP * 10 is the value for the second calculation
	  SWAP
	  IP
      GetChr                  @ get the character for the field.
	  SWAP DUP
	  IP Int2Str              @ IP of previous value is the second digit, for the square
      SWAP                    @ converted to a string.
	  FP 24. * IP             @ This forms the index into the alphabet for the last character
      GetChr                  @ get the character for the sub-square,
	  + +                     @ and concatenate the three single character strings on the stack.
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes 2 values on the stack. On level 2 is an upper-case 3-character string of
@ the encoded latitude/longitude, on level 1 is a flag. 1 = Latitude, 0 = Longitude. It returns
@ on level 1 of the stack the equivalent latitude/longitude value in DD.MMSS format.
@ NOTE: The string is in reverse order! If the original string was A1B, here it is B1A.
@ Note the last line of code. This was necessary to eliminate an occaisonal 1 in the tenth
@ decimal place that would screw up the display in the INFORM function when displaying the
@ default values!
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  CnvLatLon
  \<<
    \-> s flag
    \<<
	  Alphabet               @ Put the alphabet string on the stack
	  s 1. 1. SUB            @ get the first character
	  POS 1. - .5 +          @ calculate its position add .5 offset
	  s 2. 2. SUB            @ extract the second character
	  STR\-> 24. * +         @ and convert to a number, multiply by 24 and add the previous result
	  Alphabet               @ put the alphabet back on the stack
	  s 3. 3. SUB            @ get the final character
	  POS 1. - 240. * +      @ calculate its value and multiply by 240 and add it to the
      IF flag                @ previous values
      THEN .023 HMS\-> * 90. @ If this is latitude, use 2.5' and subtract 90 degrees 
      ELSE .05 HMS\-> * 180. @ else use 5' and subtract 180 degrees
      END
	  -
	  \->HMS                 @ convert back to DD.MMSS format, and as the calculator is already
	  \->STR STR\->          @ set to 4 decimal places, round the result.
    \>>
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes a 6-character uppercase string on level 1 of the stack and splits it into
@ two 3-character strings in reverse order. On level 2 of the stack is the longitude, on level 1
@ the latitude.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Split
  \<<
    "" ""
	\-> QRA lat long
    \<< 1. 6.
      FOR i
	    QRA i i SUB 'long' STO+
	    QRA i 1. + DUP SUB 'lat' STO+
	  2. STEP
	  long lat
    \>>
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes two 3-character upper-case strings on the stack, and merges them into a
@ single 6-character string.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Merge
  \<<
    \-> Long Lat
    \<< ""
	  1. 3.
      FOR i
	    Long i i SUB
		Lat i i SUB
		+ +
      NEXT
    \>>
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes four values on the stack, Latitude 1, Longitude 1, Latitude 2, Longitude 2
@ all in radians, and computes the great circle bearing from Latitude 1 - Longitude 1 to
@ Latitude 2 - Longitude 2, and returns the result normalised as a compass bearing in degrees
@ relative to true north rounded up to the nearest integer value.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Bearing
  \<<
    ToRads
    \-> lat1 lon1 lat2 lon2
    \<< 
	  lon2 lon1 - SIN lat2 COS *
	  lat1 COS lat2 SIN *
	  lat1 SIN lat2 COS lon2 lon1 - COS * * -
	  atan2
	  180. \pi / * 360. + 360. MOD
	  DUP FP .5 > + IP
    \>>
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes 4 values on the stack and converts them from degrees to radians.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ToRads
\<<
    RAD
    \pi 180. /
	5. ROLLD
	1. 4.
	START
	  5. PICK * 4. ROLL 
	NEXT
	5. ROLL DROP
\>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ atan2( y, x )
@ This function implemenets the atan2() function, which is surprisingly missing from the
@ calculator. In order to ensure that it is well-behaved, the input data is checked in
@ accordance with the following rules:
@ IF x>0 THEN arctan( y / x )
@ IF y>=0 AND x<0 THEN PI + arctan( y / x )
@ IF y<0 AND x<0 THEN -PI + arctan( y / x )
@ IF y>0 AND x==0 THEN PI/2
@ IF y<0 AND X==0 THEN -PI/2
@ IF y==0 AND x==0 THEN 0
@ NOTE: This latter case is usually undefined, but I have chosen the most pragmatic
@ approach, I hope the pure mathematicians will forgive me.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  atan2
\<<
  RAD
  \-> y x
  \<<
    CASE
	  x 0. > THEN y x / ATAN END
	  y 0. \>= x 0. < AND THEN \pi y x / ATAN + END
	  y 0. < x 0. < AND THEN \pi NEG y x / ATAN + END
	  y 0. > x 0. == AND THEN \pi 2. / END
	  y 0. < x 0. == AND THEN \pi 2. / NEG END
	  y 0. == x 0. == AND THEN 0. END
    END
  \>>
\>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ The calculator does not really have a function that will return just the string version of a
@ numerical value. If the ->STR function is used, then the returned string will always contain
@ a trailing decimal point, even when the display is set to zero decimal places. This function
@ will take a numeric quantity on the stack, save the current machine state, set the display to
@ zero decimal places which inherently rounds the number to the nearest integer, converts it to
@ a string, then extracts only the sign (if any) and the numeric part, but not the trailing
@ decimal point, restores the previous settings, and returns the string on the stack.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Int2Str
  \<<
      RCLF          @ Get the current machine state
      SWAP          @ and save it on stack level 2
	  0. FIX        @ set the display to 0 decimal places, rounding the value to an integer
	  \->STR        @ convert to a string, with the trailing decimal point
	  DUP
	  SIZE 1. -     @ find the length of the string, and subtract 1
      1.            @ place a 1 on the stack
	  SWAP          @ and swap so that the start and end values are in the right order
	  SUB           @ and extract the sub-string
	  SWAP          @ get the previous machine's state
	  STOF          @ and finally restore it.
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes a single numeric value on the stack and gets the character equivalent,
@ returning a single character string. Thus, 0 = "A", 1 = "B", 2 = "C" etc.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  GetChr
  \<<
      Alphabet SWAP 1. + DUP SUB
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ This function takes two values on the stack. On level 1 is an index, 1 = Latitude,
@ 2 = Longitude, 3 = Bearing, 4 = Distance, on level 2 is either a latitude or longitude in
@ DD.MMSS format, or a string. It returns a formatted string of the form: " Latitude:  34.4345"
@ or " Longitude:  32.5230" or "  Bearing:  45" or "  Distance:  12345" which is then displayed.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 FmtStr
 \<<
	  { " Latitude :" " Longitude:" "  Bearing :" "  Distance:" }
	  SWAP
	  GET
      SWAP
      \->STR         @ Convert to a string. If already a string, it does nothing.
      DUP
	  SIZE           @ calculate the length of the string
	  9. SWAP -      @ calculate the padding spaces
      MakeSpc        @ and make the padding string
      SWAP           @ put padding and the other strings in correct order
      + +            @ and concatenate the three strings for displaying.
 \>>
  
  PrtHdr
  \<< CLLCD "   " Version + 1. DISP
  \>>

@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ Global variable containing the alphabet string used in various conversion routines.
@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Alphabet "ABCDEFGHIJKLMNOPQRSTUVWX"
  Version "QRA LOCATOR 1.7"
END
