use crate::iq::IqError;
use serde::{Deserialize, Serialize};
use std::io::Write;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct TruthRecord {
pub t_s: f64,
pub sat_id: u32,
pub visible: bool,
pub elevation_deg: f64,
pub azimuth_deg: f64,
pub cn0_dbhz: f64,
pub pseudorange_m: f64,
pub code_phase_chips: f64,
pub doppler_hz: f64,
pub carrier_phase_cycles: f64,
}
impl TruthRecord {
pub const CSV_HEADER: &'static str = "t_s,sat_id,visible,elevation_deg,azimuth_deg,cn0_dbhz,pseudorange_m,code_phase_chips,doppler_hz,carrier_phase_cycles";
pub fn csv_row(&self) -> String {
format!(
"{},{},{},{},{},{},{},{},{},{}",
self.t_s,
self.sat_id,
self.visible as u8,
self.elevation_deg,
self.azimuth_deg,
self.cn0_dbhz,
self.pseudorange_m,
self.code_phase_chips,
self.doppler_hz,
self.carrier_phase_cycles
)
}
pub fn from_csv_row(line: &str) -> Result<Self, IqError> {
let f: Vec<&str> = line.trim().split(',').collect();
if f.len() != 10 {
return Err(IqError::Format(format!(
"truth row has {} fields, expected 10",
f.len()
)));
}
let num = |i: usize| -> Result<f64, IqError> {
f[i].parse::<f64>()
.map_err(|e| IqError::Format(format!("truth field {i}: {e}")))
};
Ok(Self {
t_s: num(0)?,
sat_id: f[1]
.parse()
.map_err(|e| IqError::Format(format!("truth sat_id: {e}")))?,
visible: f[2] == "1",
elevation_deg: num(3)?,
azimuth_deg: num(4)?,
cn0_dbhz: num(5)?,
pseudorange_m: num(6)?,
code_phase_chips: num(7)?,
doppler_hz: num(8)?,
carrier_phase_cycles: num(9)?,
})
}
}
pub trait TruthSink {
fn record(&mut self, r: &TruthRecord) -> Result<(), IqError>;
fn finish(&mut self) -> Result<(), IqError> {
Ok(())
}
}
impl TruthSink for Vec<TruthRecord> {
fn record(&mut self, r: &TruthRecord) -> Result<(), IqError> {
self.push(*r);
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NullTruth;
impl TruthSink for NullTruth {
fn record(&mut self, _r: &TruthRecord) -> Result<(), IqError> {
Ok(())
}
}
pub struct CsvTruthWriter<W: Write> {
out: W,
header_written: bool,
}
impl<W: Write> CsvTruthWriter<W> {
pub fn new(out: W) -> Self {
Self {
out,
header_written: false,
}
}
pub fn into_inner(self) -> W {
self.out
}
fn header(&mut self) -> Result<(), IqError> {
if !self.header_written {
writeln!(self.out, "{}", TruthRecord::CSV_HEADER).map_err(io)?;
self.header_written = true;
}
Ok(())
}
}
impl<W: Write> TruthSink for CsvTruthWriter<W> {
fn record(&mut self, r: &TruthRecord) -> Result<(), IqError> {
self.header()?;
writeln!(self.out, "{}", r.csv_row()).map_err(io)
}
fn finish(&mut self) -> Result<(), IqError> {
self.header()?;
self.out.flush().map_err(io)
}
}
pub struct JsonLinesTruthWriter<W: Write> {
out: W,
}
impl<W: Write> JsonLinesTruthWriter<W> {
pub fn new(out: W) -> Self {
Self { out }
}
pub fn into_inner(self) -> W {
self.out
}
}
impl<W: Write> TruthSink for JsonLinesTruthWriter<W> {
fn record(&mut self, r: &TruthRecord) -> Result<(), IqError> {
let s = serde_json::to_string(r).map_err(|e| IqError::Format(e.to_string()))?;
writeln!(self.out, "{s}").map_err(io)
}
fn finish(&mut self) -> Result<(), IqError> {
self.out.flush().map_err(io)
}
}
pub fn truth_to_csv(records: &[TruthRecord]) -> String {
let mut s = String::from(TruthRecord::CSV_HEADER);
s.push('\n');
for r in records {
s.push_str(&r.csv_row());
s.push('\n');
}
s
}
pub fn truth_from_csv(text: &str) -> Result<Vec<TruthRecord>, IqError> {
let mut lines = text.lines();
match lines.next() {
Some(h) if h.trim() == TruthRecord::CSV_HEADER => {}
_ => return Err(IqError::Format("missing truth CSV header".into())),
}
lines
.filter(|l| !l.trim().is_empty())
.map(TruthRecord::from_csv_row)
.collect()
}
pub fn truth_to_json(records: &[TruthRecord]) -> Result<String, IqError> {
serde_json::to_string(records).map_err(|e| IqError::Format(e.to_string()))
}
pub fn truth_from_json(text: &str) -> Result<Vec<TruthRecord>, IqError> {
serde_json::from_str(text).map_err(|e| IqError::Format(e.to_string()))
}
fn io(e: std::io::Error) -> IqError {
IqError::Io(e.to_string())
}