Skip to content

Commit

Permalink
Added Datetime parsing to SI. Run cargo fmt
Browse files Browse the repository at this point in the history
  • Loading branch information
NiklasVousten committed Oct 20, 2023
1 parent f35191c commit 8898a75
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 95 deletions.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod utilities;

#[cfg(feature = "si")]
/// Ephemeris information based on SI-units.
///
///
/// SI-units from the crate *uom*: <https://docs.rs/uom/0.35.0/uom/>
pub mod si;

Expand Down
6 changes: 3 additions & 3 deletions src/si/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::si::ephemeris::{
EphemerisOrbitalElementsItem, EphemerisOrbitalElementsParser, EphemerisVectorItem,
EphemerisVectorParser,
};
EphemerisOrbitalElementsItem, EphemerisOrbitalElementsParser, EphemerisVectorItem,
EphemerisVectorParser,
};

/// Generic Horizons response. Their API just gives some JSON with two field,
/// some statuses and `result` field which is just human-readable string
Expand Down
168 changes: 132 additions & 36 deletions src/si/ephemeris.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::utilities::{take_expecting, take_or_empty};
use chrono::{DateTime, NaiveDateTime, Utc};
use uom::si::f32::{Angle, AngularVelocity, Length, Time, Velocity};
use uom::si::{angle, angular_velocity, length, time, velocity};

use uom::si::f32::{Length, Velocity, Angle, AngularVelocity, Time};
use uom::si::{length, velocity, angle, angular_velocity, time};
use crate::utilities::{take_expecting, take_or_empty};

/// Position and velocity of a body. Units are SI-based
///
Expand All @@ -18,6 +19,9 @@ use uom::si::{length, velocity, angle, angular_velocity, time};
/// | RR | Range-rate; radial velocity wrt coord. center |
#[derive(Debug, PartialEq)]
pub struct EphemerisVectorItem {
/// Timestamp of the entry in UTC
pub time: DateTime<Utc>,

/// Position of the moving body relative to the Sun
///
/// [x, y, z]
Expand Down Expand Up @@ -49,6 +53,9 @@ pub struct EphemerisVectorItem {
/// For a detailed explenation of keplarian orbital elements, visit [Wikipedia](https://en.wikipedia.org/wiki/Orbital_elements)
#[derive(Debug, PartialEq)]
pub struct EphemerisOrbitalElementsItem {
/// Timestamp of the entry in UTC
pub time: DateTime<Utc>,

/// Describes the "roundness" of the orbit.
///
/// Value of 0 means a circle, everything until 1 is an eliptic orbit.
Expand Down Expand Up @@ -112,11 +119,13 @@ pub struct EphemerisOrbitalElementsItem {
enum EphemerisVectorParserState {
WaitingForSoe,
WaitingForDate,
WaitingForPosition,
Date(DateTime<Utc>),
Position {
time: DateTime<Utc>,
position: [Length; 3],
},
Complete {
time: DateTime<Utc>,
position: [Length; 3],
velocity: [Velocity; 3],
},
Expand All @@ -126,13 +135,15 @@ enum EphemerisVectorParserState {
enum EphemerisOrbitalElementsParserState {
WaitingForSoe,
WaitingForDate,
WaitingForFirstRow,
Date(DateTime<Utc>),
FirstRow {
time: DateTime<Utc>,
eccentricity: f32,
periapsis_distance: Length,
inclination: Angle,
},
SecondRow {
time: DateTime<Utc>,
eccentricity: f32,
periapsis_distance: Length,
inclination: Angle,
Expand All @@ -142,6 +153,7 @@ enum EphemerisOrbitalElementsParserState {
time_of_periapsis: Time,
},
ThirdRow {
time: DateTime<Utc>,
eccentricity: f32,
periapsis_distance: Length,
inclination: Angle,
Expand Down Expand Up @@ -201,10 +213,12 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisVectorParser<'a,
if line == "$$EOE" {
self.state = EphemerisVectorParserState::End;
} else {
self.state = EphemerisVectorParserState::WaitingForPosition;
let time = parse_date_time(line);

self.state = EphemerisVectorParserState::Date(time);
}
}
EphemerisVectorParserState::WaitingForPosition => {
EphemerisVectorParserState::Date(time) => {
// TODO: Don't panic.
let line = take_expecting(line, " X =").unwrap();
let (x, line) = take_or_empty(line, 22);
Expand All @@ -216,14 +230,15 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisVectorParser<'a,
let (z, _) = take_or_empty(line, 22);

self.state = EphemerisVectorParserState::Position {
time,
position: [
Length::new::<length::kilometer>(x.trim().parse::<f32>().unwrap()),
Length::new::<length::kilometer>(y.trim().parse::<f32>().unwrap()),
Length::new::<length::kilometer>(z.trim().parse::<f32>().unwrap()),
],
};
}
EphemerisVectorParserState::Position { position } => {
EphemerisVectorParserState::Position { time, position } => {
// TODO: Don't panic.
let line = take_expecting(line, " VX=").unwrap();
let (vx, line) = take_or_empty(line, 22);
Expand All @@ -235,18 +250,33 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisVectorParser<'a,
let (vz, _) = take_or_empty(line, 22);

self.state = EphemerisVectorParserState::Complete {
time,
position,
velocity: [
Velocity::new::<velocity::kilometer_per_second>(vx.trim().parse::<f32>().unwrap()),
Velocity::new::<velocity::kilometer_per_second>(vy.trim().parse::<f32>().unwrap()),
Velocity::new::<velocity::kilometer_per_second>(vz.trim().parse::<f32>().unwrap()),
Velocity::new::<velocity::kilometer_per_second>(
vx.trim().parse::<f32>().unwrap(),
),
Velocity::new::<velocity::kilometer_per_second>(
vy.trim().parse::<f32>().unwrap(),
),
Velocity::new::<velocity::kilometer_per_second>(
vz.trim().parse::<f32>().unwrap(),
),
],
};
}
// Would parse third line and then return Item => ignores third line and returns directly
EphemerisVectorParserState::Complete { position, velocity } => {
EphemerisVectorParserState::Complete {
time,
position,
velocity,
} => {
self.state = EphemerisVectorParserState::WaitingForDate;
return Some(EphemerisVectorItem { position, velocity });
return Some(EphemerisVectorItem {
time,
position,
velocity,
});
}
EphemerisVectorParserState::End => {
// Should we drain input iterator?
Expand Down Expand Up @@ -277,10 +307,12 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisOrbitalElementsP
if line == "$$EOE" {
self.state = EphemerisOrbitalElementsParserState::End;
} else {
self.state = EphemerisOrbitalElementsParserState::WaitingForFirstRow;
let time = parse_date_time(line);

self.state = EphemerisOrbitalElementsParserState::Date(time);
}
}
EphemerisOrbitalElementsParserState::WaitingForFirstRow => {
EphemerisOrbitalElementsParserState::Date(time) => {
let line = take_expecting(line, " EC=").unwrap();
let (eccentricity, line) = take_or_empty(line, 22);

Expand All @@ -291,12 +323,18 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisOrbitalElementsP
let (inclination, _) = take_or_empty(line, 22);

self.state = EphemerisOrbitalElementsParserState::FirstRow {
time,
eccentricity: eccentricity.trim().parse::<f32>().unwrap(),
periapsis_distance: Length::new::<length::kilometer>(periapsis_distance.trim().parse::<f32>().unwrap()),
inclination: Angle::new::<angle::degree>(inclination.trim().parse::<f32>().unwrap()),
periapsis_distance: Length::new::<length::kilometer>(
periapsis_distance.trim().parse::<f32>().unwrap(),
),
inclination: Angle::new::<angle::degree>(
inclination.trim().parse::<f32>().unwrap(),
),
};
}
EphemerisOrbitalElementsParserState::FirstRow {
time,
eccentricity,
periapsis_distance,
inclination,
Expand All @@ -311,21 +349,23 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisOrbitalElementsP
let (time_of_periapsis, _) = take_or_empty(line, 22);

self.state = EphemerisOrbitalElementsParserState::SecondRow {
time,
eccentricity,
periapsis_distance,
inclination,
longitude_of_ascending_node: Angle::new::<angle::degree>(longitude_of_ascending_node
.trim()
.parse::<f32>()
.unwrap()),
argument_of_perifocus: Angle::new::<angle::degree>(argument_of_perifocus
.trim()
.parse::<f32>()
.unwrap()),
time_of_periapsis: Time::new::<time::day>(time_of_periapsis.trim().parse::<f32>().unwrap()),
longitude_of_ascending_node: Angle::new::<angle::degree>(
longitude_of_ascending_node.trim().parse::<f32>().unwrap(),
),
argument_of_perifocus: Angle::new::<angle::degree>(
argument_of_perifocus.trim().parse::<f32>().unwrap(),
),
time_of_periapsis: Time::new::<time::day>(
time_of_periapsis.trim().parse::<f32>().unwrap(),
),
};
}
EphemerisOrbitalElementsParserState::SecondRow {
time,
eccentricity,
periapsis_distance,
inclination,
Expand All @@ -343,19 +383,27 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisOrbitalElementsP
let (true_anomaly, _) = take_or_empty(line, 22);

self.state = EphemerisOrbitalElementsParserState::ThirdRow {
time,
eccentricity,
periapsis_distance,
inclination,
longitude_of_ascending_node,
argument_of_perifocus,
time_of_periapsis,
mean_motion: AngularVelocity::new::<angular_velocity::degree_per_second>(mean_motion.trim().parse::<f32>().unwrap()),
mean_anomaly: Angle::new::<angle::degree>(mean_anomaly.trim().parse::<f32>().unwrap()),
true_anomaly: Angle::new::<angle::degree>(true_anomaly.trim().parse::<f32>().unwrap()),
mean_motion: AngularVelocity::new::<angular_velocity::degree_per_second>(
mean_motion.trim().parse::<f32>().unwrap(),
),
mean_anomaly: Angle::new::<angle::degree>(
mean_anomaly.trim().parse::<f32>().unwrap(),
),
true_anomaly: Angle::new::<angle::degree>(
true_anomaly.trim().parse::<f32>().unwrap(),
),
};
}
// Parses last line and return Item
EphemerisOrbitalElementsParserState::ThirdRow {
time,
eccentricity,
periapsis_distance,
inclination,
Expand All @@ -377,6 +425,7 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisOrbitalElementsP

self.state = EphemerisOrbitalElementsParserState::WaitingForDate;
return Some(EphemerisOrbitalElementsItem {
time,
eccentricity,
periapsis_distance,
inclination,
Expand All @@ -386,12 +435,15 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisOrbitalElementsP
mean_motion,
mean_anomaly,
true_anomaly,
semi_major_axis: Length::new::<length::kilometer>(semi_major_axis.trim().parse::<f32>().unwrap()),
apoapsis_distance: Length::new::<length::kilometer>(apoapsis_distance.trim().parse::<f32>().unwrap()),
siderral_orbit_period: Time::new::<time::second>(siderral_orbit_period
.trim()
.parse::<f32>()
.unwrap()),
semi_major_axis: Length::new::<length::kilometer>(
semi_major_axis.trim().parse::<f32>().unwrap(),
),
apoapsis_distance: Length::new::<length::kilometer>(
apoapsis_distance.trim().parse::<f32>().unwrap(),
),
siderral_orbit_period: Time::new::<time::second>(
siderral_orbit_period.trim().parse::<f32>().unwrap(),
),
});
}
EphemerisOrbitalElementsParserState::End => {
Expand All @@ -407,8 +459,24 @@ impl<'a, Input: Iterator<Item = &'a str>> Iterator for EphemerisOrbitalElementsP
}
}

fn parse_date_time(line: &str) -> DateTime<Utc> {
let date_time_str: &str = line.split_terminator('=').collect::<Vec<_>>()[1].trim();

let date_time_str = take_expecting(date_time_str, "A.D. ").unwrap();

//let line = line.trim_end_matches("TDB").trim();
//let line = line.trim_end_matches(".0000");
let (time, _) = take_or_empty(date_time_str, 20); //Somehow the formatter does not like %.4f

NaiveDateTime::parse_from_str(time, "%Y-%b-%d %H:%M:%S")
.unwrap()
.and_utc()
}

#[cfg(test)]
mod tests {
use chrono::TimeZone;

use super::*;

#[test]
Expand All @@ -419,6 +487,7 @@ mod tests {
// TODO: This will probably fail intermittently due to float comparison.
assert_eq!(
EphemerisVectorItem {
time: Utc.with_ymd_and_hms(2022, 8, 13, 19, 55, 56).unwrap(), // A.D. 2022-Aug-13 19:55:56.0000 TDB
position: [
Length::new::<length::kilometer>(1.870010427985840E+02),
Length::new::<length::kilometer>(2.484687803242536E+03),
Expand All @@ -443,6 +512,8 @@ mod tests {
// TODO: This will probably fail intermittently due to float comparison.
assert_eq!(
EphemerisOrbitalElementsItem {
time: Utc.with_ymd_and_hms(2022, 6, 19, 18, 0, 0).unwrap(), // A.D. 2022-Jun-19 18:00:00.0000 TDB

eccentricity: 1.711794334680415E-02,
periapsis_distance: Length::new::<length::kilometer>(1.469885520304013E+08),
inclination: Angle::new::<angle::degree>(3.134746902320420E-03),
Expand All @@ -451,7 +522,9 @@ mod tests {
argument_of_perifocus: Angle::new::<angle::degree>(3.006492364709574E+02),
time_of_periapsis: Time::new::<time::day>(2459584.392523936927),

mean_motion: AngularVelocity::new::<angular_velocity::degree_per_second>(1.141316101270797E-05),
mean_motion: AngularVelocity::new::<angular_velocity::degree_per_second>(
1.141316101270797E-05
),
mean_anomaly: Angle::new::<angle::degree>(1.635515780663357E+02),
true_anomaly: Angle::new::<angle::degree>(1.640958153023696E+02),

Expand All @@ -462,4 +535,27 @@ mod tests {
ephem[0]
);
}

#[test]
fn test_parsing_date_time() {
let lines: [&str; 4] = [
"2459750.250000000 = A.D. 2022-Jun-19 18:00:00.0000 TDB ", // orbital_elements.txt
"2459750.375000000 = A.D. 2022-Jun-19 21:00:00.0000 TDB ",
"2459805.372175926 = A.D. 2022-Aug-13 20:55:56.0000 TDB ", // vector.txt
"2459805.455509259 = A.D. 2022-Aug-13 22:55:56.0000 TDB ",
];

let expected: [DateTime<Utc>; 4] = [
Utc.with_ymd_and_hms(2022, 6, 19, 18, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2022, 6, 19, 21, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2022, 8, 13, 20, 55, 56).unwrap(),
Utc.with_ymd_and_hms(2022, 8, 13, 22, 55, 56).unwrap(),
];

for (i, line) in lines.into_iter().enumerate() {
let time = parse_date_time(line);

assert_eq!(time, expected[i]);
}
}
}
4 changes: 2 additions & 2 deletions src/si/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod ephemeris;
mod client;
mod ephemeris;

pub use client::{ephemeris_orbital_elements, ephemeris_vector};
pub use ephemeris::{EphemerisOrbitalElementsItem, EphemerisVectorItem};
pub use ephemeris::{EphemerisOrbitalElementsItem, EphemerisVectorItem};
Loading

0 comments on commit 8898a75

Please sign in to comment.