Added geodesy functions

This commit is contained in:
Mark Qvist 2023-10-24 21:04:45 +02:00
parent f79059e6da
commit fc200348f5
1 changed files with 190 additions and 0 deletions

190
sbapp/sideband/geo.py Normal file
View File

@ -0,0 +1,190 @@
import RNS
import time
from math import pi, sin, cos, acos, tan, atan, atan2
from math import radians, degrees, sqrt
# Default planetary metrics
equatorial_radius = 6378.137 *1e3
polar_radius = 6356.7523142 *1e3
ellipsoid_flattening = 1-(polar_radius/equatorial_radius)
eccentricity_squared = 2*ellipsoid_flattening-pow(ellipsoid_flattening,2)
mean_earth_radius = (1/3)*(2*equatorial_radius+polar_radius)
def central_angle(c1, c2):
lat1 = radians(c1[0]); lon1 = radians(c1[1])
lat2 = radians(c2[0]); lon2 = radians(c2[1])
d_lat = abs(lat1-lat2)
d_lon = abs(lon1-lon2)
ca = acos(
sin(lat1) * sin(lat2) +
cos(lat1) * cos(lat2) * cos(d_lon)
)
return ca
def geocentric_latitude(geodetic_latitude):
e2 = eccentricity_squared
lat = radians(geodetic_latitude)
return degrees(atan((1.0 - e2) * tan(lat)))
def geodetic_latitude(geocentric_latitude):
e2 = eccentricity_squared
lat = radians(geocentric_latitude)
return degrees(atan( (1/(1.0 - e2)) * tan(lat)))
def ellipsoid_radius_at(latitude):
lat = radians(latitude)
a = equatorial_radius; b = polar_radius;
a2 = pow(a,2); b2 = pow(b,2)
r = sqrt(
( pow(a2*cos(lat), 2) + pow(b2*sin(lat), 2) )
/
( pow(a*cos(lat), 2) + pow(b*sin(lat), 2) )
)
return r
def euclidian_point(latitude, longtitude, altitude=0, ellipsoid=True):
# Convert latitude and longtitude to radians
# and get ellipsoid or sphere radius
lat = radians(latitude); lon = radians(longtitude)
r = ellipsoid_radius_at(latitude) if ellipsoid else mean_earth_radius
# Calculate euclidian coordinates from longtitude
# and geocentric latitude.
gclat = radians(geocentric_latitude(latitude)) if ellipsoid else lat
x = cos(lat)*cos(lon)*r
y = cos(gclat)*sin(lon)*r
z = sin(gclat)*r
# Calculate surface normal of ellipsoid at
# coordinates to add altitude to point
normal_x = cos(lat)*cos(lon)
normal_y = cos(lat)*sin(lon)
normal_z = sin(lat)
if altitude != 0:
x += altitude*normal_x
y += altitude*normal_y
z += altitude*normal_z
return (x,y,z)
def distance(p1, p2):
dx = p1[0]-p2[0]
dy = p1[1]-p2[1]
dz = p1[2]-p2[2]
return sqrt(dx*dx+dy*dy+dz*dz)
def euclidian_distance(c1, c2, ellipsoid=True):
if len(c1) >= 2 and len(c2) >= 2:
if len(c1) == 2: c1 += (0,)
if len(c2) == 2: c2 += (0,)
return distance(
euclidian_point(c1[0], c1[1], c1[2], ellipsoid=ellipsoid),
euclidian_point(c2[0], c2[1], c2[2], ellipsoid=ellipsoid)
)
else:
return None
def spherical_distance(c1, c2, altitude=0, r=mean_earth_radius):
d = (r+altitude)*central_angle(c1, c2)
return d
def ellipsoid_distance(c1, c2):
# TODO: Update this to the method described by Karney in 2013
# instead of using Vincenty's algorithm.
try:
if c1[0] == 0.0: c1 = (1e-6, c1[1])
a = equatorial_radius
f = ellipsoid_flattening
b = (1 - f)*a # polar radius
tolerance = 1e-9 # to stop iteration
phi1, phi2 = radians(c1[0]), radians(c2[0])
U1 = atan((1-f)*tan(phi1))
U2 = atan((1-f)*tan(phi2))
L1, L2 = radians(c1[1]), radians(c2[1])
L = L2 - L1
lambda_old = L + 0
max_iterations = 10000
iteration = 0
timeout = 1.0
st = time.time()
while True:
iteration += 1
t = (cos(U2)*sin(lambda_old))**2
t += (cos(U1)*sin(U2) - sin(U1)*cos(U2)*cos(lambda_old))**2
sin_sigma = t**0.5
cos_sigma = sin(U1)*sin(U2) + cos(U1)*cos(U2)*cos(lambda_old)
sigma = atan2(sin_sigma, cos_sigma)
sin_alpha = cos(U1)*cos(U2)*sin(lambda_old) / sin_sigma
cos_sq_alpha = 1 - sin_alpha**2
cos_2sigma_m = cos_sigma - 2*sin(U1)*sin(U2)/cos_sq_alpha
C = f*cos_sq_alpha*(4 + f*(4-3*cos_sq_alpha))/16
t = sigma + C*sin_sigma*(cos_2sigma_m + C*cos_sigma*(-1 + 2*cos_2sigma_m**2))
lambda_new = L + (1 - C)*f*sin_alpha*t
if abs(lambda_new - lambda_old) <= tolerance:
break
else:
lambda_old = lambda_new
if iteration%1000 == 0:
if iteration >= max_iterations:
return None
if time.time() > st+timeout:
return None
u2 = cos_sq_alpha*((a**2 - b**2)/b**2)
A = 1 + (u2/16384)*(4096 + u2*(-768+u2*(320 - 175*u2)))
B = (u2/1024)*(256 + u2*(-128 + u2*(74 - 47*u2)))
t = cos_2sigma_m + 0.25*B*(cos_sigma*(-1 + 2*cos_2sigma_m**2))
t -= (B/6)*cos_2sigma_m*(-3 + 4*sin_sigma**2)*(-3 + 4*cos_2sigma_m**2)
delta_sigma = B * sin_sigma * t
s = b*A*(sigma - delta_sigma)
return s
except Exception as e:
return None
def orthodromic_distance(c1, c2, spherical=False):
if spherical:
return spherical_distance(c1, c2)
else:
return ellipsoid_distance(c1, c2)
# def tests():
# from geographiclib.geodesic import Geodesic
# geod = Geodesic.WGS84
# coords = [
# [(57.758793, 22.605194), (43.048838, -9.241343)],
# [(0.0, 0.0), (0.0, 0.0)],
# [(-90.0, 0.0), (90.0, 0.0)],
# [(-90.0, 0.0), (78.0, 0.0)],
# [(0.0, 0.0), (0.5, 179.5)],
# [(0.7, 0.0), (0.0, -180.0)],
# ]
# for cs in coords:
# c1 = cs[0]; c2 = cs[1]
# print("Testing: "+str(c1)+" -> "+str(c2))
# us = time.time()
# ld = c1+c2; g = geod.Inverse(*ld)
# print("Lib computed in "+str(round((time.time()-us)*1e6, 3))+"us")
# us = time.time()
# eld = orthodromic_distance(c1,c2,spherical=False)
# if eld:
# print("Own computed in "+str(round((time.time()-us)*1e6, 3))+"us")
# else:
# print("Own TIMED OUT in "+str(round((time.time()-us)*1e6, 3))+"us")
# print("Euclidian = "+RNS.prettydistance(euclidian_distance(c1,c2)))
# print("Spherical = "+RNS.prettydistance(orthodromic_distance(c1,c2)))
# if eld: print("Ellipsoid = "+RNS.prettydistance(eld))
# print("EllipLib = "+RNS.prettydistance(g['s12']))
# if eld: print("Diff = "+RNS.prettydistance(g['s12']-eld))
# print("")