use super::{Observation, Orient};
use crate::gnss_sim::Meteo;
use crate::pvt::{
assemble_epoch, klobuchar_from_nav_header, solve_spp, AtmosModel, SppMeasurement,
};
use crate::raim::snapshot_raim;
use crate::rinex::parse_nav;
use crate::rinex_obs::parse_obs;
const MASK_DEG: f64 = 5.0;
const P_MD: f64 = 1e-3;
fn predicted_range_at(rx_ecef: [f64; 3], clock_bias_m: f64, m: &SppMeasurement) -> f64 {
let dx = m.sat_ecef[0] - rx_ecef[0];
let dy = m.sat_ecef[1] - rx_ecef[1];
let dz = m.sat_ecef[2] - rx_ecef[2];
(dx * dx + dy * dy + dz * dz).sqrt() + clock_bias_m - m.sat_clock_m + m.iono_m + m.tropo_m
}
pub fn observations(
obs_rinex: &str,
nav_rinex: &str,
apriori: Option<[f64; 3]>,
sigma_m: f64,
p_fa: f64,
) -> Result<Vec<Observation>, String> {
let obs = parse_obs(obs_rinex)?;
let ephs = parse_nav(nav_rinex)?;
let atmos = AtmosModel {
iono: klobuchar_from_nav_header(nav_rinex).unwrap_or_default(),
meteo: Meteo::default(),
};
let apriori = apriori.or(obs.header.approx_xyz).ok_or_else(|| {
"no a-priori position: pass one or supply an observation-header APPROX POSITION XYZ"
.to_string()
})?;
let mut out = Vec::new();
for idx in 0..obs.epochs.len() {
let labeled = assemble_epoch(&obs, idx, &ephs, apriori, &atmos, MASK_DEG, true);
let meas: Vec<SppMeasurement> = labeled.iter().map(|(_, m)| *m).collect();
if meas.len() < 5 {
continue; }
let Some(fix) = solve_spp(&meas, apriori) else {
continue;
};
let sats: Vec<[f64; 3]> = meas.iter().map(|m| m.sat_ecef).collect();
let resid: Vec<f64> = meas
.iter()
.map(|m| m.pseudorange_m - predicted_range_at(fix.ecef, fix.clock_bias_m, m))
.collect();
if let Some(r) = snapshot_raim(fix.ecef, &sats, &resid, sigma_m, p_fa, P_MD) {
out.push(Observation::new("raim", r.test_statistic, Orient::Raw));
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_apriori_with_no_header_position_is_an_error() {
let err = observations("junk", "junk", None, 5.0, 1e-5);
assert!(err.is_err());
}
#[test]
fn malformed_observation_file_propagates_as_error_not_panic() {
let obs = "x RINEX VERSION / TYPE\nEND OF HEADER\n";
let out = observations(
obs,
"junk-nav",
Some([2_919_786.0, -5_383_745.0, 1_774_604.0]),
5.0,
1e-5,
);
assert!(out.is_err());
}
}