use itertools::Itertools;
use std::cmp::Ordering;
use crate::prelude::{Candidate, Carrier};
#[derive(Debug, Copy, Clone, Default, PartialEq)]
pub struct Observation {
pub carrier: Carrier,
pub pseudo_range_m: Option<f64>,
pub phase_range_m: Option<f64>,
pub doppler: Option<f64>,
pub snr_dbhz: Option<f64>,
pub(crate) ambiguity: Option<f64>,
}
impl Observation {
pub fn pseudo_range(carrier: Carrier, range_m: f64, snr_dbhz: Option<f64>) -> Self {
Self {
snr_dbhz,
carrier,
doppler: None,
ambiguity: None,
phase_range_m: None,
pseudo_range_m: Some(range_m),
}
}
pub fn ambiguous_phase_range(carrier: Carrier, range_m: f64, snr_dbhz: Option<f64>) -> Self {
Self {
snr_dbhz,
carrier,
doppler: None,
ambiguity: None,
pseudo_range_m: None,
phase_range_m: Some(range_m),
}
}
pub fn doppler(carrier: Carrier, doppler: f64, snr_dbhz: Option<f64>) -> Self {
Self {
snr_dbhz,
carrier,
ambiguity: None,
doppler: Some(doppler),
pseudo_range_m: None,
phase_range_m: None,
}
}
pub fn with_ambiguous_phase_range_m(mut self, phase_range_m: f64) -> Self {
self.ambiguity = None;
self.phase_range_m = Some(phase_range_m);
self
}
pub fn with_pseudo_range_m(mut self, pseudo_range_m: f64) -> Self {
self.pseudo_range_m = Some(pseudo_range_m);
self
}
pub fn with_doppler(mut self, doppler_hz_hz: f64) -> Self {
self.doppler = Some(doppler_hz_hz);
self
}
}
impl Candidate {
pub(crate) fn pseudo_range_iter(&self) -> Box<dyn Iterator<Item = (Carrier, f64)> + '_> {
Box::new(self.observations.iter().filter_map(|ob| {
let pseudo = ob.pseudo_range_m?;
Some((ob.carrier, pseudo))
}))
}
pub(crate) fn phase_range_iter(&self) -> Box<dyn Iterator<Item = (Carrier, f64)> + '_> {
Box::new(self.observations.iter().filter_map(|ob| {
let phase = ob.phase_range_m?;
Some((ob.carrier, phase))
}))
}
pub(crate) fn best_snr_observation(&self) -> Option<Observation> {
self.observations
.iter()
.max_by(|ob_a, ob_b| {
if let Some(snr_a) = ob_a.snr_dbhz {
if let Some(snr_b) = ob_b.snr_dbhz {
snr_a.partial_cmp(&snr_b).unwrap()
} else {
Ordering::Greater
}
} else {
Ordering::Less
}
})
.cloned()
}
pub(crate) fn best_snr_range_m(&self) -> Option<(Carrier, f64)> {
let obs = self.best_snr_observation()?;
let range_m = obs.pseudo_range_m?;
Some((obs.carrier, range_m))
}
pub(crate) fn cpp_compatible(&self) -> bool {
self.has_dual_pseudo_range()
}
pub(crate) fn ppp_compatible(&self) -> bool {
self.has_dual_pseudo_range() && self.has_dual_phase_range()
}
pub(crate) fn has_dual_pseudo_range(&self) -> bool {
self.pseudo_range_iter()
.map(|(signal, _)| (signal.frequency_mega_hz() * 100.0) as u32)
.unique()
.count()
> 1
}
pub(crate) fn has_dual_phase_range(&self) -> bool {
self.phase_range_iter()
.map(|(signal, _)| (signal.frequency_mega_hz() * 100.0) as u32)
.unique()
.count()
> 1
}
pub(crate) fn l1_pseudo_range(&self) -> Option<(Carrier, f64)> {
let l1 = self
.observations
.iter()
.filter(|ob| ob.carrier.is_l1() && ob.pseudo_range_m.is_some())
.reduce(|k, _| k)?;
Some((l1.carrier, l1.pseudo_range_m.unwrap()))
}
pub(crate) fn prefered_carrier(&self) -> Option<Carrier> {
let (carrier, _) = self.l1_pseudo_range()?;
Some(carrier)
}
pub(crate) fn l1_phase_range(&self) -> Option<(Carrier, f64)> {
let l1 = self
.observations
.iter()
.filter(|ob| ob.carrier.is_l1() && ob.phase_range_m.is_some())
.reduce(|k, _| k)?;
let mut l_1 = l1.phase_range_m.unwrap();
let lambda_1 = l1.carrier.wavelength();
l_1 += l1.ambiguity.unwrap_or_default() * lambda_1;
Some((l1.carrier, l_1))
}
pub(crate) fn subsidary_pseudo_range(&self) -> Option<(Carrier, f64)> {
let lj = self
.observations
.iter()
.filter(|ob| !ob.carrier.is_l1() && ob.pseudo_range_m.is_some())
.reduce(|k, _| k)?;
Some((lj.carrier, lj.pseudo_range_m.unwrap()))
}
pub(crate) fn subsidary_phase_range(&self) -> Option<(Carrier, f64)> {
let lj = self
.observations
.iter()
.filter(|ob| !ob.carrier.is_l1() && ob.phase_range_m.is_some())
.reduce(|k, _| k)?;
let mut l_j = lj.phase_range_m.unwrap();
let lambda_j = lj.carrier.wavelength();
l_j += lj.ambiguity.unwrap_or_default() * lambda_j;
Some((lj.carrier, l_j))
}
pub(crate) fn min_c_n0_mask(&mut self, min_snr_dbhz: f64) {
self.observations.retain(|ob| {
if let Some(snr_dbhz) = ob.snr_dbhz {
snr_dbhz > min_snr_dbhz
} else {
true
}
});
}
}
#[cfg(test)]
mod test {
use crate::prelude::{Candidate, Carrier, Epoch, Observation, SV};
use std::str::FromStr;
#[test]
fn gps_l1_observation() {
let t0 = Epoch::from_str("2000-01-01T00:00:00 UTC").unwrap();
let g01 = SV::from_str("G01").unwrap();
let (l1, l2, l5) = (Carrier::L1, Carrier::L2, Carrier::L5);
let cd = Candidate::new(
g01,
t0,
vec![Observation::pseudo_range(Carrier::L1, 0.1, None)],
);
assert_eq!(cd.l1_pseudo_range(), Some((l1, 0.1)));
assert!(cd.l1_phase_range().is_none());
assert!(cd.subsidary_phase_range().is_none());
let cd = Candidate::new(
g01,
t0,
vec![
Observation::pseudo_range(l1, 0.1, None),
Observation::pseudo_range(l2, 0.2, None),
],
);
assert_eq!(cd.l1_pseudo_range(), Some((l1, 0.1)));
assert_eq!(cd.subsidary_pseudo_range(), Some((l2, 0.2)));
assert!(cd.code_if_combination().is_some());
assert!(cd.l1_phase_range().is_none());
assert!(cd.subsidary_phase_range().is_none());
let cd = Candidate::new(
g01,
t0,
vec![
Observation::pseudo_range(l1, 0.1, None),
Observation::ambiguous_phase_range(l1, 0.2, None),
],
);
assert_eq!(cd.l1_pseudo_range(), Some((l1, 0.1)));
assert_eq!(cd.l1_phase_range(), Some((l1, 0.2)));
assert!(cd.subsidary_pseudo_range().is_none());
assert!(cd.subsidary_phase_range().is_none());
let cd = Candidate::new(
g01,
t0,
vec![
Observation::ambiguous_phase_range(l1, 0.1, None),
Observation::ambiguous_phase_range(l5, 0.5, None),
],
);
assert_eq!(cd.l1_phase_range(), Some((l1, 0.1)));
assert!(cd.l1_pseudo_range().is_none());
assert!(cd.subsidary_pseudo_range().is_none());
assert!(cd.code_if_combination().is_none());
assert!(cd.phase_if_combination().is_some());
}
}