From e05eb6b0264da808dc97785a4d68cdc5eca08626 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Sun, 31 Mar 2024 03:51:32 -0700 Subject: [PATCH] Handle (ignore) observation epochs containing event flags (#226) * Fix meteorological is_new_epoch version check * Verify that epochs have a minimum number of fields When parsing an epoch we want to ensure that we can at least read the date and time fields. This function will end up consuming non-epoch lines occasionally and we want to be sure that we bail early with an error rather than either barreling ahead and triggering an "invalid Gregorian date" panic or (even worse) assuming the bad parse is valid. * epoch: Add formatting checks to cargo tests Use the "format" function instead of the "format!" macro to verify that the module produces correctly-formatted epoch strings. * Make EpochFlag processing the responsibility of the observation module Epoch flags are only used by observations, so it does not make sense to have their processing done within the epoch module. Moves processing and testing of the parsing and formatting of the flags over to the observation module. * Split parsing of normal observations and events into dedicated functions We don't yet actually *do* anything with events, but we can at least return an Error to prevent the rest of the system from trying to treat them as normal observations. * Handle observations without significant epoch Event flags 2 - 5 allow the epoch fields to be left blank if they are not "significant". * Move Epoch Flag to Observation RINEX dedicated section * add one comment --------- Signed-off-by: Jason Gerecke Signed-off-by: Guillaume W. Bres Co-authored-by: Guillaume W. Bres --- rinex/src/algorithm/target.rs | 2 +- rinex/src/clock/record.rs | 2 +- rinex/src/{epoch/mod.rs => epoch.rs} | 238 ++++++++++------------- rinex/src/ionex/record.rs | 2 +- rinex/src/lib.rs | 2 +- rinex/src/meteo/record.rs | 6 +- rinex/src/navigation/eopmessage.rs | 2 +- rinex/src/navigation/ephemeris.rs | 4 +- rinex/src/navigation/ionmessage.rs | 6 +- rinex/src/navigation/record.rs | 6 +- rinex/src/navigation/stomessage.rs | 2 +- rinex/src/{epoch => observation}/flag.rs | 0 rinex/src/observation/mod.rs | 3 + rinex/src/observation/record.rs | 217 +++++++++++++++++++-- 14 files changed, 322 insertions(+), 170 deletions(-) rename rinex/src/{epoch/mod.rs => epoch.rs} (72%) rename rinex/src/{epoch => observation}/flag.rs (100%) diff --git a/rinex/src/algorithm/target.rs b/rinex/src/algorithm/target.rs index 1514f9558..99d7d4078 100644 --- a/rinex/src/algorithm/target.rs +++ b/rinex/src/algorithm/target.rs @@ -29,7 +29,7 @@ pub enum Error { #[error("constellation parsing error")] ConstellationParing(#[from] gnss::constellation::ParsingError), #[error("failed to parse epoch flag")] - EpochFlagParsing(#[from] crate::epoch::flag::Error), + EpochFlagParsing(#[from] crate::observation::flag::Error), #[error("failed to parse constellation")] ConstellationParsing, #[error("invalid nav item")] diff --git a/rinex/src/clock/record.rs b/rinex/src/clock/record.rs index 7e76132af..adc9ea37d 100644 --- a/rinex/src/clock/record.rs +++ b/rinex/src/clock/record.rs @@ -194,7 +194,7 @@ pub(crate) fn parse_epoch( const OFFSET: usize = "yyyy mm dd hh mm sssssssssss".len(); let (epoch, rem) = rem.split_at(OFFSET); - let (epoch, _) = epoch::parse_utc(epoch.trim())?; + let epoch = epoch::parse_utc(epoch.trim())?; // nb of data fields let (_n, rem) = rem.split_at(4); diff --git a/rinex/src/epoch/mod.rs b/rinex/src/epoch.rs similarity index 72% rename from rinex/src/epoch/mod.rs rename to rinex/src/epoch.rs index 5e7a1084f..aa1d88044 100644 --- a/rinex/src/epoch/mod.rs +++ b/rinex/src/epoch.rs @@ -1,18 +1,14 @@ +//! Epoch parsing helpers use crate::types::Type; use hifitime::{Duration, Epoch, TimeScale, Unit}; use std::str::FromStr; use thiserror::Error; -pub mod flag; -pub use flag::EpochFlag; - #[derive(Error, Debug)] pub enum ParsingError { - #[error("failed to parse epoch flag")] - EpochFlag(#[from] flag::Error), #[error("failed to parse utc timestamp")] EpochError(#[from] hifitime::Errors), - #[error("expecting \"yyyy mm dd hh mm ss.ssss xx\" format")] + #[error("expecting \"yyyy mm dd hh mm ss.ssss\" format")] FormatError, #[error("failed to parse seconds + nanos")] SecsNanosError(#[from] std::num::ParseFloatError), @@ -42,7 +38,7 @@ pub(crate) fn now() -> Epoch { /* * Formats given epoch to string, matching standard specifications */ -pub(crate) fn format(epoch: Epoch, flag: Option, t: Type, revision: u8) -> String { +pub(crate) fn format(epoch: Epoch, t: Type, revision: u8) -> String { // Hifitime V3 does not have a gregorian decomposition method let (y, m, d, hh, mm, ss, nanos) = match epoch.time_scale { TimeScale::GPST => (epoch + Duration::from_seconds(37.0)).to_gregorian_utc(), @@ -61,7 +57,7 @@ pub(crate) fn format(epoch: Epoch, flag: Option, t: Type, revision: u y += 100; } format!( - "{:02} {:>2} {:>2} {:>2} {:>2} {:>2}.{:07} {}", + "{:02} {:>2} {:>2} {:>2} {:>2} {:>2}.{:07}", y, m, d, @@ -69,11 +65,10 @@ pub(crate) fn format(epoch: Epoch, flag: Option, t: Type, revision: u mm, ss, nanos / 100, - flag.unwrap_or(EpochFlag::Ok) ) } else { format!( - "{:04} {:02} {:02} {:02} {:02} {:>2}.{:07} {}", + "{:04} {:02} {:02} {:02} {:02} {:>2}.{:07}", y, m, d, @@ -81,7 +76,6 @@ pub(crate) fn format(epoch: Epoch, flag: Option, t: Type, revision: u mm, ss, nanos / 100, - flag.unwrap_or(EpochFlag::Ok) ) } }, @@ -128,12 +122,9 @@ pub(crate) fn format(epoch: Epoch, flag: Option, t: Type, revision: u } /* - * Parses an Epoch and optional flag, interpreted as a datetime within specified TimeScale. + * Parses an Epoch, interpreted as a datetime within specified TimeScale. */ -pub(crate) fn parse_in_timescale( - content: &str, - ts: TimeScale, -) -> Result<(Epoch, EpochFlag), ParsingError> { +pub(crate) fn parse_in_timescale(content: &str, ts: TimeScale) -> Result { let mut y = 0_i32; let mut m = 0_u8; let mut d = 0_u8; @@ -141,7 +132,10 @@ pub(crate) fn parse_in_timescale( let mut mm = 0_u8; let mut ss = 0_u8; let mut ns = 0_u32; - let mut flag = EpochFlag::default(); + + if content.split_ascii_whitespace().count() < 6 { + return Err(ParsingError::FormatError); + } for (field_index, item) in content.split_ascii_whitespace().enumerate() { match field_index { @@ -207,15 +201,12 @@ pub(crate) fn parse_in_timescale( .map_err(|_| ParsingError::SecondsField(item.to_string()))?; } }, - 6 => { - flag = EpochFlag::from_str(item.trim())?; - }, _ => {}, } } //println!("content \"{}\"", content); // DEBUG - //println!("Y {} M {} D {} HH {} MM {} SS {} NS {} FLAG {}", y, m, d, hh, mm, ss, ns, flag); // DEBUG + //println!("Y {} M {} D {} HH {} MM {} SS {} NS {}", y, m, d, hh, mm, ss, ns); // DEBUG match ts { TimeScale::UTC => { @@ -226,7 +217,7 @@ pub(crate) fn parse_in_timescale( } let epoch = Epoch::from_gregorian_utc(y, m, d, hh, mm, ss, ns); - Ok((epoch, flag)) + Ok(epoch) }, _ => { // in case provided content is totally invalid, @@ -245,12 +236,12 @@ pub(crate) fn parse_in_timescale( ns / 100_000_000, ts ))?; - Ok((epoch, flag)) + Ok(epoch) }, } } -pub(crate) fn parse_utc(s: &str) -> Result<(Epoch, EpochFlag), ParsingError> { +pub(crate) fn parse_utc(s: &str) -> Result { parse_in_timescale(s, TimeScale::UTC) } @@ -278,7 +269,7 @@ mod test { fn epoch_parse_nav_v2() { let e = parse_utc("20 12 31 23 45 0.0"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2020); assert_eq!(m, 12); @@ -288,15 +279,11 @@ mod test { assert_eq!(ss, 0); assert_eq!(ns, 0); assert_eq!(e.time_scale, TimeScale::UTC); - assert_eq!(flag, EpochFlag::Ok); - assert_eq!( - format(e, None, Type::NavigationData, 2), - "20 12 31 23 45 0.0" - ); + assert_eq!(format(e, Type::NavigationData, 2), "20 12 31 23 45 0.0"); let e = parse_utc("21 1 1 16 15 0.0"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2021); assert_eq!(m, 1); @@ -306,30 +293,23 @@ mod test { assert_eq!(ss, 0); assert_eq!(ns, 0); assert_eq!(e.time_scale, TimeScale::UTC); - assert_eq!(flag, EpochFlag::Ok); - assert_eq!( - format(e, None, Type::NavigationData, 2), - "21 1 1 16 15 0.0" - ); + assert_eq!(format(e, Type::NavigationData, 2), "21 1 1 16 15 0.0"); } #[test] fn epoch_parse_nav_v2_nanos() { let e = parse_utc("20 12 31 23 45 0.1"); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (_, _, _, _, _, ss, ns) = e.to_gregorian_utc(); assert_eq!(ss, 0); assert_eq!(ns, 100_000_000); - assert_eq!( - format(e, None, Type::NavigationData, 2), - "20 12 31 23 45 0.1" - ); + assert_eq!(format(e, Type::NavigationData, 2), "20 12 31 23 45 0.1"); } #[test] fn epoch_parse_nav_v3() { let e = parse_utc("2021 01 01 00 00 00 "); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2021); assert_eq!(m, 1); @@ -339,14 +319,11 @@ mod test { assert_eq!(ss, 0); assert_eq!(ns, 0); assert_eq!(e.time_scale, TimeScale::UTC); - assert_eq!( - format(e, None, Type::NavigationData, 3), - "2021 01 01 00 00 00" - ); + assert_eq!(format(e, Type::NavigationData, 3), "2021 01 01 00 00 00"); let e = parse_utc("2021 01 01 09 45 00 "); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2021); assert_eq!(m, 1); @@ -355,14 +332,11 @@ mod test { assert_eq!(mm, 45); assert_eq!(ss, 0); assert_eq!(ns, 0); - assert_eq!( - format(e, None, Type::NavigationData, 3), - "2021 01 01 09 45 00" - ); + assert_eq!(format(e, Type::NavigationData, 3), "2021 01 01 09 45 00"); let e = parse_utc("2020 06 25 00 00 00"); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2020); assert_eq!(m, 6); @@ -371,14 +345,11 @@ mod test { assert_eq!(mm, 00); assert_eq!(ss, 0); assert_eq!(ns, 0); - assert_eq!( - format(e, None, Type::NavigationData, 3), - "2020 06 25 00 00 00" - ); + assert_eq!(format(e, Type::NavigationData, 3), "2020 06 25 00 00 00"); let e = parse_utc("2020 06 25 09 49 04"); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2020); assert_eq!(m, 6); @@ -387,16 +358,13 @@ mod test { assert_eq!(mm, 49); assert_eq!(ss, 04); assert_eq!(ns, 0); - assert_eq!( - format(e, None, Type::NavigationData, 3), - "2020 06 25 09 49 04" - ); + assert_eq!(format(e, Type::NavigationData, 3), "2020 06 25 09 49 04"); } #[test] fn epoch_parse_obs_v2() { - let e = parse_utc(" 21 12 21 0 0 0.0000000 0"); + let e = parse_utc(" 21 12 21 0 0 0.0000000"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2021); assert_eq!(m, 12); @@ -406,15 +374,14 @@ mod test { assert_eq!(ss, 0); assert_eq!(ns, 0); assert_eq!(e.time_scale, TimeScale::UTC); - assert_eq!(flag, EpochFlag::Ok); assert_eq!( - format(e, None, Type::ObservationData, 2), - "21 12 21 0 0 0.0000000 0" + format(e, Type::ObservationData, 2), + "21 12 21 0 0 0.0000000" ); - let e = parse_utc(" 21 12 21 0 0 30.0000000 0"); + let e = parse_utc(" 21 12 21 0 0 30.0000000"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2021); assert_eq!(m, 12); @@ -423,46 +390,14 @@ mod test { assert_eq!(mm, 00); assert_eq!(ss, 30); assert_eq!(ns, 0); - assert_eq!(flag, EpochFlag::Ok); assert_eq!( - format(e, None, Type::ObservationData, 2), - "21 12 21 0 0 30.0000000 0" + format(e, Type::ObservationData, 2), + "21 12 21 0 0 30.0000000" ); - let e = parse_utc(" 21 12 21 0 0 30.0000000 1"); - assert!(e.is_ok()); - let (_e, flag) = e.unwrap(); - assert_eq!(flag, EpochFlag::PowerFailure); - //assert_eq!(format!("{:o}", e), "21 12 21 0 0 30.0000000 1"); - - let e = parse_utc(" 21 12 21 0 0 30.0000000 2"); - assert!(e.is_ok()); - let (_e, flag) = e.unwrap(); - assert_eq!(flag, EpochFlag::AntennaBeingMoved); - - let e = parse_utc(" 21 12 21 0 0 30.0000000 3"); - assert!(e.is_ok()); - let (_e, flag) = e.unwrap(); - assert_eq!(flag, EpochFlag::NewSiteOccupation); - - let e = parse_utc(" 21 12 21 0 0 30.0000000 4"); - assert!(e.is_ok()); - let (_e, flag) = e.unwrap(); - assert_eq!(flag, EpochFlag::HeaderInformationFollows); - - let e = parse_utc(" 21 12 21 0 0 30.0000000 5"); - assert!(e.is_ok()); - let (_e, flag) = e.unwrap(); - assert_eq!(flag, EpochFlag::ExternalEvent); - - let e = parse_utc(" 21 12 21 0 0 30.0000000 6"); + let e = parse_utc(" 21 1 1 0 0 0.0000000"); assert!(e.is_ok()); - let (_e, flag) = e.unwrap(); - assert_eq!(flag, EpochFlag::CycleSlip); - - let e = parse_utc(" 21 1 1 0 0 0.0000000 0"); - assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2021); assert_eq!(m, 1); @@ -471,12 +406,14 @@ mod test { assert_eq!(mm, 00); assert_eq!(ss, 0); assert_eq!(ns, 0); - assert_eq!(flag, EpochFlag::Ok); - //assert_eq!(format!("{:o}", e), "21 1 1 0 0 0.0000000 0"); + assert_eq!( + format(e, Type::ObservationData, 2), + "21 1 1 0 0 0.0000000" + ); - let e = parse_utc(" 21 1 1 0 7 30.0000000 0"); + let e = parse_utc(" 21 1 1 0 7 30.0000000"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2021); assert_eq!(m, 1); @@ -485,14 +422,16 @@ mod test { assert_eq!(mm, 7); assert_eq!(ss, 30); assert_eq!(ns, 0); - assert_eq!(flag, EpochFlag::Ok); - //assert_eq!(format!("{:o}", e), "21 1 1 0 7 30.0000000 0"); + assert_eq!( + format(e, Type::ObservationData, 2), + "21 1 1 0 7 30.0000000" + ); } #[test] fn epoch_parse_obs_v3() { - let e = parse_utc(" 2022 01 09 00 00 0.0000000 0"); + let e = parse_utc(" 2022 01 09 00 00 0.0000000"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2022); assert_eq!(m, 1); @@ -501,12 +440,14 @@ mod test { assert_eq!(mm, 0); assert_eq!(ss, 00); assert_eq!(ns, 0); - assert_eq!(flag, EpochFlag::Ok); - //assert_eq!(format!("{}", e), "2022 01 09 00 00 0.0000000 0"); + assert_eq!( + format(e, Type::ObservationData, 3), + "2022 01 09 00 00 0.0000000" + ); - let e = parse_utc(" 2022 01 09 00 13 30.0000000 0"); + let e = parse_utc(" 2022 01 09 00 13 30.0000000"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2022); assert_eq!(m, 1); @@ -515,12 +456,14 @@ mod test { assert_eq!(mm, 13); assert_eq!(ss, 30); assert_eq!(ns, 0); - assert_eq!(flag, EpochFlag::Ok); - //assert_eq!(format!("{}", e), "2022 01 09 00 13 30.0000000 0"); + assert_eq!( + format(e, Type::ObservationData, 3), + "2022 01 09 00 13 30.0000000" + ); - let e = parse_utc(" 2022 03 04 00 52 30.0000000 0"); + let e = parse_utc(" 2022 03 04 00 52 30.0000000"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2022); assert_eq!(m, 3); @@ -529,12 +472,14 @@ mod test { assert_eq!(mm, 52); assert_eq!(ss, 30); assert_eq!(ns, 0); - assert_eq!(flag, EpochFlag::Ok); - //assert_eq!(format!("{}", e), "2022 03 04 00 52 30.0000000 0"); + assert_eq!( + format(e, Type::ObservationData, 3), + "2022 03 04 00 52 30.0000000" + ); - let e = parse_utc(" 2022 03 04 00 02 30.0000000 0"); + let e = parse_utc(" 2022 03 04 00 02 30.0000000"); assert!(e.is_ok()); - let (e, flag) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2022); assert_eq!(m, 3); @@ -543,49 +488,64 @@ mod test { assert_eq!(mm, 02); assert_eq!(ss, 30); assert_eq!(ns, 0); - assert_eq!(flag, EpochFlag::Ok); - //assert_eq!(format!("{}", e), "2022 03 04 00 02 30.0000000 0"); + assert_eq!( + format(e, Type::ObservationData, 3), + "2022 03 04 00 02 30.0000000" + ); } #[test] fn epoch_parse_obs_v2_nanos() { - let e = parse_utc(" 21 1 1 0 7 39.1234567 0"); + let e = parse_utc(" 21 1 1 0 7 39.1234567"); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (_, _, _, _, _, ss, ns) = e.to_gregorian_utc(); assert_eq!(ss, 39); assert_eq!(ns, 123_456_700); + assert_eq!( + format(e, Type::ObservationData, 2), + "21 1 1 0 7 39.1234567" + ); } #[test] fn epoch_parse_obs_v3_nanos() { - let e = parse_utc("2022 01 09 00 00 0.1000000 0"); + let e = parse_utc("2022 01 09 00 00 0.1000000"); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (_, _, _, _, _, ss, ns) = e.to_gregorian_utc(); assert_eq!(ss, 0); assert_eq!(ns, 100_000_000); - //assert_eq!(format!("{}", e), "2022 01 09 00 00 0.1000000 0"); + assert_eq!( + format(e, Type::ObservationData, 3), + "2022 01 09 00 00 0.1000000" + ); - let e = parse_utc(" 2022 01 09 00 00 0.1234000 0"); + let e = parse_utc(" 2022 01 09 00 00 0.1234000"); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (_, _, _, _, _, ss, ns) = e.to_gregorian_utc(); assert_eq!(ss, 0); assert_eq!(ns, 123_400_000); - //assert_eq!(format!("{}", e), "2022 01 09 00 00 0.1234000 0"); + assert_eq!( + format(e, Type::ObservationData, 3), + "2022 01 09 00 00 0.1234000" + ); - let e = parse_utc(" 2022 01 09 00 00 8.7654321 0"); + let e = parse_utc(" 2022 01 09 00 00 8.7654321"); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (_, _, _, _, _, ss, ns) = e.to_gregorian_utc(); assert_eq!(ss, 8); assert_eq!(ns, 765_432_100); - //assert_eq!(format!("{}", e), "2022 01 09 00 00 8.7654321 0"); + assert_eq!( + format(e, Type::ObservationData, 3), + "2022 01 09 00 00 8.7654321" + ); } #[test] fn epoch_parse_meteo_v2() { let e = parse_utc(" 22 1 4 0 0 0 "); assert!(e.is_ok()); - let (e, _) = e.unwrap(); + let e = e.unwrap(); let (y, m, d, hh, mm, ss, ns) = e.to_gregorian_utc(); assert_eq!(y, 2022); assert_eq!(m, 1); @@ -594,7 +554,7 @@ mod test { assert_eq!(mm, 00); assert_eq!(ss, 00); assert_eq!(ns, 0); - //assert_eq!(format!("{}", e), "2022 03 04 00 02 30.0000000 0"); + assert_eq!(format(e, Type::MeteoData, 2), "22 1 4 0 0 0"); } #[test] fn epoch_decomposition() { diff --git a/rinex/src/ionex/record.rs b/rinex/src/ionex/record.rs index d40ba1ea5..d0e6c5e7c 100644 --- a/rinex/src/ionex/record.rs +++ b/rinex/src/ionex/record.rs @@ -171,7 +171,7 @@ pub(crate) fn parse_plane( // debug // println!("NEW GRID : h: {} lat : {} lon : {}, dlon: {}", altitude, latitude, longitude, dlon); } else if marker.contains("EPOCH OF CURRENT MAP") { - epoch = epoch::parse_utc(content)?.0; + epoch = epoch::parse_utc(content)?; } else if marker.contains("EXPONENT") { // update current scaling if let Ok(e) = content.trim().parse::() { diff --git a/rinex/src/lib.rs b/rinex/src/lib.rs index 2e9c628a2..146679667 100644 --- a/rinex/src/lib.rs +++ b/rinex/src/lib.rs @@ -80,10 +80,10 @@ pub mod prelude { #[cfg(feature = "sp3")] pub use crate::context::{ProductType, RnxContext}; pub use crate::domes::Domes; - pub use crate::epoch::EpochFlag; pub use crate::ground_position::GroundPosition; pub use crate::header::Header; pub use crate::observable::Observable; + pub use crate::observation::EpochFlag; pub use crate::types::Type as RinexType; pub use crate::Error; pub use crate::Rinex; diff --git a/rinex/src/meteo/record.rs b/rinex/src/meteo/record.rs index e3b3cd010..9eba01043 100644 --- a/rinex/src/meteo/record.rs +++ b/rinex/src/meteo/record.rs @@ -18,7 +18,7 @@ pub type Record = BTreeMap>; * we should initiate the parsing of a meteo record entry. */ pub(crate) fn is_new_epoch(line: &str, v: version::Version) -> bool { - if v.major < 4 { + if v.major < 3 { let min_len = " 15 1 1 0 0 0"; if line.len() < min_len.len() { // minimum epoch descriptor @@ -65,7 +65,7 @@ pub(crate) fn parse_epoch( offset += 2; // YYYY } - let (epoch, _) = epoch::parse_utc(&line[0..offset])?; + let epoch = epoch::parse_utc(&line[0..offset])?; let codes = &header.meteo.as_ref().unwrap().codes; let nb_codes = codes.len(); @@ -117,7 +117,7 @@ pub(crate) fn fmt_epoch( let mut lines = String::with_capacity(128); lines.push_str(&format!( " {}", - epoch::format(*epoch, None, Type::MeteoData, header.version.major) + epoch::format(*epoch, Type::MeteoData, header.version.major) )); let observables = &header.meteo.as_ref().unwrap().codes; let mut index = 0; diff --git a/rinex/src/navigation/eopmessage.rs b/rinex/src/navigation/eopmessage.rs index 333645f4c..ad7a52def 100644 --- a/rinex/src/navigation/eopmessage.rs +++ b/rinex/src/navigation/eopmessage.rs @@ -60,7 +60,7 @@ impl EopMessage { let (dut, rem) = rem.split_at(19); let (ddut, dddut) = rem.split_at(19); - let (epoch, _) = epoch::parse_in_timescale(epoch.trim(), ts)?; + let epoch = epoch::parse_in_timescale(epoch.trim(), ts)?; let x = ( f64::from_str(xp.trim()).unwrap_or(0.0_f64), f64::from_str(dxp.trim()).unwrap_or(0.0_f64), diff --git a/rinex/src/navigation/ephemeris.rs b/rinex/src/navigation/ephemeris.rs index 6fcbf63d8..6c4dc9cd5 100644 --- a/rinex/src/navigation/ephemeris.rs +++ b/rinex/src/navigation/ephemeris.rs @@ -213,7 +213,7 @@ impl Ephemeris { .ok_or(Error::TimescaleIdentification(sv))?; //println!("V2/V3 CONTENT \"{}\" TIMESCALE {}", line, ts); //DEBUG - let (epoch, _) = epoch::parse_in_timescale(date.trim(), ts)?; + let epoch = epoch::parse_in_timescale(date.trim(), ts)?; let clock_bias = f64::from_str(clk_bias.replace('D', "E").trim())?; let clock_drift = f64::from_str(clk_dr.replace('D', "E").trim())?; @@ -249,7 +249,7 @@ impl Ephemeris { let (svnn, rem) = line.split_at(4); let sv = SV::from_str(svnn.trim())?; let (epoch, rem) = rem.split_at(19); - let (epoch, _) = epoch::parse_in_timescale(epoch.trim(), ts)?; + let epoch = epoch::parse_in_timescale(epoch.trim(), ts)?; let (clk_bias, rem) = rem.split_at(19); let (clk_dr, clk_drr) = rem.split_at(19); diff --git a/rinex/src/navigation/ionmessage.rs b/rinex/src/navigation/ionmessage.rs index aa860630e..7859301cc 100644 --- a/rinex/src/navigation/ionmessage.rs +++ b/rinex/src/navigation/ionmessage.rs @@ -124,7 +124,7 @@ impl KbModel { }, }; - let (epoch, _) = parse_in_timescale(epoch.trim(), ts)?; + let epoch = parse_in_timescale(epoch.trim(), ts)?; let alpha = ( f64::from_str(a0.trim()).map_err(|_| Error::KbAlphaValueError)?, f64::from_str(a1.trim()).map_err(|_| Error::KbAlphaValueError)?, @@ -257,7 +257,7 @@ impl NgModel { _ => return Err(Error::NgModelMissing2ndLine), }; - let (epoch, _) = parse_in_timescale(epoch.trim(), ts)?; + let epoch = parse_in_timescale(epoch.trim(), ts)?; let a = ( f64::from_str(a0.trim()).map_err(|_| Error::NgValueError)?, f64::from_str(a1.trim()).map_err(|_| Error::NgValueError)?, @@ -316,7 +316,7 @@ impl BdModel { }; let (a7, a8) = line.split_at(23); - let (epoch, _) = parse_in_timescale(epoch.trim(), ts)?; + let epoch = parse_in_timescale(epoch.trim(), ts)?; let alpha = ( f64::from_str(a0.trim()).unwrap_or(0.0_f64), f64::from_str(a1.trim()).unwrap_or(0.0_f64), diff --git a/rinex/src/navigation/record.rs b/rinex/src/navigation/record.rs index 2d8bfb212..490aa0ce4 100644 --- a/rinex/src/navigation/record.rs +++ b/rinex/src/navigation/record.rs @@ -396,7 +396,7 @@ fn fmt_epoch_v2v3(epoch: &Epoch, data: &Vec, header: &Header) -> Resul } lines.push_str(&format!( "{} ", - epoch::format(*epoch, None, Type::NavigationData, header.version.major) + epoch::format(*epoch, Type::NavigationData, header.version.major) )); lines.push_str(&format!( "{:14.11E} {:14.11E} {:14.11E}\n ", @@ -469,7 +469,7 @@ fn fmt_epoch_v4(epoch: &Epoch, data: &Vec, header: &Header) -> Result< } lines.push_str(&format!( "{} ", - epoch::format(*epoch, None, Type::NavigationData, header.version.major) + epoch::format(*epoch, Type::NavigationData, header.version.major) )); lines.push_str(&format!( "{:14.13E} {:14.13E} {:14.13E}\n", @@ -506,7 +506,7 @@ fn fmt_epoch_v4(epoch: &Epoch, data: &Vec, header: &Header) -> Result< )); lines.push_str(&format!( " {} {} {}\n", - epoch::format(*epoch, None, Type::NavigationData, header.version.major), + epoch::format(*epoch, Type::NavigationData, header.version.major), sto.system, sto.utc )); diff --git a/rinex/src/navigation/stomessage.rs b/rinex/src/navigation/stomessage.rs index cc20f6126..a75f991d3 100644 --- a/rinex/src/navigation/stomessage.rs +++ b/rinex/src/navigation/stomessage.rs @@ -37,7 +37,7 @@ impl StoMessage { let (epoch, rem) = line.split_at(23); let (system, _) = rem.split_at(5); - let (epoch, _) = epoch::parse_in_timescale(epoch.trim(), ts)?; + let epoch = epoch::parse_in_timescale(epoch.trim(), ts)?; let line = match lines.next() { Some(l) => l, diff --git a/rinex/src/epoch/flag.rs b/rinex/src/observation/flag.rs similarity index 100% rename from rinex/src/epoch/flag.rs rename to rinex/src/observation/flag.rs diff --git a/rinex/src/observation/mod.rs b/rinex/src/observation/mod.rs index c4a551d21..8a98e998e 100644 --- a/rinex/src/observation/mod.rs +++ b/rinex/src/observation/mod.rs @@ -4,6 +4,9 @@ use std::collections::HashMap; pub mod record; +pub mod flag; +pub use flag::EpochFlag; + mod snr; pub use snr::SNR; diff --git a/rinex/src/observation/record.rs b/rinex/src/observation/record.rs index 2fe3c9331..666349bd1 100644 --- a/rinex/src/observation/record.rs +++ b/rinex/src/observation/record.rs @@ -8,11 +8,14 @@ use crate::{ Carrier, Observable, }; +use crate::observation::EpochFlag; use crate::observation::SNR; use hifitime::Duration; #[derive(Error, Debug)] pub enum Error { + #[error("failed to parse epoch flag")] + EpochFlag(#[from] crate::observation::flag::Error), #[error("failed to parse epoch")] EpochError(#[from] epoch::ParsingError), #[error("constellation parsing error")] @@ -141,11 +144,28 @@ pub(crate) fn is_new_epoch(line: &str, v: Version) -> bool { if line.len() < 30 { false } else { - epoch::parse_utc(&line[0..29]).is_ok() + // SPLICE flag handling (still an Observation::flag) + let significant = line[0..26].trim().len() != 0; + let epoch = epoch::parse_utc(&line[0..26]); + let flag = EpochFlag::from_str(&line[26..29].trim()); + if significant { + epoch.is_ok() && flag.is_ok() + } else { + if flag.is_err() { + false + } else { + match flag.unwrap() { + EpochFlag::AntennaBeingMoved + | EpochFlag::NewSiteOccupation + | EpochFlag::HeaderInformationFollows + | EpochFlag::ExternalEvent => true, + _ => false, + } + } + } } } else { - // Modern RINEX - // OBS::V3 behaves like all::V4 + // Modern RINEX has a simple marker, like all V4 modern files match line.chars().next() { Some(c) => { c == '>' // epochs always delimited @@ -193,14 +213,12 @@ pub(crate) fn parse_epoch( line = line.split_at(1).1; } - let (date, rem) = line.split_at(offset + 3); + let (date, rem) = line.split_at(offset); + let epoch = epoch::parse_in_timescale(date, ts)?; + let (flag, rem) = rem.split_at(3); + let flag = EpochFlag::from_str(flag.trim())?; let (n_sat, rem) = rem.split_at(3); let n_sat = n_sat.trim().parse::()?; - let epoch = epoch::parse_in_timescale(date, ts)?; - - // previously identified observables (that we expect) - let obs = header.obs.as_ref().unwrap(); - let observables = &obs.codes; // grab possible clock offset let offs: Option<&str> = match header.version.major < 2 { @@ -242,6 +260,34 @@ pub(crate) fn parse_epoch( false => None, // empty field }; + match flag { + EpochFlag::Ok | EpochFlag::PowerFailure | EpochFlag::CycleSlip => { + parse_normal(header, epoch, flag, n_sat, clock_offset, rem, lines) + }, + _ => parse_event(header, epoch, flag, n_sat, clock_offset, rem, lines), + } +} + +fn parse_normal( + header: &Header, + epoch: Epoch, + flag: EpochFlag, + n_sat: u16, + clock_offset: Option, + rem: &str, + mut lines: std::str::Lines<'_>, +) -> Result< + ( + (Epoch, EpochFlag), + Option, + BTreeMap>, + ), + Error, +> { + // previously identified observables (that we expect) + let obs = header.obs.as_ref().unwrap(); + let observables = &obs.codes; + let data = match header.version.major { 2 => { // grab system descriptions @@ -262,7 +308,30 @@ pub(crate) fn parse_epoch( }, _ => parse_v3(observables, lines), }; - Ok((epoch, clock_offset, data)) + Ok(((epoch, flag), clock_offset, data)) +} + +fn parse_event( + header: &Header, + epoch: Epoch, + flag: EpochFlag, + n_records: u16, + clock_offset: Option, + rem: &str, + mut lines: std::str::Lines<'_>, +) -> Result< + ( + (Epoch, EpochFlag), + Option, + BTreeMap>, + ), + Error, +> { + // TODO: Verify that the number of lines of data + // to read matches the number of records expected + + // TODO: Actually process event data + Err(Error::MissingData) } /* @@ -586,8 +655,9 @@ fn fmt_epoch_v3( let observables = &header.obs.as_ref().unwrap().codes; lines.push_str(&format!( - "> {} {:2}", - epoch::format(epoch, Some(flag), Type::ObservationData, 3), + "> {} {} {:2}", + epoch::format(epoch, Type::ObservationData, 3), + flag, data.len() )); @@ -638,8 +708,9 @@ fn fmt_epoch_v2( let observables = &header.obs.as_ref().unwrap().codes; lines.push_str(&format!( - " {} {:2}", - epoch::format(epoch, Some(flag), Type::ObservationData, 2), + " {} {} {:2}", + epoch::format(epoch, Type::ObservationData, 2), + flag, data.len() )); @@ -1744,6 +1815,124 @@ pub(crate) fn code_multipath( #[cfg(test)] mod test { use super::*; + fn parse_and_format_helper(ver: Version, epoch_str: &str, expected_flag: EpochFlag) { + let first = epoch::parse_utc("2020 01 01 00 00 0.1000000").unwrap(); + let data: BTreeMap> = BTreeMap::new(); + let header = Header::default().with_version(ver).with_observation_fields( + crate::observation::HeaderFields::default().with_time_of_first_obs(first), + ); + let ts = TimeScale::UTC; + let clock_offset: Option = None; + + let e = parse_epoch(&header, epoch_str, ts); + + match expected_flag { + EpochFlag::Ok | EpochFlag::PowerFailure | EpochFlag::CycleSlip => { + assert!(e.is_ok()) + }, + _ => { + // TODO: Update alongside parse_event + assert!(e.is_err()); + return; + }, + } + let ((e, flag), _, _) = e.unwrap(); + assert_eq!(flag, expected_flag); + if ver.major < 3 { + assert_eq!( + fmt_epoch_v2(e, flag, &clock_offset, &data, &header) + .lines() + .next() + .unwrap(), + epoch_str + ); + } else { + assert_eq!( + fmt_epoch_v3(e, flag, &clock_offset, &data, &header) + .lines() + .next() + .unwrap(), + epoch_str + ); + } + } + + #[test] + fn obs_v2_parse_and_format() { + parse_and_format_helper( + Version { major: 2, minor: 0 }, + " 21 12 21 0 0 30.0000000 0 0", + EpochFlag::Ok, + ); + parse_and_format_helper( + Version { major: 2, minor: 0 }, + " 21 12 21 0 0 30.0000000 1 0", + EpochFlag::PowerFailure, + ); + parse_and_format_helper( + Version { major: 2, minor: 0 }, + " 21 12 21 0 0 30.0000000 2 0", + EpochFlag::AntennaBeingMoved, + ); + parse_and_format_helper( + Version { major: 2, minor: 0 }, + " 21 12 21 0 0 30.0000000 3 0", + EpochFlag::NewSiteOccupation, + ); + parse_and_format_helper( + Version { major: 2, minor: 0 }, + " 21 12 21 0 0 30.0000000 4 0", + EpochFlag::HeaderInformationFollows, + ); + parse_and_format_helper( + Version { major: 2, minor: 0 }, + " 21 12 21 0 0 30.0000000 5 0", + EpochFlag::ExternalEvent, + ); + parse_and_format_helper( + Version { major: 2, minor: 0 }, + " 21 12 21 0 0 30.0000000 6 0", + EpochFlag::CycleSlip, + ); + } + #[test] + fn obs_v3_parse_and_format() { + parse_and_format_helper( + Version { major: 3, minor: 0 }, + "> 2021 12 21 00 00 30.0000000 0 0", + EpochFlag::Ok, + ); + parse_and_format_helper( + Version { major: 3, minor: 0 }, + "> 2021 12 21 00 00 30.0000000 1 0", + EpochFlag::PowerFailure, + ); + parse_and_format_helper( + Version { major: 3, minor: 0 }, + "> 2021 12 21 00 00 30.0000000 2 0", + EpochFlag::AntennaBeingMoved, + ); + parse_and_format_helper( + Version { major: 3, minor: 0 }, + "> 2021 12 21 00 00 30.0000000 3 0", + EpochFlag::NewSiteOccupation, + ); + parse_and_format_helper( + Version { major: 3, minor: 0 }, + "> 2021 12 21 00 00 30.0000000 4 0", + EpochFlag::HeaderInformationFollows, + ); + parse_and_format_helper( + Version { major: 3, minor: 0 }, + "> 2021 12 21 00 00 30.0000000 5 0", + EpochFlag::ExternalEvent, + ); + parse_and_format_helper( + Version { major: 3, minor: 0 }, + "> 2021 12 21 00 00 30.0000000 6 0", + EpochFlag::CycleSlip, + ); + } #[test] fn obs_record_is_new_epoch() { assert!(is_new_epoch(