99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
"""
|
||
地理坐标工具集 —— 球面计算 + 矢量分解
|
||
"""
|
||
import math
|
||
from typing import Tuple
|
||
|
||
from uas.model.state import FlightPhase
|
||
|
||
EARTH_RADIUS = 6_371_000.0
|
||
DEG2RAD = math.pi / 180.0
|
||
RAD2DEG = 180.0 / math.pi
|
||
|
||
def determine_phase(alt: float, vs: float,
|
||
min_alt: float, max_alt: float):
|
||
if alt <= min_alt + 50:
|
||
return (FlightPhase.TAKEOFF if vs > 0.5
|
||
else FlightPhase.GROUND)
|
||
if alt >= max_alt - 200:
|
||
return FlightPhase.CRUISE
|
||
if vs > 2.0: return FlightPhase.CLIMB
|
||
if vs < -2.0: return FlightPhase.DESCENT
|
||
return FlightPhase.CRUISE
|
||
|
||
class GeoUtils:
|
||
@staticmethod
|
||
def offset_position(lat: float, lon: float,
|
||
bearing_deg: float, distance_m: float
|
||
) -> Tuple[float, float]:
|
||
"""球面偏移:从 (lat,lon) 沿 bearing 移动 distance_m"""
|
||
if distance_m == 0:
|
||
return lat, lon
|
||
lat_r = lat * DEG2RAD
|
||
lon_r = lon * DEG2RAD
|
||
brg_r = bearing_deg * DEG2RAD
|
||
delta = distance_m / EARTH_RADIUS
|
||
|
||
new_lat_r = math.asin(
|
||
math.sin(lat_r) * math.cos(delta) +
|
||
math.cos(lat_r) * math.sin(delta) * math.cos(brg_r)
|
||
)
|
||
new_lon_r = lon_r + math.atan2(
|
||
math.sin(brg_r) * math.sin(delta) * math.cos(lat_r),
|
||
math.cos(delta) - math.sin(lat_r) * math.sin(new_lat_r)
|
||
)
|
||
return new_lat_r * RAD2DEG, (new_lon_r * RAD2DEG + 540) % 360 - 180
|
||
|
||
@staticmethod
|
||
def haversine(lat1: float, lon1: float,
|
||
lat2: float, lon2: float) -> float:
|
||
"""大圆距离(米)"""
|
||
dlat = (lat2 - lat1) * DEG2RAD
|
||
dlon = (lon2 - lon1) * DEG2RAD
|
||
a = (math.sin(dlat / 2) ** 2 +
|
||
math.cos(lat1 * DEG2RAD) * math.cos(lat2 * DEG2RAD) *
|
||
math.sin(dlon / 2) ** 2)
|
||
return 2 * EARTH_RADIUS * math.asin(math.sqrt(max(0, min(1, a))))
|
||
|
||
@staticmethod
|
||
def bearing(lat1: float, lon1: float,
|
||
lat2: float, lon2: float) -> float:
|
||
"""初始方位角(度,真北顺时针)"""
|
||
lat1_r, lat2_r = lat1 * DEG2RAD, lat2 * DEG2RAD
|
||
dlon_r = (lon2 - lon1) * DEG2RAD
|
||
x = math.sin(dlon_r) * math.cos(lat2_r)
|
||
y = (math.cos(lat1_r) * math.sin(lat2_r) -
|
||
math.sin(lat1_r) * math.cos(lat2_r) * math.cos(dlon_r))
|
||
return (math.atan2(x, y) * RAD2DEG) % 360.0
|
||
|
||
@staticmethod
|
||
def normalize_heading(hdg: float) -> float:
|
||
return hdg % 360.0
|
||
|
||
@staticmethod
|
||
def heading_diff(h1: float, h2: float) -> float:
|
||
"""h2 - h1 的最短路径差值,范围 (-180, 180]"""
|
||
return ((h2 - h1 + 540) % 360) - 180
|
||
|
||
@staticmethod
|
||
def wind_to_components(wind_dir_deg: float, wind_speed_ms: float
|
||
) -> Tuple[float, float]:
|
||
"""
|
||
风向(气象风向:风从该方向吹来)→ 北向/东向分量(m/s)
|
||
北向分量为正表示向北,东向分量为正表示向东
|
||
"""
|
||
# 气象风向转数学方向:风从 wind_dir 吹来,速度向量方向为 wind_dir + 180
|
||
math_dir = (wind_dir_deg + 180.0) % 360.0
|
||
rad = math_dir * DEG2RAD
|
||
v_north = wind_speed_ms * math.cos(rad)
|
||
v_east = wind_speed_ms * math.sin(rad)
|
||
return v_north, v_east
|
||
|
||
@staticmethod
|
||
def components_to_heading_speed(v_north: float, v_east: float
|
||
) -> Tuple[float, float]:
|
||
"""北向/东向速度分量 → 航向(度)+ 合速度(m/s)"""
|
||
speed = math.sqrt(v_north ** 2 + v_east ** 2)
|
||
heading = (math.atan2(v_east, v_north) * RAD2DEG) % 360.0
|
||
return heading, speed
|