-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.py
105 lines (73 loc) · 3.68 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
## See `main.py` for more information
import math
from typing import Optional, Self, Tuple
from pygeomag import GeoMag
import datetime
## CONSTANTS ##
EARTH_RADIUS_METERS = 6_378_137
class GPSPoint():
""" A single point on the Earth, including altitude. """
def __init__(self, latitude: float = 0.0, longitude: float = 0.0, altitude: Optional[float] = None) -> None:
self.lat = latitude
self.lon = longitude
self.alt = altitude
def lat_rad(self) -> float:
""" Returns the latitude component in radians. """
return math.radians(self.lat)
def lon_rad(self) -> float:
""" Returns the longitude component in radians. """
return math.radians(self.lon)
def distance_to(self, other: Self) -> float:
""" Great-circle ground-only distance in meters between two GPS Points. """
delta_lat_rad = other.lat_rad() - self.lat_rad()
delta_lon_rad = other.lon_rad() - self.lon_rad()
a = math.sin(delta_lat_rad / 2)**2 + math.cos(self.lat_rad()) * math.cos(other.lat_rad()) * math.sin(delta_lon_rad / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return EARTH_RADIUS_METERS * c
def altitude_to(self, other: Self) -> Optional[float]:
if self.alt is not None and other.alt is not None:
return other.alt - self.alt
else:
return None
def bearing_to(self, other: Self, positive: bool = False) -> float:
""" Find the absolute bearing (azimuth) to another point. """
# Calculate the bearing
bearing = math.atan2(
math.sin(other.lon_rad() - self.lon_rad()) * math.cos(other.lat_rad()),
math.cos(self.lat_rad()) * math.sin(other.lat_rad()) - math.sin(self.lat_rad())
* math.cos(other.lat_rad()) * math.cos(other.lon_rad() - self.lon_rad())
)
# Convert the bearing to degrees
bearing = math.degrees(bearing)
# Ensure the value is from -180→180 instead of 0→360
if positive:
bearing = (bearing + 360) % 360
return bearing
def bearing_mag_corrected_to(self, other: Self, positive: bool = False) -> float:
""" Find the absolute bearing (azimuth) to another point, to be used with a device basing its heading on magnetic north """
bearing = self.bearing_to(other, False)
current_datetime = datetime.datetime.now()
fractional_year = (float((current_datetime.date() - datetime.date(current_datetime.year, 1, 1)).days) / 365.2425) + current_datetime.year
geo_mag = GeoMag(base_year=datetime.datetime.now(), high_resolution=True)
result = geo_mag.calculate(glat=self.lat, glon=self.lon, alt=self.alt or 0.0, time=fractional_year)
bearing = bearing + result.d
if positive:
bearing = (bearing + 360) % 360
return bearing
def elevation_to(self, other: Self) -> float:
""" Find the elevation above the horizon (altitude) to another point. """
# Distance in meters, and horizontal angle (azimuth)
horizontal_distance = self.distance_to(other)
# In this case things would divide by zero, so bail
if horizontal_distance == 0:
return 0.0
# Altitude difference in meters
altitude_delta = self.altitude_to(other)
if altitude_delta is None:
raise Exception("Cannot calculate elevation with no altitude")
# Vertical angle (altitude)
vertical_angle = math.degrees(math.atan(altitude_delta / horizontal_distance))
return vertical_angle
def m_to_ft(meters: float) -> float:
""" Helper function to convert meters to feet, mainly for display """
return meters / 0.3048