Spaces:
Running on Zero
Running on Zero
| """Great-circle geometry helpers for projecting estimated flight paths.""" | |
| from __future__ import annotations | |
| import math | |
| EARTH_RADIUS_KM = 6371.0088 | |
| KNOTS_TO_KMH = 1.852 | |
| def destination_point(lat: float, lon: float, bearing_deg: float, distance_km: float): | |
| """Great-circle destination given a start point, bearing and distance.""" | |
| ang = distance_km / EARTH_RADIUS_KM | |
| brg = math.radians(bearing_deg) | |
| lat1 = math.radians(lat) | |
| lon1 = math.radians(lon) | |
| lat2 = math.asin( | |
| math.sin(lat1) * math.cos(ang) + math.cos(lat1) * math.sin(ang) * math.cos(brg) | |
| ) | |
| lon2 = lon1 + math.atan2( | |
| math.sin(brg) * math.sin(ang) * math.cos(lat1), | |
| math.cos(ang) - math.sin(lat1) * math.sin(lat2), | |
| ) | |
| return math.degrees(lat2), (math.degrees(lon2) + 540) % 360 - 180 | |
| def haversine_km(lat1, lon1, lat2, lon2): | |
| p1, p2 = math.radians(lat1), math.radians(lat2) | |
| dphi = math.radians(lat2 - lat1) | |
| dlmb = math.radians(lon2 - lon1) | |
| a = math.sin(dphi / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dlmb / 2) ** 2 | |
| return 2 * EARTH_RADIUS_KM * math.asin(math.sqrt(a)) | |
| def initial_bearing(lat1, lon1, lat2, lon2): | |
| p1, p2 = math.radians(lat1), math.radians(lat2) | |
| dl = math.radians(lon2 - lon1) | |
| y = math.sin(dl) * math.cos(p2) | |
| x = math.cos(p1) * math.sin(p2) - math.sin(p1) * math.cos(p2) * math.cos(dl) | |
| return (math.degrees(math.atan2(y, x)) + 360) % 360 | |
| def estimate_endpoint(lat, lon, track_deg, gspeed_knots, seconds_remaining, | |
| dest_lat=None, dest_lon=None): | |
| """Return (end_lat, end_lon, distance_km, used_dest). | |
| If we know the destination airport coords, the arc goes there. Otherwise we | |
| dead-reckon forward along the current track for the remaining ETA. | |
| """ | |
| if dest_lat is not None and dest_lon is not None: | |
| d = haversine_km(lat, lon, dest_lat, dest_lon) | |
| return dest_lat, dest_lon, d, True | |
| if not gspeed_knots or not seconds_remaining or seconds_remaining <= 0: | |
| # Fall back to a short forward stub so the arc is still visible. | |
| seconds_remaining = 1800 | |
| gspeed_knots = gspeed_knots or 450 | |
| dist_km = gspeed_knots * KNOTS_TO_KMH * (seconds_remaining / 3600.0) | |
| elat, elon = destination_point(lat, lon, track_deg or 0, dist_km) | |
| return elat, elon, dist_km, False | |