use crate::{
error::ParsingError,
header::{Antenna, Header, Receiver, Version},
observable::Observable,
prelude::{Duration, Epoch, TimeScale, COSPAR},
station::GroundStation,
Comments,
};
use std::{
collections::HashMap,
io::{BufRead, BufReader, Read},
str::FromStr,
};
impl Header {
pub fn parse<R: Read>(reader: &mut BufReader<R>) -> Result<Self, ParsingError> {
let mut version = Version::default();
let mut satellite = String::with_capacity(16);
let mut program = Option::<String>::None;
let mut run_by = Option::<String>::None;
let mut date = Option::<String>::None;
let mut observer = Option::<String>::None;
let mut agency = Option::<String>::None;
let mut license = Option::<String>::None;
let mut doi = Option::<String>::None;
let mut receiver = Option::<Receiver>::None;
let mut antenna = Option::<Antenna>::None;
let mut cospar = Option::<COSPAR>::None;
let mut l1_l2_date_offset = Duration::default();
let mut ground_stations = Vec::with_capacity(8);
let mut scaling_factors = HashMap::new();
let mut time_of_first_observation = Option::<Epoch>::None;
let mut time_of_last_observation = Option::<Epoch>::None;
let mut observables = Vec::<Observable>::with_capacity(8);
let mut observables_continuation = false;
let mut comments = Comments::default();
for line in reader.lines() {
if line.is_err() {
continue;
}
let line = line.unwrap();
if line.len() < 60 {
continue;
}
let (content, marker) = line.split_at(60);
let marker = marker.trim();
if marker.eq("END OF HEADER") {
break;
}
if marker.eq("COMMENT") {
comments.push(content.trim().to_string());
continue;
} else if marker.eq("RINEX VERSION / TYPE") {
let (vers, rem) = line.split_at(20);
let (type_str, rem) = rem.split_at(20);
let (constell_str, _) = rem.split_at(20);
let vers = vers.trim();
let type_str = type_str.trim();
let constell_str = constell_str.trim();
if !type_str.eq("O") {
return Err(ParsingError::InvalidDoris);
}
if !constell_str.eq("D") {
return Err(ParsingError::InvalidDoris);
}
version = Version::from_str(vers).or(Err(ParsingError::Version))?;
} else if marker.eq("PGM / RUN BY / DATE") {
let (pgm, rem) = line.split_at(20);
let pgm = pgm.trim();
if pgm.len() > 0 {
program = Some(pgm.to_string());
}
let (runby, rem) = rem.split_at(20);
let runby = runby.trim();
if runby.len() > 0 {
run_by = Some(runby.to_string());
}
let date_str = rem.split_at(20).0.trim();
if date_str.len() > 0 {
date = Some(date_str.to_string());
}
} else if marker.eq("SATELLITE NAME") {
let name = content.split_at(20).0.trim();
satellite = name.to_string();
} else if marker.eq("OBSERVER / AGENCY") {
let (obs, ag) = content.split_at(20);
let obs = obs.trim();
let ag = ag.trim();
if obs.len() > 0 {
observer = Some(obs.to_string());
}
if ag.len() > 0 {
agency = Some(ag.to_string());
}
} else if marker.eq("REC # / TYPE / VERS") {
if let Ok(rx) = Receiver::from_str(content) {
receiver = Some(rx);
}
} else if marker.eq("SYS / SCALE FACTOR") {
} else if marker.eq("LICENSE OF USE") {
let lic = content.split_at(40).0.trim();
if lic.len() > 0 {
license = Some(lic.to_string());
}
} else if marker.eq("DOI") {
let content = content.split_at(40).0.trim();
if content.len() > 0 {
doi = Some(content.to_string());
}
} else if marker.eq("ANT # / TYPE") {
let (sn, rem) = content.split_at(20);
let (model, _) = rem.split_at(20);
antenna = Some(
Antenna::default()
.with_model(model.trim())
.with_serial_number(sn.trim()),
);
} else if marker.eq("# OF STATIONS") {
} else if marker.eq("TIME OF FIRST OBS") {
time_of_first_observation = Some(Self::parse_time_of_obs(content)?);
} else if marker.eq("TIME OF LAST OBS") {
time_of_last_observation = Some(Self::parse_time_of_obs(content)?);
} else if marker.eq("SYS / # / OBS TYPES") {
if observables_continuation {
for item in content.split_ascii_whitespace() {
if let Ok(observable) = Observable::from_str(item) {
observables.push(observable);
}
}
} else {
Self::parse_observables(content, &mut observables);
observables_continuation = true;
}
} else if marker.eq("COSPAR NUMBER") {
cospar = Some(COSPAR::from_str(content.trim())?);
} else if marker.eq("L2 / L1 DATE OFFSET") {
let content = content[1..].trim();
let time_offset_us = content
.parse::<f64>()
.or(Err(ParsingError::DorisL1L2DateOffset))?;
l1_l2_date_offset = Duration::from_microseconds(time_offset_us);
} else if marker.eq("STATION REFERENCE") {
let station = GroundStation::from_str(content.trim())?;
ground_stations.push(station);
}
}
Ok(Header {
version,
comments,
program,
run_by,
date,
agency,
observer,
license,
doi,
receiver,
antenna,
cospar,
satellite,
scaling_factors,
l1_l2_date_offset,
observables,
ground_stations,
time_of_first_observation,
time_of_last_observation,
})
}
fn parse_observables(line: &str, observables: &mut Vec<Observable>) {
let items = line.split_at(6).1;
for item in items.split_ascii_whitespace() {
if let Ok(observable) = Observable::from_str(item) {
observables.push(observable);
}
}
}
fn parse_time_of_obs(content: &str) -> Result<Epoch, ParsingError> {
let (_, rem) = content.split_at(2);
let (y, rem) = rem.split_at(4);
let (m, rem) = rem.split_at(6);
let (d, rem) = rem.split_at(6);
let (hh, rem) = rem.split_at(6);
let (mm, rem) = rem.split_at(6);
let (ss, rem) = rem.split_at(5);
let (_dot, rem) = rem.split_at(1);
let (ns, rem) = rem.split_at(8);
let mut y = y
.trim()
.parse::<u32>()
.map_err(|_| ParsingError::EpochFormat)?;
if y >= 79 && y <= 99 {
y += 1900;
} else if y < 79 {
y += 2000;
}
let m = m
.trim()
.parse::<u8>()
.map_err(|_| ParsingError::EpochFormat)?;
let d = d
.trim()
.parse::<u8>()
.map_err(|_| ParsingError::EpochFormat)?;
let hh = hh
.trim()
.parse::<u8>()
.map_err(|_| ParsingError::EpochFormat)?;
let mm = mm
.trim()
.parse::<u8>()
.map_err(|_| ParsingError::EpochFormat)?;
let ss = ss
.trim()
.parse::<u8>()
.map_err(|_| ParsingError::EpochFormat)?;
let ns = ns
.trim()
.parse::<u32>()
.map_err(|_| ParsingError::EpochFormat)?;
let mut ts = TimeScale::TAI;
let rem = rem.trim();
if !rem.is_empty() && rem != "DOR" {
ts = TimeScale::from_str(rem.trim())?;
}
Epoch::from_str(&format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:08} {}",
y, m, d, hh, mm, ss, ns, ts
))
.map_err(|_| ParsingError::EpochFormat)
}
}
#[cfg(test)]
mod test {
use crate::prelude::{Epoch, Header};
use std::str::FromStr;
#[test]
fn parse_time_of_obs() {
let content = " 2021 12 21 0 0 0.0000000 GPS";
let parsed = Header::parse_time_of_obs(&content).unwrap();
assert_eq!(parsed, Epoch::from_str("2021-12-21T00:00:00 GPST").unwrap());
let content = " 1995 01 01 00 00 00.000000 ";
let parsed = Header::parse_time_of_obs(&content).unwrap();
assert_eq!(parsed, Epoch::from_str("1995-01-01T00:00:00 TAI").unwrap());
}
}