use crate::{
coords::{AzimuthElevation, ECEF},
signal::{Code, Constellation, GnssSignal, InvalidGnssSignal},
time::GpsTime,
};
use std::error::Error;
use std::fmt;
pub const GAL_INAV_CONTENT_BYTE: usize = (128 + 8 - 1) / 8;
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub enum InvalidEphemeris {
Null,
Invalid,
WnEqualsZero,
FitIntervalEqualsZero,
Unhealthy,
TooOld,
InvalidSid,
InvalidIod,
}
impl fmt::Display for InvalidEphemeris {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Invalid ephemeris ({:?})", self)
}
}
impl Error for InvalidEphemeris {}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub enum Status {
Invalid(InvalidEphemeris),
Valid,
}
impl Status {
fn from_ephemeris_status_t(value: swiftnav_sys::ephemeris_status_t) -> Status {
match value {
swiftnav_sys::ephemeris_status_t_EPH_NULL => Status::Invalid(InvalidEphemeris::Null),
swiftnav_sys::ephemeris_status_t_EPH_INVALID => {
Status::Invalid(InvalidEphemeris::Invalid)
}
swiftnav_sys::ephemeris_status_t_EPH_WN_EQ_0 => {
Status::Invalid(InvalidEphemeris::WnEqualsZero)
}
swiftnav_sys::ephemeris_status_t_EPH_FIT_INTERVAL_EQ_0 => {
Status::Invalid(InvalidEphemeris::FitIntervalEqualsZero)
}
swiftnav_sys::ephemeris_status_t_EPH_UNHEALTHY => {
Status::Invalid(InvalidEphemeris::Unhealthy)
}
swiftnav_sys::ephemeris_status_t_EPH_TOO_OLD => {
Status::Invalid(InvalidEphemeris::TooOld)
}
swiftnav_sys::ephemeris_status_t_EPH_VALID => Status::Valid,
_ => panic!("Invalid ephemeris_status_t value: {}", value),
}
}
pub fn to_result(self) -> Result<(), InvalidEphemeris> {
match self {
Status::Valid => Ok(()),
Status::Invalid(invalid_status) => Err(invalid_status),
}
}
}
#[derive(Clone)]
pub enum EphemerisTerms {
Kepler(swiftnav_sys::ephemeris_kepler_t),
Xyz(swiftnav_sys::ephemeris_xyz_t),
Glo(swiftnav_sys::ephemeris_glo_t),
}
impl EphemerisTerms {
#[allow(clippy::too_many_arguments)]
pub fn new_kepler(
constellation: Constellation,
tgd: [f32; 2],
crc: f64,
crs: f64,
cuc: f64,
cus: f64,
cic: f64,
cis: f64,
dn: f64,
m0: f64,
ecc: f64,
sqrta: f64,
omega0: f64,
omegadot: f64,
w: f64,
inc: f64,
inc_dot: f64,
af0: f64,
af1: f64,
af2: f64,
toc: GpsTime,
iodc: u16,
iode: u16,
) -> EphemerisTerms {
EphemerisTerms::Kepler(swiftnav_sys::ephemeris_kepler_t {
tgd: match constellation {
Constellation::Gps => swiftnav_sys::ephemeris_kepler_t__bindgen_ty_1 { gps_s: tgd },
Constellation::Qzs => {
swiftnav_sys::ephemeris_kepler_t__bindgen_ty_1 { qzss_s: tgd }
}
Constellation::Bds => swiftnav_sys::ephemeris_kepler_t__bindgen_ty_1 { bds_s: tgd },
Constellation::Gal => swiftnav_sys::ephemeris_kepler_t__bindgen_ty_1 { gal_s: tgd },
_ => panic!("Invalid constellation for a Kepler ephemeris"),
},
crc,
crs,
cuc,
cus,
cic,
cis,
dn,
m0,
ecc,
sqrta,
omega0,
omegadot,
w,
inc,
inc_dot,
af0,
af1,
af2,
toc: toc.to_gps_time_t(),
iodc,
iode,
})
}
pub fn new_xyz(
pos: [f64; 3],
vel: [f64; 3],
acc: [f64; 3],
a_gf0: f64,
a_gf1: f64,
) -> EphemerisTerms {
EphemerisTerms::Xyz(swiftnav_sys::ephemeris_xyz_t {
pos,
vel,
acc,
a_gf0,
a_gf1,
})
}
#[allow(clippy::too_many_arguments)]
pub fn new_glo(
gamma: f64,
tau: f64,
d_tau: f64,
pos: [f64; 3],
vel: [f64; 3],
acc: [f64; 3],
fcn: u16,
iod: u8,
) -> EphemerisTerms {
EphemerisTerms::Glo(swiftnav_sys::ephemeris_glo_t {
gamma,
tau,
d_tau,
pos,
vel,
acc,
fcn,
iod,
})
}
}
pub struct Ephemeris(swiftnav_sys::ephemeris_t);
impl Ephemeris {
#[allow(clippy::too_many_arguments)]
pub fn new(
sid: crate::signal::GnssSignal,
toe: crate::time::GpsTime,
ura: f32,
fit_interval: u32,
valid: u8,
health_bits: u8,
source: u8,
terms: EphemerisTerms,
) -> Ephemeris {
Ephemeris(swiftnav_sys::ephemeris_t {
sid: sid.to_gnss_signal_t(),
toe: toe.to_gps_time_t(),
ura,
fit_interval,
valid,
health_bits,
source,
data: match terms {
EphemerisTerms::Kepler(c_kepler) => {
assert!(matches!(
sid.to_constellation(),
Constellation::Gps
| Constellation::Gal
| Constellation::Bds
| Constellation::Qzs
));
swiftnav_sys::ephemeris_t__bindgen_ty_1 { kepler: c_kepler }
}
EphemerisTerms::Xyz(c_xyz) => {
assert_eq!(sid.to_constellation(), Constellation::Sbas);
swiftnav_sys::ephemeris_t__bindgen_ty_1 { xyz: c_xyz }
}
EphemerisTerms::Glo(c_glo) => {
assert_eq!(sid.to_constellation(), Constellation::Glo);
swiftnav_sys::ephemeris_t__bindgen_ty_1 { glo: c_glo }
}
},
})
}
pub fn decode_gps(frame_words: &[[u32; 8]; 3], tot_tow: f64) -> Ephemeris {
let mut e = Ephemeris::default();
unsafe {
swiftnav_sys::decode_ephemeris(frame_words, e.mut_c_ptr(), tot_tow);
}
e
}
pub fn decode_bds(words: &[[u32; 10]; 3], sid: GnssSignal) -> Ephemeris {
let mut e = Ephemeris::default();
unsafe {
swiftnav_sys::decode_bds_d1_ephemeris(words, sid.to_gnss_signal_t(), e.mut_c_ptr());
}
e
}
pub fn decode_gal(page: &[[u8; GAL_INAV_CONTENT_BYTE]; 5]) -> Ephemeris {
let mut e = Ephemeris::default();
unsafe {
swiftnav_sys::decode_gal_ephemeris(page, e.mut_c_ptr());
}
e
}
pub(crate) fn mut_c_ptr(&mut self) -> *mut swiftnav_sys::ephemeris_t {
&mut self.0
}
pub fn calc_satellite_state(&self, t: GpsTime) -> Result<SatelliteState, InvalidEphemeris> {
self.detailed_status(t).to_result()?;
let mut sat = SatelliteState {
pos: ECEF::default(),
vel: ECEF::default(),
acc: ECEF::default(),
clock_err: 0.0,
clock_rate_err: 0.0,
iodc: 0,
iode: 0,
};
let result = unsafe {
swiftnav_sys::calc_sat_state(
&self.0,
t.c_ptr(),
sat.pos.as_mut_array_ref(),
sat.vel.as_mut_array_ref(),
sat.acc.as_mut_array_ref(),
&mut sat.clock_err,
&mut sat.clock_rate_err,
)
};
assert_eq!(result, 0);
Ok(sat)
}
pub fn calc_satellite_az_el(
&self,
t: GpsTime,
pos: ECEF,
) -> Result<AzimuthElevation, InvalidEphemeris> {
self.detailed_status(t).to_result()?;
let mut sat = AzimuthElevation::default();
let result = unsafe {
swiftnav_sys::calc_sat_az_el(
&self.0,
t.c_ptr(),
pos.as_array_ref(),
swiftnav_sys::satellite_orbit_type_t_MEO,
&mut sat.az,
&mut sat.el,
true,
)
};
assert_eq!(result, 0);
Ok(sat)
}
pub fn calc_satellite_doppler(
&self,
t: GpsTime,
pos: ECEF,
vel: ECEF,
) -> Result<f64, InvalidEphemeris> {
self.detailed_status(t).to_result()?;
let mut doppler = 0.0;
let result = unsafe {
swiftnav_sys::calc_sat_doppler(
&self.0,
t.c_ptr(),
pos.as_array_ref(),
vel.as_array_ref(),
swiftnav_sys::satellite_orbit_type_t_MEO,
&mut doppler,
)
};
assert_eq!(result, 0);
Ok(doppler)
}
pub fn sid(&self) -> Result<GnssSignal, InvalidGnssSignal> {
GnssSignal::from_gnss_signal_t(self.0.sid)
}
pub fn status(&self) -> Status {
Status::from_ephemeris_status_t(unsafe { swiftnav_sys::get_ephemeris_status_t(&self.0) })
}
pub fn detailed_status(&self, t: GpsTime) -> Status {
Status::from_ephemeris_status_t(unsafe {
swiftnav_sys::ephemeris_valid_detailed(&self.0, t.c_ptr())
})
}
pub fn is_valid_at_time(&self, t: GpsTime) -> bool {
let result = unsafe { swiftnav_sys::ephemeris_valid(&self.0, t.c_ptr()) };
result == 1
}
pub fn is_healthy(&self, code: &Code) -> bool {
unsafe { swiftnav_sys::ephemeris_healthy(&self.0, code.to_code_t()) }
}
}
impl PartialEq for Ephemeris {
fn eq(&self, other: &Self) -> bool {
unsafe { swiftnav_sys::ephemeris_equal(&self.0, &other.0) }
}
}
impl Eq for Ephemeris {}
impl Default for Ephemeris {
fn default() -> Self {
unsafe { std::mem::zeroed::<Ephemeris>() }
}
}
pub struct SatelliteState {
pub pos: ECEF,
pub vel: ECEF,
pub acc: ECEF,
pub clock_err: f64,
pub clock_rate_err: f64,
pub iodc: u16,
pub iode: u8,
}
#[cfg(test)]
mod tests {
use crate::ephemeris::{Ephemeris, EphemerisTerms};
use crate::signal::{Code, Constellation, GnssSignal};
use crate::time::GpsTime;
use std::os::raw::c_int;
#[test]
fn bds_decode() {
let expected_ephemeris = Ephemeris::new(
GnssSignal::new(25, Code::Bds2B1).unwrap(), GpsTime::new_unchecked(2091, 460800.0), 2.0, 0, 0, 0, 0, EphemerisTerms::new_kepler(
Constellation::Bds,
[-2.99999997e-10, -2.99999997e-10], 167.140625, -18.828125, -9.0105459094047546e-07, 9.4850547611713409e-06, -4.0978193283081055e-08, 1.0104849934577942e-07, 3.9023054038264214e-09, 0.39869951815527438, 0.00043709692545235157, 5282.6194686889648, 2.2431156200949509, -6.6892072037584707e-09, 0.39590413040186828, 0.95448398903792575, -6.2716898124832475e-10, -0.00050763087347149849, -1.3019807454384136e-11, 0.000000, GpsTime::new_unchecked(2091, 460800.), 160, 160, ),
);
let words: [[u32; 10]; 3] = [
[
0x38901714, 0x5F81035, 0x5BEE184, 0x3FDF95, 0x3D0B09CA, 0x3C47CDE6, 0x19AC7AD,
0x24005E73, 0x2ED79F72, 0x38D7A13C,
],
[
0x38902716, 0x610AAF9, 0x2EFE1C86, 0x1103E979, 0x18E80030, 0x394A8A9E, 0x4F9109A,
0x29C9FE18, 0x34BA516C, 0x13D2B18F,
],
[
0x38903719, 0x62B0869, 0x4DC786, 0x1087FF8F, 0x3D47FD49, 0x2DAE0084, 0x1B3C9264,
0xB6C9161, 0x1B58811D, 0x2DC18C7,
],
];
let sid = GnssSignal::new(25, Code::Bds2B1).unwrap();
let decoded_eph = Ephemeris::decode_bds(&words, sid);
assert!(expected_ephemeris == decoded_eph);
}
#[test]
fn gal_decode() {
use super::GAL_INAV_CONTENT_BYTE;
let expected_ephemeris = Ephemeris::new(
GnssSignal::new(8, Code::GalE1b).unwrap(), GpsTime::new_unchecked(2090, 135000.), 3.120000, 14400, 1, 0, 0, EphemerisTerms::new_kepler(
Constellation::Gal,
[-5.5879354476928711e-09, -6.5192580223083496e-09], 62.375, -54.0625, -2.3748725652694702e-06, 1.2902542948722839e-05, 7.4505805969238281e-09, 4.6566128730773926e-08, 2.9647663515616992e-09, 1.1731263781996162, 0.00021702353842556477, 5440.6276874542236, 0.7101536200630526, -5.363080536688408e-09, 0.39999676368790066, 0.95957029480011957, 4.3751822439020375e-10, 0.0062288472545333198, -5.4427573559223666e-12, 0., GpsTime::new_unchecked(2090, 135000.), 97, 97, ),
);
let words: [[u8; GAL_INAV_CONTENT_BYTE]; 5] = [
[
0x4, 0x61, 0x23, 0x28, 0xBF, 0x30, 0x9B, 0xA0, 0x0, 0x71, 0xC8, 0x6A, 0xA8, 0x14,
0x16, 0x7,
],
[
0x8, 0x61, 0x1C, 0xEF, 0x2B, 0xC3, 0x27, 0x18, 0xAE, 0x65, 0x10, 0x4C, 0x1E, 0x1A,
0x13, 0x25,
],
[
0xC, 0x61, 0xFF, 0xC5, 0x58, 0x20, 0x6D, 0xFB, 0x5, 0x1B, 0xF, 0x7, 0xCC, 0xF9,
0x3E, 0x6B,
],
[
0x10, 0x61, 0x20, 0x0, 0x10, 0x0, 0x64, 0x8C, 0xA0, 0xCC, 0x1B, 0x5B, 0xBF, 0xFE,
0x81, 0x1,
],
[
0x14, 0x50, 0x80, 0x20, 0x5, 0x81, 0xF4, 0x7C, 0x80, 0x21, 0x51, 0x9, 0xB6, 0xAA,
0xAA, 0xAA,
],
];
let mut decoded_eph = Ephemeris::decode_gal(&words);
decoded_eph.0.sid.code = Code::GalE1b as c_int;
decoded_eph.0.valid = 1;
assert!(expected_ephemeris == decoded_eph);
}
}