ayatfakhry/SecureNav-AI
GitHub: ayatfakhry/SecureNav-AI
SecureNav AI 是一个基于人工智能的系统,专门用于检测全球导航卫星系统的欺骗和干扰攻击。
Stars: 0 | Forks: 0
"""
drift_simulator.py
──────────────────
模拟由以下原因引起的 GNSS 位置漂移异常:
1. 多径干扰 – 信号在建筑物/地形上的反射
2. 电离层延迟累积 – TEC (Total Electron Content,总电子含量) 风暴
3. 对流层延迟 – 湿度 / 气压锋面
4. 接收机时钟漂移 – 振荡器老化 / 温度效应
5. 城市峡谷效应 – 密集环境下的严重多径
漂移不同于欺骗:接收机没有受到攻击。
误差是由环境物理因素缓慢且自然地增长。
标签为 "DRIFT"。
"""
import copy
import numpy as np
from typing import List, Optional
from src.gnss_simulator import GNSSEpoch, EARTH_RADIUS_M
# ──────────────────────────────────────────────────────────────
# 配置
# ──────────────────────────────────────────────────────────────
class DriftConfig:
# 多径
MULTIPATH_REFL_STD_M = 3.0 # 每次反射的伪距误差标准差 (m)
MULTIPATH_MAX_REFLECTIONS = 3
```
# Ionospheric
IONO_INITIAL_DELAY_M = 2.0
IONO_GROWTH_RATE_M_EPOCH = 0.15 # m per epoch (storm buildup)
IONO_MAX_DELAY_M = 40.0
# Tropospheric
TROPO_INITIAL_DELAY_M = 1.0
TROPO_GROWTH_RATE = 0.05
TROPO_MAX_DELAY_M = 15.0
# Clock drift
CLOCK_DRIFT_RATE_M_EPOCH = 0.08 # m/epoch (slow oscillator aging)
CLOCK_DRIFT_NOISE_STD = 0.02
# Urban canyon
URBAN_SNR_PENALTY_DB = 8.0 # dB-Hz loss
URBAN_SAT_LOSS_PROB = 0.25 # probability a satellite is blocked
# Position noise amplification under drift
POS_NOISE_SCALE = 4.0 # multiplier on normal position noise
```
# ──────────────────────────────────────────────────────────────
# 有状态漂移注入器
# ──────────────────────────────────────────────────────────────
class DriftSimulator:
"""
随时间累积物理漂移误差的有状态模拟器。
```
Parameters
----------
mode : str
'multipath' | 'ionospheric' | 'tropospheric' | 'clock' | 'urban' | 'mixed'
seed : int
cfg : DriftConfig (optional override)
"""
MODES = ("multipath", "ionospheric", "tropospheric", "clock", "urban", "mixed")
def __init__(
self,
mode : str = "mixed",
seed : int = 99,
cfg : Optional[DriftConfig] = None,
):
if mode not in self.MODES:
raise ValueError(f"mode must be one of {self.MODES}")
self.mode = mode
self.rng = np.random.default_rng(seed)
self.cfg = cfg or DriftConfig()
# Accumulated state
self._iono_delay_m = self.cfg.IONO_INITIAL_DELAY_M
self._tropo_delay_m = self.cfg.TROPO_INITIAL_DELAY_M
self._clock_accum_m = 0.0
self._epoch_count = 0
# ------------------------------------------------------------------
def inject(self, epoch: GNSSEpoch) -> GNSSEpoch:
"""Apply the configured drift to a single epoch and return it."""
self._epoch_count += 1
ep = copy.deepcopy(epoch)
if self.mode == "multipath":
ep = self._apply_multipath(ep)
elif self.mode == "ionospheric":
ep = self._apply_ionospheric(ep)
elif self.mode == "tropospheric":
ep = self._apply_tropospheric(ep)
elif self.mode == "clock":
ep = self._apply_clock_drift(ep)
elif self.mode == "urban":
ep = self._apply_urban_canyon(ep)
else: # mixed — layer multiple effects
ep = self._apply_multipath(ep)
if self._epoch_count % 3 == 0:
ep = self._apply_ionospheric(ep)
if self._epoch_count % 5 == 0:
ep = self._apply_tropospheric(ep)
ep = self._apply_clock_drift(ep)
# Add amplified position noise
pos_noise = float(self.rng.normal(0.0, 1e-5 * self.cfg.POS_NOISE_SCALE))
ep.lat += pos_noise
ep.lon += pos_noise * 0.7
ep.alt += float(self.rng.normal(0.0, 1.5 * self.cfg.POS_NOISE_SCALE))
ep.label = "DRIFT"
return ep
# ------------------------------------------------------------------
def _apply_multipath(self, ep: GNSSEpoch) -> GNSSEpoch:
"""Add pseudorange errors from signal reflections."""
n_reflections = int(self.rng.integers(1, self.cfg.MULTIPATH_MAX_REFLECTIONS + 1))
for sat in ep.satellites:
mp_error = sum(
float(self.rng.normal(0.0, self.cfg.MULTIPATH_REFL_STD_M))
for _ in range(n_reflections)
)
sat.pseudorange_m += mp_error
sat.carrier_phase_cycles += mp_error / 0.19029 # ~L1 wavelength
sat.snr_db_hz = max(15.0, sat.snr_db_hz - abs(mp_error) * 0.3)
# Multipath degrades PDOP slightly
ep.pdop = min(20.0, ep.pdop * float(self.rng.uniform(1.0, 1.3)))
return ep
# ------------------------------------------------------------------
def _apply_ionospheric(self, ep: GNSSEpoch) -> GNSSEpoch:
"""Build up ionospheric delay storm."""
self._iono_delay_m = min(
self.cfg.IONO_MAX_DELAY_M,
self._iono_delay_m + self.cfg.IONO_GROWTH_RATE_M_EPOCH
+ float(self.rng.normal(0.0, 0.05))
)
delay = self._iono_delay_m
for sat in ep.satellites:
# Iono delay is elevation-dependent: larger at low elevation
el_factor = 1.0 / max(0.1, np.sin(np.deg2rad(sat.elevation_deg)))
el_factor = np.clip(el_factor, 1.0, 4.0)
sat.pseudorange_m += delay * el_factor + float(self.rng.normal(0.0, 0.5))
# Phase advance (opposite sign to pseudorange for iono)
sat.carrier_phase_cycles -= (delay * el_factor) / 0.19029
# Position computation biased by iono
iono_pos_bias = delay * 1e-7 # converts to ~degrees
ep.lat += float(self.rng.normal(iono_pos_bias, iono_pos_bias * 0.3))
ep.lon += float(self.rng.normal(iono_pos_bias, iono_pos_bias * 0.3))
return ep
# ------------------------------------------------------------------
def _apply_tropospheric(self, ep: GNSSEpoch) -> GNSSEpoch:
"""Add tropospheric wet/dry delay."""
self._tropo_delay_m = min(
self.cfg.TROPO_MAX_DELAY_M,
self._tropo_delay_m + self.cfg.TROPO_GROWTH_RATE
+ float(self.rng.normal(0.0, 0.02))
)
delay = self._tropo_delay_m
for sat in ep.satellites:
el_factor = 1.0 / max(0.1, np.sin(np.deg2rad(sat.elevation_deg)))
el_factor = np.clip(el_factor, 1.0, 3.0)
sat.pseudorange_m += delay * el_factor * float(self.rng.uniform(0.9, 1.1))
ep.alt -= delay * 0.6 # altitude biased downward
return ep
# ------------------------------------------------------------------
def _apply_clock_drift(self, ep: GNSSEpoch) -> GNSSEpoch:
"""Accumulate receiver clock drift."""
self._clock_accum_m += (
self.cfg.CLOCK_DRIFT_RATE_M_EPOCH
+ float(self.rng.normal(0.0, self.cfg.CLOCK_DRIFT_NOISE_STD))
)
ep.clock_bias_m += self._clock_accum_m
# Clock drift affects all pseudoranges
for sat in ep.satellites:
sat.pseudorange_m += self._clock_accum_m * float(self.rng.uniform(0.98, 1.02))
return ep
# ------------------------------------------------------------------
def _apply_urban_canyon(self, ep: GNSSEpoch) -> GNSSEpoch:
"""Model urban canyon: lost satellites + SNR penalty."""
surviving = []
for sat in ep.satellites:
if float(self.rng.random()) < self.cfg.URBAN_SAT_LOSS_PROB:
continue # satellite blocked by building
sat.snr_db_hz = max(15.0,
sat.snr_db_hz - self.cfg.URBAN_SNR_PENALTY_DB
- float(self.rng.uniform(0.0, 4.0))
)
sat.pseudorange_m += float(self.rng.normal(0.0, 5.0))
surviving.append(sat)
ep.satellites = surviving if len(surviving) >= 2 else ep.satellites[:3]
ep.pdop = min(25.0, ep.pdop * float(self.rng.uniform(1.5, 3.0)))
return ep
# ------------------------------------------------------------------
def generate_dataset(self, base_epochs: List[GNSSEpoch]) -> List[GNSSEpoch]:
"""Inject drift into a list of clean epochs."""
return [self.inject(ep) for ep in base_epochs]
# ------------------------------------------------------------------
def reset(self) -> None:
"""Reset accumulated state (call between independent runs)."""
self._iono_delay_m = self.cfg.IONO_INITIAL_DELAY_M
self._tropo_delay_m = self.cfg.TROPO_INITIAL_DELAY_M
self._clock_accum_m = 0.0
self._epoch_count = 0
```
标签:GNSS, Python, 信号欺骗检测, 卫星导航, 干扰检测, 异常检测, 无后门, 配置错误