use crate::portable_math::PortableFloat;
use serde::{Deserialize, Serialize};
pub const MU: f64 = 3.986_004_418e14;
pub const OMEGA_E: f64 = 7.292_115_146_7e-5;
pub const C_LIGHT: f64 = 299_792_458.0;
pub const F_REL: f64 = -4.442_807_309e-10;
pub const WEEK_S: f64 = 604_800.0;
pub const RAC_TAU_S: f64 = 512.0;
pub const POLY_TAU_S: f64 = 64.0;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct SysTime {
pub week: u32,
pub tow: f64,
}
impl SysTime {
pub fn new(week: u32, tow: f64) -> Self {
let mut s = SysTime { week, tow };
s.normalise();
s
}
fn normalise(&mut self) {
while self.tow >= WEEK_S {
self.tow -= WEEK_S;
self.week += 1;
}
while self.tow < 0.0 && self.week > 0 {
self.tow += WEEK_S;
self.week -= 1;
}
}
pub fn plus(&self, dt: f64) -> Self {
SysTime::new(self.week, self.tow + dt)
}
pub fn minus(&self, other: &SysTime) -> f64 {
(self.week as f64 - other.week as f64) * WEEK_S + (self.tow - other.tow)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Keplerian {
pub sqrt_a: f64,
pub e: f64,
pub i0: f64,
pub omega0: f64,
pub omega: f64,
pub m0: f64,
pub delta_n: f64,
pub omega_dot: f64,
pub i_dot: f64,
pub cuc: f64,
pub cus: f64,
pub crc: f64,
pub crs: f64,
pub cic: f64,
pub cis: f64,
pub toe: f64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Liu22Extra {
pub a_dot: f64,
pub n_dot: f64,
pub crs3: f64,
pub crc3: f64,
pub crs1: f64,
pub crc1: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RacPoly {
pub tau_s: f64,
pub along: Vec<f64>,
pub cross: Vec<f64>,
pub radial: Vec<f64>,
}
impl Default for RacPoly {
fn default() -> Self {
RacPoly {
tau_s: RAC_TAU_S,
along: Vec::new(),
cross: Vec::new(),
radial: Vec::new(),
}
}
}
pub fn rac_tau_for(interval_s: f64) -> f64 {
let half = (interval_s / 2.0).max(1.0);
2f64.ppowi(half.plog2().ceil() as i32).clamp(1.0, 32768.0)
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EcefPoly {
pub t_ref: f64,
pub coeffs: [Vec<f64>; 3],
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "model", rename_all = "kebab-case")]
pub enum EphemerisModel {
Kepler16 {
kepler: Keplerian,
},
KeplerRac {
kepler: Keplerian,
rac: RacPoly,
},
Liu22 {
kepler: Keplerian,
extra: Liu22Extra,
},
EcefPoly {
poly: EcefPoly,
},
}
impl EphemerisModel {
pub fn code(&self) -> &'static str {
match self {
EphemerisModel::Kepler16 { .. } => "kepler16",
EphemerisModel::KeplerRac { .. } => "kepler-rac",
EphemerisModel::Liu22 { .. } => "liu22",
EphemerisModel::EcefPoly { .. } => "ecef-poly",
}
}
pub fn kepler(&self) -> Option<&Keplerian> {
match self {
EphemerisModel::Kepler16 { kepler }
| EphemerisModel::KeplerRac { kepler, .. }
| EphemerisModel::Liu22 { kepler, .. } => Some(kepler),
EphemerisModel::EcefPoly { .. } => None,
}
}
pub fn n_parameters(&self) -> usize {
match self {
EphemerisModel::Kepler16 { .. } => 16,
EphemerisModel::KeplerRac { rac, .. } => {
16 + rac.along.len() + rac.cross.len() + rac.radial.len()
}
EphemerisModel::Liu22 { .. } => 22,
EphemerisModel::EcefPoly { poly } => {
1 + poly.coeffs.iter().map(Vec::len).sum::<usize>()
}
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ClockPoly {
pub toc: f64,
pub af0: f64,
pub af1: f64,
pub af2: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct KlobucharSet {
pub alpha: [f64; 4],
pub beta: [f64; 4],
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct NequickSet {
pub ai0: f64,
pub ai1: f64,
pub ai2: f64,
pub storm_flags: [bool; 5],
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct UtcOffset {
pub a0: f64,
pub a1: f64,
pub dt_ls: i32,
pub t_ot: f64,
pub wn_ot: u32,
pub wn_lsf: u32,
pub dn: u8,
pub dt_lsf: i32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Services {
pub klobuchar: Option<KlobucharSet>,
pub nequick: Option<NequickSet>,
pub utc: Option<UtcOffset>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct LeoNavMessage {
pub svid: u8,
pub iod: u16,
pub band: u8,
pub health: u8,
pub week: u32,
pub tow: f64,
pub clock: Option<ClockPoly>,
pub ephemeris: EphemerisModel,
pub services: Services,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SatState {
pub pos: [f64; 3],
pub clock_s: f64,
pub frame: RacFrame,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RacFrame {
pub along: [f64; 3],
pub cross: [f64; 3],
pub radial: [f64; 3],
}
impl RacFrame {
pub fn from_rv(r: [f64; 3], v: [f64; 3]) -> RacFrame {
let radial = unit(r);
let cross = unit(cross3(r, v));
let along = cross3(cross, radial);
RacFrame {
along,
cross,
radial,
}
}
pub fn project(&self, d: [f64; 3]) -> [f64; 3] {
[dot(d, self.along), dot(d, self.cross), dot(d, self.radial)]
}
}
pub(crate) fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
pub(crate) fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
pub(crate) fn norm(a: [f64; 3]) -> f64 {
dot(a, a).sqrt()
}
pub(crate) fn unit(a: [f64; 3]) -> [f64; 3] {
let n = norm(a);
[a[0] / n, a[1] / n, a[2] / n]
}
pub(crate) fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
pub fn polyval(c: &[f64], x: f64) -> f64 {
c.iter().rev().fold(0.0, |acc, &k| acc * x + k)
}
pub fn polyder(c: &[f64], x: f64) -> f64 {
let mut acc = 0.0;
for (k, &ck) in c.iter().enumerate().skip(1).rev() {
acc = acc * x + k as f64 * ck;
}
acc
}
pub fn tk_from(week: u32, toe: f64, t: &SysTime) -> f64 {
t.minus(&SysTime { week, tow: toe })
}
#[derive(Clone, Copy, Debug)]
pub struct KeplerPoint {
pub pos: [f64; 3],
pub ecc_anomaly: f64,
pub u: f64,
pub i: f64,
pub node: f64,
}
pub fn kepler_point(k: &Keplerian, extra: Option<&Liu22Extra>, tk: f64) -> KeplerPoint {
let a0 = k.sqrt_a * k.sqrt_a;
let n0 = (MU / (a0 * a0 * a0)).sqrt();
let (a, n) = match extra {
Some(x) => (a0 + x.a_dot * tk, n0 + k.delta_n + 0.5 * x.n_dot * tk),
None => (a0, n0 + k.delta_n),
};
let mk = k.m0 + n * tk;
let mut ek = mk;
for _ in 0..30 {
let d = (ek - k.e * ek.psin() - mk) / (1.0 - k.e * ek.pcos());
ek -= d;
if d.abs() < 1e-15 {
break;
}
}
let nu = ((1.0 - k.e * k.e).sqrt() * ek.psin()).patan2(ek.pcos() - k.e);
let phi = nu + k.omega;
let (s2, c2) = (2.0 * phi).psin_cos();
let du = k.cus * s2 + k.cuc * c2;
let mut dr = k.crs * s2 + k.crc * c2;
if let Some(x) = extra {
let (s1, c1) = phi.psin_cos();
let (s3, c3) = (3.0 * phi).psin_cos();
dr += x.crs1 * s1 + x.crc1 * c1 + x.crs3 * s3 + x.crc3 * c3;
}
let di = k.cis * s2 + k.cic * c2;
let u = phi + du;
let r = a * (1.0 - k.e * ek.pcos()) + dr;
let i = k.i0 + di + k.i_dot * tk;
let (su, cu) = u.psin_cos();
let (xp, yp) = (r * cu, r * su);
let node = k.omega0 + (k.omega_dot - OMEGA_E) * tk - OMEGA_E * k.toe;
let (so, co) = node.psin_cos();
let (si, ci) = i.psin_cos();
KeplerPoint {
pos: [xp * co - yp * ci * so, xp * so + yp * ci * co, yp * si],
ecc_anomaly: ek,
u,
i,
node,
}
}
pub fn kepler_frame(ev: &KeplerPoint) -> RacFrame {
let (su, cu) = ev.u.psin_cos();
let (si, ci) = ev.i.psin_cos();
let (so, co) = ev.node.psin_cos();
RacFrame {
radial: [cu * co - su * ci * so, cu * so + su * ci * co, su * si],
along: [-su * co - cu * ci * so, -su * so + cu * ci * co, cu * si],
cross: [si * so, -si * co, ci],
}
}
pub fn kepler_relativistic_s(k: &Keplerian, ecc_anomaly: f64) -> f64 {
F_REL * k.e * k.sqrt_a * ecc_anomaly.psin()
}
pub fn ephemeris_at(model: &EphemerisModel, week: u32, t: &SysTime) -> ([f64; 3], RacFrame, f64) {
match model {
EphemerisModel::Kepler16 { kepler } => {
let ev = kepler_point(kepler, None, tk_from(week, kepler.toe, t));
(
ev.pos,
kepler_frame(&ev),
kepler_relativistic_s(kepler, ev.ecc_anomaly),
)
}
EphemerisModel::Liu22 { kepler, extra } => {
let ev = kepler_point(kepler, Some(extra), tk_from(week, kepler.toe, t));
(
ev.pos,
kepler_frame(&ev),
kepler_relativistic_s(kepler, ev.ecc_anomaly),
)
}
EphemerisModel::KeplerRac { kepler, rac } => {
let tk = tk_from(week, kepler.toe, t);
let ev = kepler_point(kepler, None, tk);
let f = kepler_frame(&ev);
let tau = tk / rac.tau_s;
let (da, dc, dr) = (
polyval(&rac.along, tau),
polyval(&rac.cross, tau),
polyval(&rac.radial, tau),
);
let mut p = ev.pos;
for (k, pk) in p.iter_mut().enumerate() {
*pk += da * f.along[k] + dc * f.cross[k] + dr * f.radial[k];
}
(p, f, kepler_relativistic_s(kepler, ev.ecc_anomaly))
}
EphemerisModel::EcefPoly { poly } => {
let dt = t.minus(&SysTime {
week,
tow: poly.t_ref,
});
let tau = dt / POLY_TAU_S;
let p = [
polyval(&poly.coeffs[0], tau),
polyval(&poly.coeffs[1], tau),
polyval(&poly.coeffs[2], tau),
];
let ve = [
polyder(&poly.coeffs[0], tau) / POLY_TAU_S,
polyder(&poly.coeffs[1], tau) / POLY_TAU_S,
polyder(&poly.coeffs[2], tau) / POLY_TAU_S,
];
let vi = [ve[0] - OMEGA_E * p[1], ve[1] + OMEGA_E * p[0], ve[2]];
let rel = -2.0 * dot(p, ve) / (C_LIGHT * C_LIGHT);
(p, RacFrame::from_rv(p, vi), rel)
}
}
}
pub fn sat_state(msg: &LeoNavMessage, t: &SysTime) -> SatState {
let (pos, frame, rel) = ephemeris_at(&msg.ephemeris, msg.week, t);
let clock_s = match &msg.clock {
Some(c) => {
let dt = t.minus(&SysTime {
week: msg.week,
tow: c.toc,
});
c.af0 + c.af1 * dt + c.af2 * dt * dt + rel
}
None => 0.0,
};
SatState {
pos,
clock_s,
frame,
}
}