PROGRAM Bearing_Distance; {Determines bearing and distance based on} {algorithm compensating for earth's shape} {Copyright 1993 Michael R. Owen, W9IP & } { Paul Wade, N1BWT } {Released to the public domain Feb 23, 1993} {NOTE: NORTH latitude, WEST longitude apre positive; } { South & East should be input as negative numbers} {$N+,E+} {Use coprocessor if it's present, otherwise emulate w/software} Uses CRT; Type Float = Extended; {for maximum precision} TYPE grid = String[6]; CONST MyLat : Float = 0; MyLon : Float = 0; KeepGoing : Boolean = True; FirstTime : Boolean = True; MyGrid : Grid = 'None'; Var error : BOOLEAN ; FirstLat, FirstLon, OtherLat, OtherLon : Float; FirstGrid, OtherGrid : Grid ; Choice : Char ; {------------------------------------------------------------} Function Tan(x:Float): Float; Begin Tan := sin(x)/cos(x); end; {------------------------------------------------------------} Function ArcCos(x:Float): Float; Begin ArcCos := (Pi/2) - ArcTan(x/sqrt(1-x*x)); End; {---------------------------------------------------------} Function ATan2 (Y, X : Float) : Float; {returns ArcTan in the rnge 0.. 2Pi} var Arg : Float; Label 10, 11, 12, 13, 14, 15, 16, 17; Begin If X <> 0 then Arg := ArcTan(Y/X); If X < 0 then GOTO 10 else If X = 0 then GOTO 14 else GOTO 11; 10 : Atan2 := Pi + Arg; Exit; 11 : If Y < 0 then GOTO 12 else GOTO 13; 12 : Atan2 := 2.0 * Pi + Arg; Exit; 13 : Atan2 := Arg; Exit; 14 : If Y < 0 then GOTO 15 else if Y = 0 then GOTO 16 else GOTO 17; 15 : Atan2 := 1.5 * Pi; Exit; 16 : Atan2 := 0.0; Exit; 17 : Atan2 := 0.5 * Pi; Exit; end; {Atan2} {------------------------------------------------------------} Procedure Clear_Buff; {Clears keyboard buffer} Begin MemW[$0:$041A] := MemW[$0:$041C]; end; {Clear_Buff} {---------------------------------------------------------------} Procedure Wait; {Suspends program until key pressed} var Dump : Char; Begin Writeln; Writeln('Press any key to continue'); Repeat Until KeyPressed; Dump := ReadKey; {so KeyPressed will be false upon return} End; {wait} {---------------------------------------------------------------} Function DMS (InVal : Float) : String; {converts to degrees, minutes, seconds} Var Deg, Min, Sec : Integer; DegStr, MinStr, SecStr : String; Begin Deg := Trunc(InVal); Min := Trunc(Frac(InVal) * 60); Sec := Round(((Frac(InVal) * 60) - Min) * 60); Str(Deg:2,DegStr); Str(Min:2,MinStr); Str(Sec:2,SecStr); DMS := DegStr + 'ø ' + MinStr + ''' ' + SecStr + '"' End; {DMS} {---------------------------------------------------------------} Function NS_EW (InVal : Float; LatOrLon : String) : String; {Adds North, South...} Begin If LatOrLon = 'Lat' then If InVal > 0 then NS_EW := ' North' else NS_EW := ' South' else If InVal > 0 then NS_EW := ' West' else NS_EW := ' East'; end; {NS_EW} {---------------------------------------------------------------} PROCEDURE Check_Grid (VAR g : grid ; VAR error : boolean ) ; {verifies that the grid square is legitimate} VAR i : INTEGER ; BEGIN If Length(g) = 4 then g := g + 'LL'; {choose middle if only 4-character} error := FALSE ; i := 0; REPEAT Inc(i) ; if ((i = 3) OR (i = 4)) then if ((ord(g[i])>=ord('0')) AND (ord(g[i])<=ord('9'))) then error := FALSE {2nd two characters are numbers} else error := TRUE else if ((ord(g[i])>=ord('A')) AND (ord(g[i])<=ord('Z'))) then error := FALSE {first and last 2 characters are letters} else if ((ord(g[i])>=ord('a')) AND (ord(g[i])<=ord('z'))) then BEGIN g[i] := chr(ord(g[i]) - ord('a') + ord('A')) ; error := FALSE END { else if } else error := TRUE UNTIL ( i=6 ) OR ( error ) ; END ; { Check_Grid } {------------------------------------------------------------} PROCEDURE Grid_center (gridsq : grid ; VAR lon,lat : Float ) ; {finds the lat/lon of center of the sub-square} VAR lonmin, londeg, latmin, latdeg : Float ; BEGIN lonmin := (5 * (ord(gridsq[5])-ord('A'))) + 2.5 ; { center } londeg := 180 -( 20 * (ord(gridsq[1])-ord('A'))) { tens of deg } - ( 2 * (ord(gridsq[3])-ord('0'))) ; { two deg } lon := londeg - (lonmin/60) ; latdeg := -90 + ( 10 * (ord(gridsq[2])-ord('A'))) { tens of deg } + (ord(gridsq[4])-ord('0')); { degrees } latmin := 2.5 * (ord(gridsq[6])-ord('A')) { minutes } + 1.25 ; { for center } lat := latdeg + (latmin/60) ; END ; { Grid_center } {------------------------------------------------------------} PROCEDURE Input_Grid (VAR aGrid : grid ; VAR lon, lat : Float; Var OK : Boolean); {reads grid square input from keyboard} VAR error : BOOLEAN ; Ans : String; BEGIN error := TRUE ; while error do BEGIN Write(' 4 or 6 digit grid square: [Q to quit] ') ; Readln(Ans); OK := NOT (Ans[1] in ['q', 'Q']); If OK then begin aGrid := ans; Check_Grid(aGrid, error) ; Grid_Center(aGrid,lon,lat) ; if error then Writeln(' *** Error in grid format ***') ; end else error := False; {lets you out} END; { while } END ; { Input_Grid } {------------------------------------------------------------} Procedure LatLon_to_Grid(LocLat, LocLon : Float; Var GS : Grid); {converts lat & long to grid square} var c : Integer; g4, R, M, L4 : Float; M1, M2, M3, M4, M5, M6 : Char; BEGIN G4 := -LocLon+180; C := Trunc (G4/20); M1 := CHR(C+65); R := ABS(LocLon/20); R := INT(((R-INT(R))*20+0.001)); C := Trunc(R/2); IF LocLon >= 0 THEN C := ABS(C-9); M3 := CHR(C+48); M := ABS(LocLon*60); M := ((M/120)-INT(M/120))*120; M := INT(M+0.001); C := Trunc(M/5); IF LocLon >=0 THEN C := ABS(C-23); M5 := CHR(C+65); L4 := LocLat+90; C := Trunc(L4/10); M2 := CHR(C+65); R := ABS(LocLat/10); R := INT(((R-INT(R))*10+0.001)); C := Trunc(R); IF LocLat <0 THEN C := ABS(C-9); M4 := CHR(C+48); M := ABS(LocLat*60); M := ((M/60)-INT(M/60))*60; C := Trunc(M/2.5); IF LocLat <0 THEN C:=ABS(C-23); M6 := CHR(C+65); GS := M1 + M2 + M3 + M4 + M5 + M6; end; {Lat_Lon_to_Grid} {------------------------------------------------------------} Function In_Range(Value, LowVal, HighVal : Float) : Boolean; {checks that the value is between Lowval and Highval} BEGIN In_Range := (Value >= LowVal) AND (Value <= HighVal); END; {In_Range} {------------------------------------------------------------} PROCEDURE Input_LatLon(Title: String; Var Lat, Lon : Float; var OK : Boolean); {reads lat & lon from keyboard; Decimal values are OK} Var LatDeg, LatMin, LatSec, LonDeg, LonMin, LonSec : Float; BEGIN ClrScr; HighVideo; GotoXY(40-Length(title) DIV 2,10); Writeln(Title); NormVideo; GoToXY(5,25); Write('North latitude is positive, South is negative. [enter zeros to quit]'); GotoXY(1,12); Repeat Write('Latitude degrees: '); ReadLn(LatDeg); Until In_Range(LatDeg, -90, 90); Repeat Write('Latitude minutes: '); ReadLn(LatMin); Until In_Range(LatMin, -50.9999, 59.9999); If (LatDeg < 0) and (LatMin > 0) then LatMin := -LatMin; Repeat Write('Latitude seconds: '); ReadLn(LatSec); Until In_Range(LatSec, -59.9999, 59.9999); If (LatDeg < 0) and (LatSec > 0) then LatSec := -LatSec; Lat := LatDeg + (LatMin/60) + (LatSec/3600); OK := Abs(Lat) > 0.001; {check for zero, which lets you out} WriteLn; If OK then Begin GotoXY(1,25); ClrEol; GoToXY(15,25); Write('West longitude is positive, East is negative'); GotoXY(1,16); Repeat Write('Longitude degrees: '); ReadLn(LonDeg); Until In_Range(LonDeg, -180, 179.9999); Repeat Write('Longitude minutes: '); ReadLn(LonMin); Until In_Range(LonMin, -59.9999, 59.9999); If (LonDeg < 0) and (LonMin > 0) then LonMin := -LonMin; Repeat Write('Longitude seconds: '); ReadLn(LonSec); Until In_Range(LonSec, -59.9999, 59.9999); If (LonDeg < 0) and (LonSec > 0) then LonSec := -LonSec; Lon := LonDeg + (LonMin/60) + (LonSec/3600); OK := Abs(Lon) > 0.001; end; GoToXY(1,25); ClrEol; END; {Input_LatLon} {------------------------------------------------------------} PROCEDURE SetUp; {Gets and stores user's location for future use} Var SetUpLat, SetUpLon : Float; SetUpGrid : Grid; Ans : Char; DataFile : Text; BEGIN ClrScr; GoToXY(1,10); Writeln('YOUR current location (in decimal form): '); Writeln('Latitude: ',Abs(MyLat):8:3,'ø ', NS_EW(MyLat,'Lat')); Writeln('Longitude: ',Abs(MyLon):8:3,'ø ', NS_EW(MyLon,'Lon')); Writeln('Grid Square : ',MyGrid); Writeln; WriteLn('Do you want to change these values? '); Repeat Until KeyPressed; Ans := ReadKey; If Ans in ['Y', 'y'] then begin Input_LatLon('Your location',SetUpLat, SetUpLon, KeepGoing); WriteLn; LatLon_to_Grid(SetUpLat, SetUpLon, SetUpGrid); Write('Grid Square: '); Highvideo; Writeln(SetUpGrid); NormVideo; Writeln; If KeepGoing then begin Writeln('Do you want to save these values? [Y/N] '); Repeat Until KeyPressed; Ans := ReadKey; If Ans in ['Y', 'y'] then begin {save data} Assign(DataFile,'BD.DAT'); ReWrite(DataFile); Write(DataFile, SetUpLat:8:3,' ',SetUpLon:8:3); Close(DataFile); MyLat := SetUpLat; MyLon := SetUpLon; MyGrid := SetUpGrid; Writeln('Saved'); end; {Otherwise don't change anything} end; end; {if user didn't want to change anything} END; {SetUp} {------------------------------------------------------------} Procedure Calc_GeoDist(Eplat, Eplon, Stlat, Stlon : Float; var Az, Baz, Dist, Deg : Float); {Taken directly from: } {Thomas, P.D., 1970, Spheroidal Geodesics, reference systems,} { & local geometry, U.S. Naval Oceanographic Office SP-138,} { 165 pp.} {assumes North Latitude and East Longitude are positive} {EpLat, EpLon = MyLat, MyLon} {Stlat, Stlon = HisLat, HisLon} {Az, BAz = direct & reverse azimuith} {Dist = Dist (km); Deg = central angle, discarded} Const AL = 6378206.4; {Clarke 1866 ellipsoid} BL = 6356583.8; D2R = pi/180.0; {degrees to radians conversion factor} Pi2 = 2.0 * Pi; Label 1, 2, 3, 4, 5, 6, 7, 8, 9; var BOA, F, P1R, P2R, L1R, L2R, DLR, T1R, T2R, TM, DTM, STM, CTM, SDTM,CDTM, KL, KK, SDLMR, L, CD, DL, SD, T, U, V, D, X, E, Y, A, FF64, TDLPM, HAPBR, HAMBR, A1M2, A2M1 : Float; begin BOA := BL/AL; F := 1.0 - BOA; P1R := Eplat * D2R; P2R := Stlat * D2R; L1R := Eplon * D2R; L2R := StLon * D2R; DLR := L2R - L1R; T1R := ArcTan(BOA * Tan(P1R)); T2R := ArcTan(BOA * Tan(P2R)); TM := (T1R + T2R) / 2.0; DTM := (T2R - T1R) / 2.0; STM := Sin(TM); CTM := Cos(TM); SDTM := Sin(DTM); CDTM := Cos(DTM); KL := STM * CDTM; KK := SDTM * CTM; SDLMR := Sin(DLR/2.0); L := SDTM * SDTM + SDLMR * SDLMR * (CDTM * CDTM - STM * STM); CD := 1.0 - 2.0 * L; DL := ArcCos(CD); SD := Sin(DL); T := DL/SD; U := 2.0 * KL * KL / (1.0 - L); V := 2.0 * KK * KK / L; D := 4.0 * T * T; X := U + V; E := -2.0 * CD; Y := U - V; A := -D * E; FF64 := F * F / 64.0; Dist := AL*SD*(T -(F/4.0)*(T*X-Y)+FF64*(X*(A+(T-(A+E)/2.0)*X)+Y*(-2.0*D+E*Y)+D*X*Y))/1000.0; Deg := Dist/111.195; TDLPM := Tan((DLR+(-((E*(4.0-X)+2.0*Y)*((F/2.0)*T+FF64*(32.0*T+(A-20.0*T)*X-2.0*(D+2.0)*Y))/4.0)*Tan(DLR)))/2.0); HAPBR := ATan2(SDTM,(CTM*TDLPM)); HAMBR := Atan2(CDTM,(STM*TDLPM)); A1M2 := Pi2 + HAMBR - HAPBR; A2M1 := Pi2 - HAMBR - HAPBR; 1 : If (A1M2 >= 0.0) AND (A1M2 < Pi2) then GOTO 5 else GOTO 2; 2 : If A1M2 >= Pi2 then GOTO 3 else GOTO 4; 3 : A1M2 := A1M2 - Pi2; GOTO 1; 4 : A1M2 := A1M2 + Pi2; GOTO 1; 5 : If (A2M1 >= 0.0) AND (A2M1 < Pi2) then GOTO 9 else GOTO 6; 6 : If A2M1 >= Pi2 then GOTO 7 else GOTO 8; 7 : A2M1 := A2M1 - Pi2; GOTO 5; 8 : A2M1 := A2M1 + Pi2; GOTO 5; 9 : Az := A1M2 / D2R; BAZ := A2M1 / D2R; end; {Calc_GeoDist} {------------------------------------------------------------------} PROCEDURE Read_QTH_File; {read user's QTH data file created by SetUp} Var DataFile : Text; IOR : Integer; Ans : Char; BEGIN Assign(DataFile,'BD.DAT'); {$I-} {turn off I/O checking} Reset(DataFile); {see if the file is there} {$I+} {I/O checking back on} IOR := IOResult; If IOR <> 0 then begin {if it's not there...} Writeln; HighVideo; Writeln('You need to set up your current station location.'); Writeln('Press Y to run Setup or any Other key to quit'); NormVideo; Repeat Until KeyPressed; Ans := ReadKey; If Ans in ['Y','y'] then SetUp else begin ClrScr; Halt; {quit program} end; end else begin {read it} Readln(DataFile,MyLat,MyLon); LatLon_to_Grid(MyLat, MyLon, MyGrid); FirstTime := False; {so it won't be read again} Close(DataFile); end; END; {Read_QTH_File} {------------------------------------------------------------} Function Format(GridIn: Grid) : Grid; {makes last two lettters of grid lowercase} Begin GridIn[5] := Chr(Ord(GridIn[5])+32); GridIn[6] := Chr(Ord(GridIn[6])+32); Format := GridIn; end; {Format} {------------------------------------------------------------} PROCEDURE Write_Results(Grid1, Grid2 : Grid; Lat1, Lat2, Lon1, Lon2 : Float); {output of calculations} VAR PathDistmi, PathDistKm, Bearing, ReverseBearing, Deg : Float ; BEGIN ClrScr; GotoXY(1,10); Calc_GeoDist(Lat1, -Lon1, Lat2, -Lon2, Bearing, ReverseBearing, PathDistKm, Deg); PathDistMi := PathDistKm / 1.609344; Writeln(' From grid ',Format(Grid1),' to grid ',Format(Grid2) ) ; Writeln; Write (' Distance = '); Highvideo; Writeln(PathDistMi:9:3,' mi, ', PathDistKm:9:3,' km'); NormVideo; Write (' True bearing = '); HighVideo; Writeln(Bearing:6:2,'ø' ) ; NormVideo; IF Lon1 > Lon2 {Other station is west} THEN Bearing := 360.0 - Bearing ; Write ('Reverse bearing = '); HighVideo; Writeln(ReverseBearing:6:2,'ø' ) ; NormVideo; wait; Clear_Buff; {clear keyboard buffer} END; {Write_Results} {------------------------------------------------------------} {------------------------------------------------------------} {------------------------------------------------------------} BEGIN { main BD rogram } Repeat ClrScr; Highvideo; GotoXY(20,1); Write('BD.Pas : Bearing and Distance Calculator'); NormVideo; GotoXY(10,3); Write('Calculates bearing & distance, compensating for earth''s shape'); GoToXY(2,6); Write('REF: Thomas, P.D., 1970, Spheroidal Geodesics, reference systems, & local'); GoToXY(2,7); Write(' geometry, U.S. Naval Oceanographic Office #SP-138, 165 pp.'); GotoXY(10,9); Writeln('Copyright 1993 Michael R. Owen, W9IP & Paul Wade, N1BWT'); GotoXY(27,10); Writeln('Northern Lights Software'); Writeln; Writeln; If FirstTime then Read_QTH_File; {check stored QTH} Writeln('Press <1> for calculations from your QTH to another grid square'); Writeln(' <2> for calculations from your QTH to another latitude/longitude'); Writeln(' <3> for calculations between any two grid squares'); Writeln(' <4> for calculations between any two latitude/longitude points'); Writeln(' <5> to convert latitude/longitude to grid square'); Writeln(' <6> to convert grid square to latitude/longitude'); Writeln(' <7> set your stored QTH'); Writeln(' to quit the program'); Writeln; Writeln('Your choice: '); Repeat until keypressed; Choice := ReadKey; Case Choice of '1' : Repeat ClrScr; GotoXY(1,10); Input_Grid(OtherGrid,OtherLon,OtherLat, KeepGoing); If KeepGoing then Write_Results(MyGrid, OtherGrid, MyLat, OtherLat, MyLon, OtherLon); Until Not KeepGoing; '2' : Repeat Input_LatLon('Other location',OtherLat, OtherLon, KeepGoing); If KeepGoing then begin LatLon_to_Grid(OtherLat, OtherLon, OtherGrid); Write_Results(MyGrid, OtherGrid, MyLat, OtherLat, MyLon, OtherLon); end; Until Not KeepGoing; '3' : Repeat ClrScr; GotoXY(1,10); Write(' Enter first') ; Input_Grid (FirstGrid,FirstLon,FirstLat, KeepGoing); Writeln; If KeepGoing then begin Write(' Enter second') ; Input_Grid (OtherGrid,OtherLon,OtherLat, KeepGoing); end; If KeepGoing then Write_Results(FirstGrid, OtherGrid, FirstLat, OtherLat, FirstLon, OtherLon); Until Not KeepGoing; '4' : Repeat Input_LatLon('Location #1',FirstLat, FirstLon, KeepGoing); LatLon_to_Grid(FirstLat, FirstLon, FirstGrid); If KeepGoing then Input_LatLon('Location #2',OtherLat, OtherLon, KeepGoing); LatLon_to_Grid(OtherLat, OtherLon, OtherGrid); If KeepGoing then Write_Results(FirstGrid, OtherGrid, FirstLat, OtherLat, FirstLon, OtherLon); Until not KeepGoing; '5' : Repeat Input_LatLon('Location?',FirstLat, FirstLon, KeepGoing); If KeepGoing then begin LatLon_to_Grid(FirstLat, FirstLon, FirstGrid); Writeln; HighVideo; Writeln('Grid square: ',Format(FirstGrid)); LowVideo; Wait; end; Until Not KeepGoing; '6' : Repeat ClrScr; GoToXY(1,10); Input_Grid (OtherGrid,OtherLon,OtherLat, KeepGoing); If KeepGoing then begin Grid_center(OtherGrid,OtherLon,OtherLat); WriteLn; HighVideo; Writeln('Grid : ',Format(OtherGrid)); Writeln('Latitide : ',Abs(OtherLat):8:3,'ø = ', DMS(OtherLat), NS_EW(OtherLat,'Lat')); Write('Longitude: ',Abs(OtherLon):8:3,'ø = ', DMS(OtherLon),NS_EW(OtherLon,'Lon')); LowVideo; Wait; end; Until Not KeepGoing; '7' : SetUp; #27 : Begin {Esc character quits program} ClrScr; Halt; {Terminate program} end; end; {Case} Until 1 = 2; {i.e. forever; the only way out is through above} END.