use super::FormatError;
use affn::cartesian::Displacement;
use affn::frames::ReferenceFrame;
use qtty::length::Millimeter;
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AntexNeu;
impl ReferenceFrame for AntexNeu {
fn frame_name() -> &'static str {
"ANTEX NEU"
}
}
pub type Pco = Displacement<AntexNeu, Millimeter>;
pub type AntennaPco = HashMap<String, Pco>;
pub type AntexCatalog = HashMap<String, AntennaPco>;
pub fn read_antex<R: Read>(rdr: R) -> Result<AntexCatalog, FormatError> {
let br = BufReader::new(rdr);
let mut catalog = AntexCatalog::new();
let mut current_antenna: Option<String> = None;
let mut current_pcos: AntennaPco = AntennaPco::new();
let mut current_freq: Option<String> = None;
for line in br.lines() {
let line = line?;
let label = line.get(60..).unwrap_or("").trim();
let body = line.get(..60).unwrap_or("");
match label {
"START OF ANTENNA" => {
current_antenna = None;
current_pcos.clear();
current_freq = None;
}
"TYPE / SERIAL NO" => {
current_antenna = Some(body.trim().to_string());
}
"START OF FREQUENCY" => {
current_freq = body.split_whitespace().next().map(|s| s.to_string());
}
"NORTH / EAST / UP" => {
if let Some(freq) = ¤t_freq {
let parts: Vec<f64> = body
.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect();
if parts.len() == 3 {
current_pcos.insert(freq.clone(), Pco::new(parts[0], parts[1], parts[2]));
}
}
}
"END OF FREQUENCY" => {
current_freq = None;
}
"END OF ANTENNA" => {
if let Some(name) = current_antenna.take() {
catalog.insert(name, std::mem::take(&mut current_pcos));
}
}
_ => {}
}
}
Ok(catalog)
}
fn header_line<W: Write>(w: &mut W, body: &str, label: &str) -> std::io::Result<()> {
let body = if body.len() > 60 { &body[..60] } else { body };
writeln!(w, "{:<60}{}", body, label)
}
pub fn write_antex<W: Write>(w: &mut W, catalog: &AntexCatalog) -> Result<(), FormatError> {
header_line(
w,
" 1.4 G ",
"ANTEX VERSION / SYST",
)?;
header_line(
w,
"ANTEX ",
"PCV TYPE / REFANT",
)?;
header_line(w, "", "END OF HEADER")?;
let mut antennas: Vec<&String> = catalog.keys().collect();
antennas.sort();
for ant in antennas {
header_line(w, "", "START OF ANTENNA")?;
header_line(w, ant, "TYPE / SERIAL NO")?;
let pcos = &catalog[ant];
let mut freqs: Vec<&String> = pcos.keys().collect();
freqs.sort();
for freq in freqs {
let pco = &pcos[freq];
header_line(w, &format!(" {:<3}", freq), "START OF FREQUENCY")?;
let body = format!(
"{:10.2}{:10.2}{:10.2}",
pco.x().value(),
pco.y().value(),
pco.z().value()
);
header_line(w, &body, "NORTH / EAST / UP")?;
header_line(w, &format!(" {:<3}", freq), "END OF FREQUENCY")?;
}
header_line(w, "", "END OF ANTENNA")?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = "\
START OF ANTENNA
TEST-ANTENNA ABC TYPE / SERIAL NO
G01 START OF FREQUENCY
1.50 0.20 90.00 NORTH / EAST / UP
END OF FREQUENCY
G02 START OF FREQUENCY
0.50 -0.30 85.00 NORTH / EAST / UP
END OF FREQUENCY
END OF ANTENNA
";
#[test]
fn parses_pco_blocks() {
let cat = read_antex(SAMPLE.as_bytes()).unwrap();
let ant = &cat["TEST-ANTENNA ABC"];
let g01 = ant["G01"];
assert!((g01.z().value() - 90.0).abs() < 1e-9);
let g02 = ant["G02"];
assert!((g02.y().value() + 0.3).abs() < 1e-9);
}
#[test]
fn round_trips_through_writer() {
let cat = read_antex(SAMPLE.as_bytes()).unwrap();
let mut buf = Vec::new();
write_antex(&mut buf, &cat).unwrap();
let cat2 = read_antex(&buf[..]).unwrap();
assert_eq!(cat.len(), cat2.len());
for (name, pcos) in &cat {
let pcos2 = cat2.get(name).expect("antenna missing after round-trip");
assert_eq!(pcos.len(), pcos2.len());
for (freq, pco) in pcos {
let pco2 = pcos2[freq];
assert!((pco.x().value() - pco2.x().value()).abs() < 1e-3);
assert!((pco.y().value() - pco2.y().value()).abs() < 1e-3);
assert!((pco.z().value() - pco2.z().value()).abs() < 1e-3);
}
}
}
}