base_agent/uas/utils/nav_utils.py

327 lines
14 KiB
Python
Raw Normal View History

2026-06-18 03:28:14 +00:00
import math
import random
from copy import deepcopy
from typing import Optional
from uas.model.constants import *
from uas.model.sensor_noise import SensorNoiseParams
from uas.model.state import AircraftState
from uas.utils.geo_utils import GeoUtils
# ══════════════════════════════════════════════════════════════════════
# 导航解算器(简化卡尔曼滤波)
# ══════════════════════════════════════════════════════════════════════
class NavigationFilter:
"""
简化导航滤波器
功能
融合 GPS 观测值和 INS 推算值输出导航解算状态nav_state
该状态用于 FMS 的航线跟踪决策
算法
采用简化的互补滤波Complementary Filter
是卡尔曼滤波的简化版本
nav_pos = α × gps_pos + (1-α) × ins_pos
其中 α = gps_ins_blendGPS 权重
GPS 丢失时 α=0完全依赖 INS 推算误差随时间积累
与完整卡尔曼滤波的区别
完整卡尔曼滤波会动态调整增益矩阵
此处简化为固定权重足以模拟导航误差对轨迹的影响
"""
def __init__(self, init_state: AircraftState,
sensor_params: SensorNoiseParams,
seed: Optional[int] = None):
"""
Args:
init_state: 初始真实状态用于初始化导航解算状态
sensor_params: 传感器噪声参数
seed: 随机种子用于可重复仿真
"""
self.params = sensor_params
self._rng = random.Random(seed)
# ── INS 积分状态(独立于真实状态)────────────────────────────
# INS 从初始位置开始积分,误差随时间累积
self._ins_lat: float = init_state.latitude
self._ins_lon: float = init_state.longitude
self._ins_alt: float = init_state.altitude
self._ins_heading: float = init_state.heading
self._ins_speed: float = init_state.speed
# INS 累积漂移量(东向、北向,单位 m
# 用于模拟 INS 位置误差的随机游走特性
self._ins_drift_east: float = 0.0
self._ins_drift_north: float = 0.0
# GPS 丢失状态
self._gps_available: bool = True
# 导航解算状态(初始与真实状态相同)
self.nav_state: AircraftState = deepcopy(init_state)
# 各传感器观测值(供外部分析)
self.obs_gps_lat: float = init_state.latitude
self.obs_gps_lon: float = init_state.longitude
self.obs_baro_alt: float = init_state.altitude
self.obs_airspeed: float = init_state.airspeed
self.obs_heading: float = init_state.heading
def update(self, true_state: AircraftState,
dt: float) -> AircraftState:
"""
根据真实状态生成传感器观测值并融合输出导航解算状态
执行步骤
1. 用真实状态模拟各传感器测量加噪声
2. 用真实速度/航向推算 INS 状态加漂移
3. 融合 GPS INS 得到导航解算位置
4. 融合气压计和 GPS 得到导航解算高度
5. 融合空速管和 INS 得到导航解算速度
6. 返回导航解算状态
Args:
true_state: 当前真实物理状态
dt: 仿真步长 (s)
Returns:
nav_state: 导航解算状态FMS 实际使用的状态
"""
p = self.params
# ── 步骤 1GPS 测量 ──────────────────────────────────────────
# 模拟 GPS 信号丢失(以概率 gps_dropout_prob 丢失)
# 信号丢失时GPS 观测值保持上一次的值(不更新)
self._gps_available = (
self._rng.random() > p.gps_dropout_prob
)
if self._gps_available:
# GPS 水平噪声(考虑 HDOP 因子放大)
# HDOP > 1 时,水平精度下降
h_sigma = p.gps_horizontal_noise * p.gps_hdop
# 将水平位置噪声(米)转换为经纬度偏差(度)
# 纬度方向1° ≈ 111320m与纬度无关
# 经度方向1° ≈ 111320 × cos(lat) m随纬度变化
lat_noise_deg = (
self._rng.gauss(0, h_sigma) / 111320.0
)
cos_lat = math.cos(math.radians(true_state.latitude))
lon_noise_deg = (
self._rng.gauss(0, h_sigma)
/ (111320.0 * max(cos_lat, 0.001))
)
self.obs_gps_lat = true_state.latitude + lat_noise_deg
self.obs_gps_lon = true_state.longitude + lon_noise_deg
# GPS 垂直噪声(高度)
self.obs_gps_alt = (
true_state.altitude
+ self._rng.gauss(0, p.gps_vertical_noise)
)
# ── 步骤 2INS 推算(积分更新)─────────────────────────────
# INS 用上一步的速度和航向推算当前位置
# 误差来源:速度噪声、航向漂移、积分误差
# INS 速度观测(含噪声)
ins_speed_obs = max(
MIN_SPEED,
true_state.speed + self._rng.gauss(0, p.ins_velocity_noise)
)
# INS 航向观测(含漂移)
# 航向漂移是累积的(随机游走),每步增加一个小随机量
heading_drift_step = self._rng.gauss(0, p.ins_heading_drift * dt)
self._ins_heading = (
true_state.heading + heading_drift_step
) % 360.0
# INS 位置漂移(随机游走)
# 每步在东向和北向各增加一个随机漂移量
# 漂移量 = drift_rate × dt × 随机方向
drift_magnitude = p.ins_drift_rate * dt
self._ins_drift_east += self._rng.gauss(0, drift_magnitude)
self._ins_drift_north += self._rng.gauss(0, drift_magnitude)
# INS 推算位置 = 真实位置 + 累积漂移
# (真实位置用于初始化,漂移量叠加在上面)
drift_dist = math.sqrt(
self._ins_drift_east ** 2
+ self._ins_drift_north ** 2
)
drift_bearing = math.degrees(
math.atan2(self._ins_drift_east, self._ins_drift_north)
) % 360.0
if drift_dist > 0:
self._ins_lat, self._ins_lon = GeoUtils.offset_position(
true_state.latitude, true_state.longitude,
drift_bearing, drift_dist
)
else:
self._ins_lat = true_state.latitude
self._ins_lon = true_state.longitude
# ── 步骤 3气压高度计测量 ────────────────────────────────────
# 气压高度计含固定偏置(气压设定误差)和随机噪声
self.obs_baro_alt = (
true_state.altitude
+ p.baro_altitude_bias # 固定偏置
+ self._rng.gauss(0, p.baro_altitude_noise) # 随机噪声
)
obs_baro_vs = (
true_state.vertical_speed
+ self._rng.gauss(0, p.baro_vs_noise)
)
# ── 步骤 4空速管测量 ────────────────────────────────────────
self.obs_airspeed = max(
MIN_SPEED,
true_state.airspeed
+ p.airspeed_bias
+ self._rng.gauss(0, p.airspeed_noise)
)
# ── 步骤 5航向传感器测量 ────────────────────────────────────
self.obs_heading = (
true_state.heading
+ p.heading_bias
+ self._rng.gauss(0, p.heading_noise)
) % 360.0
# ── 步骤 6导航融合互补滤波──────────────────────────────
# GPS 有效时:融合 GPS 和 INS
# GPS 丢失时:纯 INS 推算(误差快速累积)
if self._gps_available:
alpha = p.gps_ins_blend # GPS 权重(典型值 0.8
else:
# GPS 丢失:完全依赖 INS误差随时间积累
alpha = 0.0
# 融合水平位置
nav_lat = (alpha * self.obs_gps_lat
+ (1.0 - alpha) * self._ins_lat)
nav_lon = (alpha * self.obs_gps_lon
+ (1.0 - alpha) * self._ins_lon)
# 融合高度GPS 垂直精度较差,气压计更可靠
# 典型民航高度主要用气压计GPS 作辅助修正
if self._gps_available:
# 气压计权重 0.7GPS 高度权重 0.3
nav_alt = (0.7 * self.obs_baro_alt
+ 0.3 * self.obs_gps_alt)
else:
nav_alt = self.obs_baro_alt
# 融合航向:使用磁罗盘/AHRS 观测值
nav_heading = self.obs_heading
# 融合空速:使用空速管观测值
nav_airspeed = self.obs_airspeed
# 垂直速度:使用气压计差分
nav_vs = obs_baro_vs
# ── 步骤 7更新并返回导航解算状态 ───────────────────────────
self.nav_state = true_state.copy_with(latitude = nav_lat,
longitude = nav_lon,
altitude = nav_alt,
heading = nav_heading,
airspeed = nav_airspeed,
speed = ins_speed_obs,
vertical_speed = nav_vs,
wind_direction = true_state.wind_direction,
wind_speed = true_state.wind_speed)
# self.nav_state.latitude = nav_lat
# self.nav_state.longitude = nav_lon
# self.nav_state.altitude = nav_alt
# self.nav_state.heading = nav_heading
# self.nav_state.airspeed = nav_airspeed
# self.nav_state.speed = ins_speed_obs
# self.nav_state.vertical_speed = nav_vs
# self.nav_state.wind_direction = true_state.wind_direction
# self.nav_state.wind_speed = true_state.wind_speed
# self.nav_state.phase = true_state.phase
# self.nav_state.timestamp = true_state.timestamp
# self.nav_state.elapsed_time = true_state.elapsed_time
# self.nav_state.lifecycle = true_state.lifecycle
# self.nav_state = AircraftState(
# latitude = nav_lat,
# longitude = nav_lon,
# altitude = nav_alt,
# heading = nav_heading,
# airspeed = nav_airspeed,
# speed = ins_speed_obs,
# vertical_speed = nav_vs,
# wind_direction = true_state.wind_direction,
# wind_speed = true_state.wind_speed,
# phase = true_state.phase,
# timestamp = true_state.timestamp,
# )
return self.nav_state
def reset_ins_drift(self) -> None:
"""
重置 INS 漂移模拟 GPS 重新锁定后的 INS 对准
GPS 信号恢复后调用 INS 漂移归零
"""
self._ins_drift_east = 0.0
self._ins_drift_north = 0.0
@property
def gps_available(self) -> bool:
return self._gps_available
@property
def ins_drift_distance(self) -> float:
"""当前 INS 累积漂移距离 (m)"""
return math.sqrt(
self._ins_drift_east ** 2
+ self._ins_drift_north ** 2
)
# ══════════════════════════════════════════════════════════════════════
# 转弯计算工具(保持不变,略)
# ══════════════════════════════════════════════════════════════════════
class TurnCalculator:
"""转弯几何计算(同前版本)"""
@staticmethod
def compute_turn_radius(speed_ms, bank_angle_deg):
bank_rad = math.radians(
max(1.0, min(bank_angle_deg, MAX_BANK_ANGLE_LIMIT))
)
return (speed_ms ** 2) / (GRAVITY * math.tan(bank_rad))
@staticmethod
def compute_turn_angle(current_heading, target_heading):
delta = (target_heading - current_heading) % 360.0
if delta > 180.0:
delta -= 360.0
return delta
@staticmethod
def compute_turn_lead_distance(speed_ms, turn_radius_m,
turn_angle_deg):
half = math.radians(abs(turn_angle_deg) / 2.0)
if half >= math.radians(89.0):
return turn_radius_m * 10.0
return turn_radius_m * math.tan(half)
@staticmethod
def compute_turn_rate(speed_ms, turn_radius_m):
if turn_radius_m <= 0 or speed_ms < MIN_TURN_SPEED:
return 0.0
return math.degrees(speed_ms / turn_radius_m)