use crate::gps_lnav::{
data_words, encode_subframe, encode_subframes, LnavConventions, LnavEphemeris,
};
use crate::rinex::RinexEphemeris;
use rand::RngCore;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
pub const LNAV_BIT_S: f64 = 0.02;
pub const LNAV_FRAME_BITS: usize = 1500;
const WEEK_S: f64 = 604_800.0;
#[derive(Clone, Debug, PartialEq)]
pub enum NavData {
None,
Lnav {
eph: Box<LnavEphemeris>,
conv: LnavConventions,
},
Seeded {
seed: u64,
},
}
pub fn lnav_from_rinex(e: &RinexEphemeris) -> LnavEphemeris {
LnavEphemeris {
week: e.gps_week.max(0.0) as u32,
toc_s: e.toc.gps_time_of_week(),
toe_s: e.toe,
iodc: e.iodc.max(0.0) as u32,
iode: e.iode.max(0.0) as u32,
af0: e.af0,
af1: e.af1,
af2: e.af2,
tgd: e.tgd,
crs: e.crs,
delta_n: e.delta_n,
m0: e.m0,
cuc: e.cuc,
e: e.e,
cus: e.cus,
sqrt_a: e.sqrt_a,
cic: e.cic,
omega0: e.omega0,
cis: e.cis,
i0: e.i0,
crc: e.crc,
omega: e.omega,
omega_dot: e.omega_dot,
idot: e.idot,
health: e.sv_health.max(0.0) as u32,
code_on_l2: e.data_sources.max(0.0) as u32,
}
}
pub fn lnav_frame_bits(
eph: &LnavEphemeris,
conv: &LnavConventions,
frame_start_tow_s: f64,
) -> Result<Vec<u8>, String> {
let start = frame_start_tow_s.rem_euclid(WEEK_S);
let count = ((start / 6.0).round() as u32 + 1) % 100_800;
let sf123 = encode_subframes(eph, conv, count, 0)?;
let template = data_words(eph, conv, count)?;
let mut words: Vec<u32> = sf123.iter().flatten().copied().collect();
let mut prev = sf123[2][9];
for sf in 4..=5u32 {
let mut data = [0u32; 10];
data[0] = template[0][0];
let tow = (count + sf - 1) % 100_800;
data[1] =
(tow << 7) | ((conv.alert as u32) << 6) | ((conv.anti_spoof as u32) << 5) | (sf << 2);
let enc = encode_subframe(&data, prev);
prev = enc[9];
words.extend_from_slice(&enc);
}
let mut bits = Vec::with_capacity(LNAV_FRAME_BITS);
for w in words {
for i in (0..30).rev() {
bits.push(((w >> i) & 1) as u8);
}
}
Ok(bits)
}
#[derive(Clone, Debug, Default)]
pub(crate) struct BitWindow {
pub first: i64,
pub signs: Vec<f64>,
}
impl BitWindow {
#[inline]
pub fn sign(&self, n: i64) -> f64 {
let i = n - self.first;
if i >= 0 && (i as usize) < self.signs.len() {
self.signs[i as usize]
} else {
1.0
}
}
}
pub(crate) struct NavState {
data: NavData,
frame0_tow_s: f64,
frames: Vec<(i64, Vec<u8>)>,
}
impl NavState {
pub fn new(data: NavData, frame0_tow_s: f64) -> Self {
Self {
data,
frame0_tow_s,
frames: Vec::new(),
}
}
fn frame(&mut self, f: i64) -> Result<&[u8], String> {
if let Some(pos) = self.frames.iter().position(|(k, _)| *k == f) {
return Ok(&self.frames[pos].1);
}
let NavData::Lnav { eph, conv } = &self.data else {
return Err("not an LNAV source".into());
};
let bits = lnav_frame_bits(eph.as_ref(), conv, self.frame0_tow_s + 30.0 * f as f64)?;
if self.frames.len() >= 2 {
self.frames.remove(0);
}
self.frames.push((f, bits));
Ok(&self.frames.last().expect("just pushed").1)
}
pub fn window(&mut self, first: i64, last: i64) -> Result<BitWindow, String> {
let n = (last - first + 1).max(0) as usize;
let signs = match self.data.clone() {
NavData::None => vec![1.0; n],
NavData::Seeded { seed } => {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let base = (first as i128 + (1i128 << 62)) as u128;
rng.set_word_pos(base * 2);
(0..n)
.map(|_| if rng.next_u64() & 1 == 0 { 1.0 } else { -1.0 })
.collect()
}
NavData::Lnav { .. } => {
let mut v = Vec::with_capacity(n);
for b in first..=last {
let f = b.div_euclid(LNAV_FRAME_BITS as i64);
let i = b.rem_euclid(LNAV_FRAME_BITS as i64) as usize;
let bit = self.frame(f)?[i];
v.push(if bit == 0 { 1.0 } else { -1.0 });
}
v
}
};
Ok(BitWindow { first, signs })
}
}