sp3 1.4.1

IGS SP3 file parser
Documentation
//! Header line #1 helpers

use crate::{
    header::{DataType, OrbitType, Version},
    prelude::Epoch,
    FormattingError, ParsingError,
};

use std::io::{BufWriter, Write};

pub(crate) fn is_header_line1(content: &str) -> bool {
    content.starts_with('#')
}

pub(crate) struct Line1 {
    pub version: Version,
    pub data_type: DataType,
    pub epoch: Epoch,
    pub observables: String,
    pub num_epochs: u64,
    pub coord_system: String,
    pub orbit_type: OrbitType,
    pub agency: String,
}

impl std::str::FromStr for Line1 {
    type Err = ParsingError;

    fn from_str(line: &str) -> Result<Self, Self::Err> {
        if line.len() < 59 {
            return Err(ParsingError::MalformedH1);
        }

        let (y, m, d, hh, mm, ss, nanos) = (
            &line[3..7].trim(),
            &line[8..11].trim(),
            &line[11..13].trim(),
            &line[14..16].trim(),
            &line[17..19].trim(),
            &line[20..22].trim(),
            &line[23..31].trim(),
        );

        let y = y.parse::<i32>().or(Err(ParsingError::Epoch))?;
        let m = m.parse::<u8>().or(Err(ParsingError::Epoch))?;
        let d = d.parse::<u8>().or(Err(ParsingError::Epoch))?;
        let hh = hh.parse::<u8>().or(Err(ParsingError::Epoch))?;
        let mm = mm.parse::<u8>().or(Err(ParsingError::Epoch))?;
        let ss = ss.parse::<u8>().or(Err(ParsingError::Epoch))?;
        let nanos = nanos.parse::<u32>().or(Err(ParsingError::Epoch))?;

        let epoch = Epoch::from_gregorian_utc(y, m, d, hh, mm, ss, nanos * 10);

        let num_epochs = &line[32..40].trim();
        let num_epochs = num_epochs.parse::<u64>().or(Err(ParsingError::Epoch))?;

        Ok(Self {
            epoch,
            num_epochs,
            observables: line[40..45].to_string(),
            version: Version::from_str(&line[1..2])?,
            data_type: DataType::from_str(&line[2..3])?,
            coord_system: line[45..51].trim().to_string(),
            orbit_type: OrbitType::from_str(line[51..55].trim())?,
            agency: line[55..].trim().to_string(),
        })
    }
}

impl Line1 {
    /// Formats [Line1] according to SP3 standards.
    pub fn format<W: Write>(&self, w: &mut BufWriter<W>) -> Result<(), FormattingError> {
        let (y, m, d, hh, mm, ss, nanos) = self.epoch.to_gregorian_utc();

        write!(
            w,
            "#{}{}{:04} {:2} {:2} {:2} {:2} {:2}.{:08} {:7} {} {} {}  {}",
            self.version,
            self.data_type,
            y,
            m,
            d,
            hh,
            mm,
            ss,
            nanos / 10,
            self.num_epochs,
            self.observables,
            self.coord_system,
            self.orbit_type,
            self.agency,
        )?;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use crate::{
        header::{line1::Line1, DataType, OrbitType, Version},
        prelude::Epoch,
        tests::formatting::Utf8Buffer,
    };

    use std::{io::BufWriter, str::FromStr};

    #[test]
    fn test_line1() {
        for (line, version, dtype, epoch_str, num_epochs, observables, coord_system, orbit_type) in [
            (
                "#dP2020  6 24  1  3  4.12345678      97 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Position,
                "2020-06-24T01:03:04.12345678 UTC",
                97,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dV2020  6 24  1  3 54.12345678      97 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Velocity,
                "2020-06-24T01:03:54.12345678 UTC",
                97,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dV2020 12 24 21  3 54.12345678      97 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Velocity,
                "2020-12-24T21:03:54.12345678 UTC",
                97,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dV2020 12 24 21 43 54.12345678      97 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Velocity,
                "2020-12-24T21:43:54.12345678 UTC",
                97,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dV2020 12 24 21 43 54.12345678       7 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Velocity,
                "2020-12-24T21:43:54.12345678 UTC",
                7,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dV2020 12 24 21 43 54.12345678    1000 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Velocity,
                "2020-12-24T21:43:54.12345678 UTC",
                1000,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dV2020 12 24 21 43 54.12345678  100022 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Velocity,
                "2020-12-24T21:43:54.12345678 UTC",
                100022,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dV2020 12 24 21 43 54.12345678 9100022 __u+U IGS14 FIT  IAC",
                Version::D,
                DataType::Velocity,
                "2020-12-24T21:43:54.12345678 UTC",
                9100022,
                "__u+U",
                "IGS14",
                OrbitType::FIT,
            ),
            (
                "#dP2019 10 27  0  0  0.00000000       1   u+U IGS14 FIT  IGS",
                Version::D,
                DataType::Position,
                "2019-10-27T00:00:00.00000000 UTC",
                1,
                "  u+U",
                "IGS14",
                OrbitType::FIT,
            ),
        ] {
            let line1 = Line1::from_str(line).unwrap();
            let epoch = Epoch::from_str(epoch_str).unwrap();

            assert_eq!(line1.version, version);
            assert_eq!(line1.coord_system, coord_system);
            assert_eq!(line1.orbit_type, orbit_type);
            assert_eq!(line1.epoch, epoch);
            assert_eq!(line1.data_type, dtype);
            assert_eq!(line1.observables, observables);
            assert_eq!(line1.num_epochs, num_epochs);

            let mut buf = BufWriter::new(Utf8Buffer::new(1024));

            line1.format(&mut buf).unwrap_or_else(|e| {
                panic!("Header/Line#1 formatting issue: {}", e);
            });

            let formatted = buf.into_inner().unwrap();
            let formatted = formatted.to_ascii_utf8();

            assert_eq!(formatted, line);
        }
    }
}