I recenty needed my Great CIrcle navigation functions again so decided to turn them into an SLL:
(Posted in full here so that you don't have to follow a link to another site that may disappear like so many other PB forum links have over the years.)
Discussion thread here: https://forum.powerbasic.com/forum/u...in-source-code
Useable with both PBWin and PBCC
'
and a test harness useable in both PBWIn10 and PBCC6
'
(Posted in full here so that you don't have to follow a link to another site that may disappear like so many other PB forum links have over the years.)
Discussion thread here: https://forum.powerbasic.com/forum/u...in-source-code
Useable with both PBWin and PBCC
'
Code:
'NAvCalcsSLL - Great Circle and Rhumb Line navigation functions '====================== NavCalcs.SLL Common Functions ================================== '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ' Nav Calculations '------------------ ''Distances in Nautical Miles, Latitudes and Longitudes in Degrees (S/W = Negative) 'FUNCTION nc_GCDist(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT ' 'Great Circle distance ' 'FUNCTION nc_GCDir(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT ' 'Great Circle Initial Bearing ' 'FUNCTION nc_RhumbDir(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT ' 'Rhumb Line direction in degrees ' 'FUNCTION nc_RhumbDist(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT ' 'Rhumb line distance ' 'FUNCTON nc_Lat2Lon2 (lat1 AS EXT, lon1 AS EXT, Dist AS EXT, bearing AS EXT, BYREF lat2 AS EXT, BYREF lon2 AS EXT) COMMON AS LONG ' 'Location Lat2,Lon2 at a GC distance and bearing from Lat1,Lon1 ' 'Caution: Precision of result is potentially much higher than accuracy, because of complexity of floating point math ' '(Expect a potential error in Lat/Lon of up to 1NM per 1000NM i.e +/-0.1% of distance) ' 'Distance Conversions '-------------------- 'FUNCTION nc_NMToKM(nm AS EXT) COMMON AS EXT ' 'Convert Nautical Miles to Kilometers ' 'FUNCTION nc_NMToSM(nm AS EXT) COMMON AS EXT ' 'Convert Nautical Miles to Statute Miles ' 'Lat/Lon Format conversion Functions '------------------------------------ 'FUNCTION nc_LatStrToDeg(ls AS STRING) COMMON AS EXT 'convert Latitude DMS or DM.m string to degrees 'FUNCTION nc_LontStrToDeg(ls AS STRING) COMMON AS EXT 'convert Longitude DMS or DM.m string to degrees 'FUNCTION nc_LatDtoDMS(BYVAL D AS EXT) AS STRING ''convert latitude degrees to string in form (DMS) 0°0'0"N ' 'FUNCTION nc_LonDtoDMS(BYVAL D AS EXT) COMMON AS STRING ''convert Longitude degrees to string in form 0°0'0"E ' 'FUNCTION nc_LatDtoDM(BYVAL D AS EXT,MinDecPl AS LONG) COMMON AS STRING ''convert latitude degrees to string in form 0°0.000'N (minutes to MinDecPl decimal places) '' MinDecPl = Minute Decimal Places ' 'FUNCTION nc_LonDtoDM(BYVAL D AS EXT,MinDecPl AS LONG) COMMON AS STRING ''convert Longitude degrees to string in form 0°0.000'E (minutes to MinDecPl decimal places) '' MinDecPl = Minute Decmal Places '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #COMPILE SLL "NavCalcs.SLL" #DIM ALL MACRO EarthRadiusNM = 3440.06479481645## 'mean radius of the Earth in Nautical Miles MACRO Pi = 3.14159265358979324## MACRO TwoPi = 6.283185307179586## MACRO HalfPi = 1.57079632679489662## MACRO QtrPi = 0.7853981633974483## MACRO DegToRad(d) = (d * 0.0174532925199432958##) MACRO RadToDeg(r) = (r * 57.2957795130823209##) MACRO RadToNM(r) = (r * 3437.74677078493925##) FUNCTION nc_GCDist(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT LOCAL lat1, lat2,lon1, lon2 AS EXT lat1 = DegToRad(la1):lon1 = DegToRad(lo1) lat2 = DegToRad(la2):lon2 = DegToRad(lo2) FUNCTION = RadToNM(2 * arcsin(SQR((SIN((lat1 - lat2) / 2)) ^ 2 + COS(lat1) * COS(lat2) * (SIN((lon1 - lon2) / 2)) ^ 2))) END FUNCTION FUNCTION nc_GCDir(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT LOCAL lat1, lat2 , lon1, lon2, Dist AS EXT lat1 = DegToRad(la1):lon1 = DegToRad(lo1) lat2 = DegToRad(la2):lon2 = DegToRad(lo2) FUNCTION = (RadToDeg(ATAN2(COS(lat1)*SIN(lat2)-SIN(lat1)*COS(lat2)*COS(lon2-lon1),SIN(lon2-lon1)*COS(lat2))) +360) MOD 360 END FUNCTION FUNCTION nc_RhumbDir(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT LOCAL lat1, lat2 , lon1, lon2, Dist AS EXT LOCAL dlon, dphi, q AS EXT lat1 = DegToRad(la1):lon1 = DegToRad(lo1) lat2 = DegToRad(la2):lon2 = DegToRad(lo2) dLon = DegToRad(lon2) - DegToRad(lon1) dPhi = LOG(TAN(DegToRad(lat2) / 2 + QtrPi) / TAN(DegToRad(lat1) / 2 + QtrPi)) IF(ABS(dLon) > pi) THEN IF(dLon > 0) THEN dLon = (TwoPi - dLon) * -1 ELSE dLon = TwoPi + dLon END IF END IF FUNCTION = (RadToDeg(atan2(dPhi,dLon)) + 360) MOD 360 END FUNCTION FUNCTION nc_RhumbDist(la1 AS EXT, lo1 AS EXT, la2 AS EXT, lo2 AS EXT) COMMON AS EXT LOCAL lat1, lat2 , lon1, lon2, Dist AS EXT LOCAL dlon_W, dlon_E, dphi, q AS EXT lat1 = DegToRad(la1):lon1 = DegToRad(lo1) lat2 = DegToRad(la2):lon2 = DegToRad(lo2) dlon_W = ModDbl(TwoPi, (lon2 - lon1)) dlon_E = ModDbl(TwoPi, (lon1 - lon2)) dphi = LOG(TAN(lat2 / 2 + QtrPi) / TAN(lat1 / 2 + QtrPi)) IF (ABS(lat2 - lat1) < 0.00000001) THEN q = COS(lat1) ELSE q = (lat2 - lat1) / dphi END IF IF (dlon_W < dlon_E) THEN ' Westerly rhumb line is the shortest FUNCTION = radToNM(SQR(q ^ 2 * dlon_W ^ 2 + (lat2 - lat1) ^ 2)) ELSE FUNCTION = radToNM(SQR(q ^ 2 * dlon_E ^ 2 + (lat2 - lat1) ^ 2)) END IF END FUNCTION FUNCTION nc_Lat2Lon2 (lat1 AS EXT, lon1 AS EXT, Dist AS EXT, bearing AS EXT, BYREF lat2 AS EXT, BYREF lon2 AS EXT) COMMON AS LONG LOCAL la1,lo1,la2,lo2,D,B AS EXT la1 = degToRad(lat1) lo1 = degToRad(lon1) D = Dist/EarthRadiusNM B = degtoRad(bearing) la2 = arcsin(SIN(la1) * COS(D) + COS(la1) * SIN(D) * COS(B)) lo2 = lo1 + atan2(COS(D)-SIN(la1) * SIN(la2),SIN(B) * SIN(D) * COS(la1)) IF ABS(lo2) > pi THEN lo2 = -(twopi-lo2) lat2 = radToDeg(la2) lon2= radToDeg(Lo2) END FUNCTION 'Distance Conversions FUNCTION nc_NMToKM(nm AS EXT) COMMON AS EXT FUNCTION = nm * 1.852## END FUNCTION FUNCTION nc_NMToSM(nm AS EXT) COMMON AS EXT 'Convert Nautical Miles to Kilometers FUNCTION = nm * 1.150779## END FUNCTION 'Lat/Lon Format conversion Functions '------------------------------------ FUNCTION nc_LatStrToDeg(ls AS STRING) COMMON AS EXT 'convert Latitude DMS or DM.m string to degrees LOCAL sLat AS STRING LOCAL deg AS EXT LOCAL direction AS LONG sLat = ls direction = 1 IF INSTR(sLat,ANY "sS-") THEN direction = -1 sLat = REMOVE$(sLat, ANY "-sSNn"" ") REPLACE ANY "°'" WITH ($TAB & $TAB) IN sLat 'REPLACE "'" WITH $TAB IN sLat FUNCTION = direction * (VAL(PARSE$(sLat,$TAB,1)) + VAL(PARSE$(sLat,$TAB,2))/60 + VAL(PARSE$(sLat,$TAB,3))/3600) END FUNCTION FUNCTION nc_LonStrToDeg(ls AS STRING) COMMON AS EXT 'convert Longitude DMS or DM.m string to degrees LOCAL sLat AS STRING LOCAL deg AS EXT LOCAL direction AS LONG sLat = ls direction = 1 IF INSTR(sLat,ANY "wW-") THEN direction = -1 sLat = REMOVE$(sLat, ANY "-wWeE"" ") REPLACE "°" WITH $TAB IN sLat REPLACE "'" WITH $TAB IN sLat FUNCTION = direction * (VAL(PARSE$(sLat,$TAB,1)) + VAL(PARSE$(sLat,$TAB,2))/60 + VAL(PARSE$(sLat,$TAB,3))/3600) END FUNCTION FUNCTION nc_LatDtoDMS(BYVAL D AS EXT) COMMON AS STRING LOCAL Deg AS LONG LOCAL Mn AS EXT LOCAL Sec AS EXT LOCAL Cardinal AS STRING Cardinal = "N" IF SGN(D)= -1 THEN Cardinal = "S" : D = ABS(D) Deg = INT(D) Mn = FRAC(D)* 60 Sec = ROUND(FRAC(Mn) * 60, 2) Mn = INT(Mn) FUNCTION = FORMAT$(Deg) & "°" & FORMAT$(Mn) & "'" & FORMAT$(Sec) & $DQ & Cardinal END FUNCTION FUNCTION nc_LonDtoDMS(BYVAL D AS EXT) COMMON AS STRING LOCAL Deg AS LONG LOCAL Mn AS EXT LOCAL Sec AS EXT LOCAL Cardinal AS STRING Cardinal = "E" IF SGN(D) = -1 THEN Cardinal = "W" : D = ABS(D) Deg = INT(D) Mn = FRAC(D)* 60 Sec = ROUND(FRAC(Mn) * 60, 2) Mn = INT(Mn) FUNCTION = FORMAT$(Deg) & "°" & FORMAT$(Mn) & "'" + FORMAT$(Sec) & $DQ & Cardinal END FUNCTION FUNCTION nc_LatDtoDM(BYVAL D AS EXT,MinDecPl AS LONG) COMMON AS STRING LOCAL Deg AS LONG LOCAL Mn AS EXT LOCAL Cardinal AS STRING Cardinal = "N" IF SGN(D)= -1 THEN Cardinal = "S" : D = ABS(D) Deg = INT(D) Mn = FRAC(D)* 60 IF MinDecPl > 0 THEN FUNCTION = FORMAT$(Deg) & "°" & FORMAT$(Mn,"0." & STRING$(MinDecPl,"0")) & "'" & Cardinal ELSE FUNCTION = FORMAT$(Deg) & "°" & FORMAT$(CINT(Mn)) & "'" & Cardinal END IF END FUNCTION FUNCTION nc_LonDtoDM(BYVAL D AS EXT,MinDecPl AS LONG) COMMON AS STRING LOCAL Deg AS LONG LOCAL Mn AS EXT LOCAL Sec AS EXT LOCAL Cardinal AS STRING Cardinal = "E" IF SGN(D) = -1 THEN Cardinal = "W" : D = ABS(D) Deg = INT(D) Mn = FRAC(D)* 60 IF MinDecPl > 0 THEN FUNCTION = FORMAT$(Deg) & "°" & FORMAT$(Mn,"0." & STRING$(MinDecPl,"0")) & "'" & Cardinal ELSE FUNCTION = FORMAT$(Deg) & "°" & FORMAT$(CINT(Mn)) & "'" & Cardinal END IF END FUNCTION 'Math Helpers FUNCTION arcsin(X AS EXT) AS EXT FUNCTION = ATN(X / SQR(1 - X * X )) END FUNCTION FUNCTION arccos(X AS EXT) AS EXT FUNCTION = HalfPi - ATN(X / SQR(1 - X * X ) ) END FUNCTION FUNCTION atan2(x AS EXT, y AS EXT) AS EXT SELECT CASE X CASE IS > 0 FUNCTION = ATN(y / X) ' x>0 CASE IS = 0 SELECT CASE y CASE IS > 0 FUNCTION = HalfPi ' x=0, y>0 CASE IS < 0 FUNCTION = - HalfPi 'x=0, y<0 CASE 0 FUNCTION = 0 'x = 0, y = 0 END SELECT CASE IS < 0 SELECT CASE y CASE IS >= 0 FUNCTION = ATN(y / X) + pi ' x<0, y>=0 CASE IS < 0 FUNCTION = ATN(y / X) - pi 'x<0, y<0 END SELECT END SELECT END FUNCTION FUNCTION ModDbl(X AS EXT, y AS EXT) AS EXT 'Modulus of floating point values - used in Rhumb Line calculations IF y >= 0 THEN FUNCTION = y - X * INT(y / X) ELSE FUNCTION = y + X * (INT(-y / X) + 1) END IF END FUNCTION '
'
Code:
#COMPILE EXE #DIM ALL #LINK "NavCalcs.SLL" FUNCTION PBMAIN() AS LONG LOCAL lDebug AS LONG: TXT.WINDOW EXE.FULL$, 200,50,40,85 TO lDebug LOCAL sla1,slo1,sla2,slo2 AS STRING 'Lat and Lon strings LOCAL la1,lo1,la2,lo2 AS EXT 'Lat and long as degrees LOCAL d,b,d2,b2 AS EXT 'distance (NM) and bearing (degrees) 'Initialise locations with strings (DM.m) sla1 = "9°27.9'S" :slo1 = "147°11.5'E" 'Port Moresby sla2 = "51°28' N" :slo2 = "0°12' E" ' Greenwich 'Convert Lat/Lon strings to Degrees la1= nc_LatstrToDeg(sla1):lo1= nc_LonStrToDeg(slo1) la2= nc_LatstrToDeg(sla2):lo2= nc_LonStrToDeg(slo2) 'Display Back conversion as test of conversion functions TXT.PRINT "From: " & nc_LatDtoDM(la1,2) & ", " & nc_LonDtoDM(lo1,2) TXT.PRINT "To: " & nc_LatDtoDM(la2,2) & ", " & nc_LonDtoDM(lo2,2) TXT.PRINT 'Great Circle distance and bearing d = nc_gcdist(la1,lo1,la2,lo2) b = nc_gcdir(la1,lo1,la2,lo2) TXT.PRINT "Great Circle Distance: " & FORMAT$(d,"0.0" ) & "NM";" ("; FORMAT$(nc_NMtoKM(d),"0.0" ) & "km, ";FORMAT$(nc_NMtoSM(d),"0.0" ) & " miles)" TXT.PRINT "Great Circle Bearing: " & FORMAT$(b," 0.0") & "°" 'Rhumb Line Distance and Bearing d2 = nc_rhumbdist(la1,lo1,la2,lo2) b2= nc_rhumbdir(la1,lo1,la2,lo2) TXT.PRINT "Rhumb Line Distance: " & FORMAT$(d2,"0.0" ) & "NM";" ("; FORMAT$(nc_NMtoKM(d2),"0.0" ) & "km, ";FORMAT$(nc_NMtoSM(d2),"0.0" ) & " miles)" TXT.PRINT "Rhumb Line Bearing: " & FORMAT$(b2,"000.0") & "°" TXT.PRINT 'Location at a GC distance and bearing from first Location nc_Lat2Lon2 la1,lo1,d,b ,la2,lo2 TXT.PRINT FORMAT$(d," 0.00");"NM on a bearing of ";FORMAT$(b,"0.0"); "° from: " & nc_LatDtoDM(la1,2) & ", " & nc_LonDtoDM(lo1,2) TXT.PRINT " takes us to: ";nc_LatDtoDM(la2,0) & ", " & nc_LonDtoDM(lo2,0); " (approx)" TXT.PRINT " (+/- 0.1% of distance! i.e error of up to "; FORMAT$(d/1000,"0.0"); "NM in this case)" TXT.COLOR = %RGB_BLUE TXT.PRINT TXT.PRINT " ....Press any key to exit": TXT.WAITKEY$: TXT.END END FUNCTION '
Comment