#![doc(
html_logo_url = "https://raw.githubusercontent.com/nav-solutions/.github/master/logos/logo2.jpg"
)]
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate gnss_rs as gnss;
use itertools::Itertools;
#[cfg(feature = "qc")]
extern crate gnss_qc_traits as qc_traits;
use gnss::prelude::{Constellation, SV};
use hifitime::Epoch;
use prelude::ProductionAttributes;
use production::Campaign;
use std::collections::BTreeMap;
#[cfg(feature = "anise")]
#[cfg_attr(docsrs, doc(cfg(feature = "anise")))]
mod anise;
#[cfg(feature = "qc")]
#[cfg_attr(docsrs, doc(cfg(feature = "qc")))]
mod qc;
#[cfg(feature = "processing")]
#[cfg_attr(docsrs, doc(cfg(feature = "processing")))]
mod processing;
#[cfg(feature = "nyx-space")]
#[cfg_attr(docsrs, doc(cfg(feature = "nyx-space")))]
mod nyx;
#[cfg(test)]
mod tests;
mod dynamics;
mod entry;
mod errors;
mod formatting;
mod header;
mod parsing;
mod position;
mod production;
mod velocity;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "anise")]
pub use anise::{SatelliteOrbitalAttitude, SatelliteOrbitalState};
#[cfg(feature = "nyx-space")]
pub use nyx::{SpacecraftModel, SpacecraftTrajectory};
use header::Header;
use hifitime::Unit;
use entry::SP3Entry;
use errors::*;
type Vector3D = (f64, f64, f64);
pub mod prelude {
pub use crate::{
entry::SP3Entry,
errors::{Error, FormattingError, ParsingError},
header::{version::Version, DataType, Header, OrbitType},
production::{Availability, ProductionAttributes, ReleaseDate, ReleasePeriod},
SP3Key, SP3,
};
#[cfg(feature = "qc")]
pub use gnss_qc_traits::{Merge, Timeshift};
#[cfg(feature = "processing")]
pub use gnss_qc_traits::Split;
pub use gnss::prelude::{Constellation, SV};
pub use hifitime::{Duration, Epoch, TimeScale};
#[cfg(feature = "anise")]
pub use anise::{
constants::frames::{EARTH_J2000, IAU_EARTH_FRAME},
prelude::{Almanac, Frame},
};
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SP3Key {
pub sv: SV,
pub epoch: Epoch,
}
#[derive(Default, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SP3 {
pub header: Header,
pub comments: Vec<String>,
pub prod_attributes: Option<ProductionAttributes>,
pub data: BTreeMap<SP3Key, SP3Entry>,
}
use crate::prelude::{Availability, DataType, ReleasePeriod};
pub(crate) fn lagrange_interpolation(
order: usize,
t: Epoch,
x: Vec<(Epoch, Vector3D)>,
) -> Option<Vector3D> {
let x_len = x.len();
let mut polynomials = Vector3D::default();
if x_len < order + 1 {
return None;
}
for i in 0..order + 1 {
let mut l_i = 1.0_f64;
let (t_i, (x_km_i, y_km_i, z_km_i)) = x[i];
for j in 0..order + 1 {
let (t_j, _) = x[j];
if j != i {
l_i *= (t - t_j).to_seconds();
l_i /= (t_i - t_j).to_seconds();
}
}
polynomials.0 += x_km_i * l_i;
polynomials.1 += y_km_i * l_i;
polynomials.2 += z_km_i * l_i;
}
Some(polynomials)
}
impl SP3 {
pub fn first_epoch(&self) -> Option<Epoch> {
self.epochs_iter().nth(0)
}
pub fn last_epoch(&self) -> Option<Epoch> {
self.epochs_iter().last()
}
pub fn has_satellite_velocity(&self) -> bool {
self.header.data_type == DataType::Velocity
}
pub fn has_satellite_positions_prediction(&self) -> bool {
self.data
.iter()
.filter_map(|(k, v)| {
if v.predicted_orbit {
Some((k, v))
} else {
None
}
})
.count()
> 0
}
pub fn has_satellite_clock_event(&self) -> bool {
self.satellites_epoch_clock_event_iter().count() > 0
}
pub fn has_satellite_clock_offset(&self) -> bool {
self.satellites_clock_offset_sec_iter().count() > 0
}
pub fn has_satellite_clock_drift(&self) -> bool {
self.satellites_clock_drift_sec_sec_iter().count() > 0
}
pub fn has_satellite_maneuver(&self) -> bool {
self.satellites_epoch_maneuver_iter().count() > 0
}
pub fn has_steady_sampling(&self) -> bool {
let dt = self.header.sampling_period;
let mut t = Epoch::default();
let mut past_t = Option::<Epoch>::None;
for now in self.epochs_iter() {
if now > t {
if let Some(past_t) = past_t {
if now - past_t != dt {
return false;
}
}
t = now;
}
past_t = Some(now);
}
true
}
pub fn standardized_filename(&self) -> String {
let mut batch_id = 0;
let mut campaign = Campaign::default();
let mut avail = Availability::default();
let mut release_period = ReleasePeriod::default();
let mut agency = self.header.agency[..3].to_string();
let mut extension = "";
if let Some(attributes) = &self.prod_attributes {
batch_id = attributes.batch_id;
avail = attributes.availability;
campaign = attributes.campaign;
release_period = attributes.release_period;
agency = attributes.agency.clone();
extension = ".gz";
}
let (year, doy) = (
self.header.release_epoch.year(),
self.header.release_epoch.day_of_year() as u16,
);
let doy_padding = if doy < 100 { "00000" } else { "0000" };
let sampling_period_mins = (self.header.sampling_period.to_seconds() / 60.0).round() as u16;
format!(
"{}{}{}{}_{}{:03}{}_{}_{:02}M_ORB.SP3{}",
agency,
batch_id,
campaign,
avail,
year,
doy as u16,
doy_padding,
release_period,
sampling_period_mins,
extension,
)
}
pub fn total_epochs(&self) -> usize {
self.epochs_iter().count()
}
pub fn epochs_iter(&self) -> impl Iterator<Item = Epoch> + '_ {
self.data.keys().map(|k| k.epoch).unique()
}
pub fn constellations_iter(&self) -> impl Iterator<Item = Constellation> + '_ {
self.satellites_iter().map(|sv| sv.constellation).unique()
}
pub fn comments_iter(&self) -> impl Iterator<Item = &String> + '_ {
self.comments.iter()
}
pub fn satellites_iter(&self) -> impl Iterator<Item = SV> + '_ {
self.header.satellites.iter().copied()
}
pub fn satellites_position_km_iter(
&self,
) -> Box<dyn Iterator<Item = (Epoch, SV, bool, bool, Vector3D)> + '_> {
Box::new(self.data.iter().filter_map(|(k, v)| {
Some((k.epoch, k.sv, v.predicted_orbit, v.maneuver, v.position_km))
}))
}
pub fn satellites_stable_position_km_iter(
&self,
) -> Box<dyn Iterator<Item = (Epoch, SV, bool, Vector3D)> + '_> {
Box::new(self.satellites_position_km_iter().filter_map(
|(t, sv, predicted, maneuvered, coords)| {
if !maneuvered {
Some((t, sv, predicted, coords))
} else {
None
}
},
))
}
pub fn satellites_stable_fitted_position_km_iter(
&self,
) -> Box<dyn Iterator<Item = (Epoch, SV, Vector3D)> + '_> {
Box::new(self.satellites_stable_position_km_iter().filter_map(
|(t, sv, predicted, coords)| {
if !predicted {
Some((t, sv, coords))
} else {
None
}
},
))
}
pub fn satellites_stable_predicted_position_km_iter(
&self,
) -> Box<dyn Iterator<Item = (Epoch, SV, Vector3D)> + '_> {
Box::new(self.satellites_stable_position_km_iter().filter_map(
|(t, sv, predicted, coords)| {
if predicted {
Some((t, sv, coords))
} else {
None
}
},
))
}
pub fn satellites_epoch_maneuver_iter(&self) -> Box<dyn Iterator<Item = (Epoch, SV)> + '_> {
Box::new(self.data.iter().filter_map(|(k, v)| {
if v.maneuver {
Some((k.epoch, k.sv))
} else {
None
}
}))
}
pub fn satellites_epoch_clock_event_iter(&self) -> Box<dyn Iterator<Item = (Epoch, SV)> + '_> {
Box::new(self.data.iter().filter_map(|(k, v)| {
if v.clock_event {
Some((k.epoch, k.sv))
} else {
None
}
}))
}
pub fn satellites_velocity_km_s_iter(
&self,
) -> Box<dyn Iterator<Item = (Epoch, SV, Vector3D)> + '_> {
Box::new(self.data.iter().filter_map(|(k, v)| {
if !v.maneuver {
let velocity_km_s = v.velocity_km_s?;
Some((k.epoch, k.sv, velocity_km_s))
} else {
None
}
}))
}
pub fn satellites_pos_vel_km_iter(
&self,
) -> Box<dyn Iterator<Item = (Epoch, SV, Vector3D, Vector3D)> + '_> {
Box::new(self.data.iter().filter_map(|(k, v)| {
if !v.maneuver {
let position_km = v.position_km;
let velocity_km = v.velocity_km_s?;
Some((k.epoch, k.sv, position_km, velocity_km))
} else {
None
}
}))
}
pub fn satellites_clock_offset_sec_iter(&self) -> impl Iterator<Item = (Epoch, SV, f64)> + '_ {
self.data.iter().filter_map(|(k, v)| {
let clock = v.clock_us? * 1.0E-6;
Some((k.epoch, k.sv, clock))
})
}
pub fn satellites_clock_drift_sec_sec_iter(
&self,
) -> impl Iterator<Item = (Epoch, SV, f64)> + '_ {
self.data.iter().filter_map(|(k, v)| {
let rate = v.clock_drift_ns? * 1.0E-9;
Some((k.epoch, k.sv, rate))
})
}
pub fn satellite_position_interpolate(
&self,
sv: SV,
t: Epoch,
order: usize,
interp: fn(usize, Epoch, Vec<(Epoch, Vector3D)>) -> Option<Vector3D>,
) -> Option<Vector3D> {
let odd_order = order % 2 > 0;
if !odd_order {
panic!("even interpolation order is not supported");
}
let smallest_dt = 2.0 * Unit::Nanosecond;
let target_len = order + 1;
let target_len_2 = target_len / 2;
let target_len_2_1 = target_len_2 - 1;
let mut past_t = Epoch::default();
let mut t_x = Option::<Epoch>::None;
let mut tx_perfect_match = false;
let (mut w0_len, mut w1_len) = (0, 0);
let mut window = Vec::<(Epoch, Vector3D)>::with_capacity(target_len);
for (index_i, (t_i, sv_i, _, (x_i, y_i, z_i))) in
self.satellites_stable_position_km_iter().enumerate()
{
if sv_i != sv {
past_t = t_i;
continue;
}
window.push((t_i, (x_i, y_i, z_i)));
let win_len = window.len();
if win_len > target_len {
window.remove(0);
}
if t_x.is_none() {
if past_t < t && t_i >= t {
w0_len = index_i;
t_x = Some(t_i);
if (t_i - t).abs() < smallest_dt {
tx_perfect_match = true;
}
}
} else {
if index_i == w0_len + target_len_2 - 1 {
w1_len = target_len_2;
break;
}
}
past_t = t_i;
}
t_x?;
if w0_len < target_len_2 {
return None;
}
if tx_perfect_match {
if w1_len < target_len_2_1 {
return None;
}
} else if w1_len < target_len_2 {
return None;
}
interp(order, t, window)
}
pub fn satellite_position_lagrangian_interpolation(
&self,
sv: SV,
t: Epoch,
order: usize,
) -> Option<Vector3D> {
self.satellite_position_interpolate(sv, t, order, lagrange_interpolation)
}
pub fn satellite_position_lagrangian_9_interpolation(
&self,
sv: SV,
t: Epoch,
) -> Option<Vector3D> {
self.satellite_position_lagrangian_interpolation(sv, t, 9)
}
pub fn satellite_position_lagrangian_11_interpolation(
&self,
sv: SV,
t: Epoch,
) -> Option<Vector3D> {
self.satellite_position_lagrangian_interpolation(sv, t, 11)
}
pub fn satellite_position_lagrangian_17_interpolation(
&self,
sv: SV,
t: Epoch,
) -> Option<Vector3D> {
self.satellite_position_lagrangian_interpolation(sv, t, 17)
}
}