Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/lox-io/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ rust-version.workspace = true

[dependencies]
nom.workspace = true
thiserror.workspace = true
96 changes: 85 additions & 11 deletions crates/lox-io/src/spice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,27 @@ use std::collections::HashMap;

use nom::branch::alt;
use nom::bytes::complete::{tag, take_until, take_while, take_while1};
use nom::character::complete::{alpha1, line_ending, multispace0, one_of};
use nom::character::complete::{alpha1, digit1, line_ending, multispace0, one_of};
use nom::combinator::{map, map_res, recognize, rest};
use nom::error::Error;
use nom::multi::{fold_many1, many0, many1};
use nom::number::complete::{double, float};
use nom::sequence::{delimited, preceded, separated_pair, terminated, tuple};
use nom::IResult;
use nom::{Finish, IResult};
use thiserror::Error;

#[derive(Debug, Error, PartialEq)]
#[error(transparent)]
pub struct KernelError(#[from] Error<String>);

#[derive(Clone, Debug, PartialEq)]
enum Value {
Double(f64),
String(String),
Timestamp(String),
DoubleArray(Vec<f64>),
StringArray(Vec<String>),
TimestampArray(Vec<String>),
}

#[derive(Clone, Debug, PartialEq)]
Expand All @@ -34,14 +42,14 @@ pub struct Kernel {
type Entries = Vec<(String, Value)>;

impl Kernel {
pub fn from_string(input: &str) -> Result<Self, &'static str> {
if let Ok(("", (type_id, entries, _))) = kernel(input) {
Ok(Self {
pub fn from_string(input: &str) -> Result<Self, KernelError> {
let result = kernel(input).map_err(|e| e.to_owned()).finish();
match result {
Ok((_, (type_id, entries, _))) => Ok(Self {
type_id: type_id.to_string(),
items: entries.into_iter().collect(),
})
} else {
Err("kernel parsing failed")
}),
Err(err) => Err(KernelError(err)),
}
}

Expand All @@ -67,6 +75,15 @@ impl Kernel {
}
}

pub fn get_timestamp_array(&self, key: &str) -> Option<&Vec<String>> {
let value = self.items.get(key)?;
if let Value::TimestampArray(v) = value {
Some(v)
} else {
None
}
}

pub fn keys(&self) -> Vec<&String> {
self.items.keys().collect()
}
Expand Down Expand Up @@ -120,8 +137,26 @@ fn spice_string(s: &str) -> IResult<&str, String> {
parser(s)
}

fn timestamp(s: &str) -> IResult<&str, String> {
let mut parser = map(
// NASA NAIF's LSK kernels break their own rules and mix timestamps with integers within a
// single array.
// See: https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/kernel.html#Variable%20Value%20Rules
alt((
preceded(tag("@"), take_while1(|c| !is_separator(c))),
digit1,
)),
String::from,
);
parser(s)
}

fn is_separator(c: char) -> bool {
c.is_whitespace() || c == ','
}

fn separator(s: &str) -> IResult<&str, &str> {
take_while1(|x: char| x.is_whitespace() || x == ',')(s)
take_while1(is_separator)(s)
}

fn double_array(s: &str) -> IResult<&str, Value> {
Expand All @@ -148,6 +183,18 @@ fn string_array(s: &str) -> IResult<&str, Value> {
parser(s)
}

fn timestamp_array(s: &str) -> IResult<&str, Value> {
let mut parser = map(
delimited(
terminated(tag("("), separator),
many1(terminated(timestamp, separator)),
tag(")"),
),
Value::TimestampArray,
);
parser(s)
}

fn double_value(s: &str) -> IResult<&str, Value> {
let mut parser = map(spice_double, Value::Double);
parser(s)
Expand All @@ -158,8 +205,13 @@ fn string_value(s: &str) -> IResult<&str, Value> {
parser(s)
}

fn timestamp_value(s: &str) -> IResult<&str, Value> {
let mut parser = map(timestamp, Value::Timestamp);
parser(s)
}

fn array_value(s: &str) -> IResult<&str, Value> {
let mut parser = alt((double_array, string_array));
let mut parser = alt((double_array, string_array, timestamp_array));
parser(s)
}

Expand All @@ -171,7 +223,7 @@ fn key_value(s: &str) -> IResult<&str, (String, Value)> {
take_while(char::is_whitespace),
),
terminated(tag("="), take_while1(char::is_whitespace)),
alt((double_value, string_value, array_value)),
alt((double_value, string_value, timestamp_value, array_value)),
),
|kv: (&str, Value)| (kv.0.to_string(), kv.1),
);
Expand Down Expand Up @@ -243,6 +295,11 @@ mod tests {
);
}

#[test]
fn test_timestamp() {
assert_eq!(timestamp("@1972-JAN-1"), Ok(("", "1972-JAN-1".to_string())));
}

#[test]
fn test_separator() {
assert_eq!(separator(" "), Ok(("", " ")));
Expand Down Expand Up @@ -289,6 +346,23 @@ mod tests {
);
}

#[test]
fn test_timestamp_array() {
let input = "( @1972-JAN-1,@1972-JAN-1 \
@1972-JAN-1 )";
assert_eq!(
timestamp_array(input),
Ok((
"",
Value::TimestampArray(vec!(
"1972-JAN-1".to_string(),
"1972-JAN-1".to_string(),
"1972-JAN-1".to_string()
))
))
);
}

#[test]
fn test_array() {
let exp_float = Value::DoubleArray(vec![6378.1366, 6378.1366, 6356.7519]);
Expand Down
85 changes: 85 additions & 0 deletions crates/lox-io/tests/lsk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2023. Helge Eichhorn and the LOX contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

use lox_io::spice::Kernel;

#[test]
fn test_lsk() {
let lsk = include_str!("../../../data/naif0012.tls");
let kernel = Kernel::from_string(lsk).expect("file should be parsable");
assert_eq!(kernel.type_id(), "LSK");

assert!(!kernel.keys().is_empty());

let exp = vec![
"10",
"1972-JAN-1",
"11",
"1972-JUL-1",
"12",
"1973-JAN-1",
"13",
"1974-JAN-1",
"14",
"1975-JAN-1",
"15",
"1976-JAN-1",
"16",
"1977-JAN-1",
"17",
"1978-JAN-1",
"18",
"1979-JAN-1",
"19",
"1980-JAN-1",
"20",
"1981-JUL-1",
"21",
"1982-JUL-1",
"22",
"1983-JUL-1",
"23",
"1985-JUL-1",
"24",
"1988-JAN-1",
"25",
"1990-JAN-1",
"26",
"1991-JAN-1",
"27",
"1992-JUL-1",
"28",
"1993-JUL-1",
"29",
"1994-JUL-1",
"30",
"1996-JAN-1",
"31",
"1997-JUL-1",
"32",
"1999-JAN-1",
"33",
"2006-JAN-1",
"34",
"2009-JAN-1",
"35",
"2012-JUL-1",
"36",
"2015-JUL-1",
"37",
"2017-JAN-1",
];
let act = kernel
.get_timestamp_array("DELTET/DELTA_AT")
.expect("array should be present");
assert_eq!(act, &exp);

assert!(kernel.get_timestamp_array("DELTET/DELTA_T_A").is_none());
assert!(kernel.get_double("DELTET/DELTA_AT").is_none());
assert!(kernel.get_double_array("DELTET/DELTA_AT").is_none());
}
1 change: 1 addition & 0 deletions crates/lox-time/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ license.workspace = true
authors.workspace = true

[dependencies]
lox-io.workspace = true
lox-utils.workspace = true
lox-eop.workspace = true

Expand Down
43 changes: 33 additions & 10 deletions crates/lox-time/src/calendar_dates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ impl Date {
Err(DateError::InvalidDate(year, month, day))
} else {
let calendar = calendar(year, month, day);
let check = Date::from_days_since_j2000(days_since_j2000(calendar, year, month, day));
let check = Date::from_days_since_j2000(j2000_day_number(calendar, year, month, day));

if check.year() != year || check.month() != month || check.day() != day {
Err(DateError::InvalidDate(year, month, day))
Expand Down Expand Up @@ -201,12 +201,17 @@ impl Date {
day,
})
}

pub fn j2000_day_number(&self) -> i64 {
j2000_day_number(self.calendar, self.year, self.month, self.day)
}
}

impl JulianDate for Date {
fn julian_date(&self, epoch: Epoch, unit: Unit) -> f64 {
let mut seconds =
days_since_j2000(self.calendar, self.year, self.month, self.day) * SECONDS_PER_DAY;
let mut seconds = j2000_day_number(self.calendar, self.year, self.month, self.day)
* SECONDS_PER_DAY
- SECONDS_PER_HALF_DAY;
seconds = match epoch {
Epoch::JulianDate => seconds + SECONDS_BETWEEN_JD_AND_J2000,
Epoch::ModifiedJulianDate => seconds + SECONDS_BETWEEN_MJD_AND_J2000,
Expand Down Expand Up @@ -309,7 +314,7 @@ fn calendar(year: i64, month: u8, day: u8) -> Calendar {
}
}

fn days_since_j2000(calendar: Calendar, year: i64, month: u8, day: u8) -> i64 {
fn j2000_day_number(calendar: Calendar, year: i64, month: u8, day: u8) -> i64 {
let d1 = last_day_of_year_j2k(calendar, year - 1);
let d2 = find_day_in_year(month, day, is_leap_year(calendar, year));
d1 + d2 as i64
Expand Down Expand Up @@ -377,17 +382,35 @@ mod tests {
assert_eq!(actual, expected);
}

#[test]
fn test_date_jd_epoch() {
let date = Date::default();
assert_eq!(date.days_since_julian_epoch(), 2451544.5);
}

#[test]
fn test_date_julian_date() {
let date = Date::new(2100, 1, 1).unwrap();
assert_eq!(date.seconds_since_j2000(), SECONDS_PER_JULIAN_CENTURY);
assert_eq!(date.days_since_j2000(), DAYS_PER_JULIAN_CENTURY);
assert_eq!(date.centuries_since_j2000(), 1.0);
assert_eq!(date.centuries_since_j1950(), 1.5);
assert_eq!(
date.seconds_since_j2000(),
SECONDS_PER_JULIAN_CENTURY - SECONDS_PER_HALF_DAY as f64
);
assert_eq!(date.days_since_j2000(), DAYS_PER_JULIAN_CENTURY - 0.5);
assert_eq!(
date.centuries_since_j2000(),
1.0 - 0.5 / DAYS_PER_JULIAN_CENTURY
);
assert_eq!(
date.centuries_since_j1950(),
1.5 - 0.5 / DAYS_PER_JULIAN_CENTURY
);
assert_eq!(
date.centuries_since_modified_julian_epoch(),
2.411211498973306
2.411211498973306 - 0.5 / DAYS_PER_JULIAN_CENTURY
);
assert_eq!(
date.centuries_since_julian_epoch(),
68.11964407939767 - 0.5 / DAYS_PER_JULIAN_CENTURY
);
assert_eq!(date.centuries_since_julian_epoch(), 68.11964407939767);
}
}
2 changes: 1 addition & 1 deletion crates/lox-time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl<T: TimeScale + Copy> Time<T> {

/// Instantiates a [Time] in the given `scale` from a [Date] and a [TimeOfDay].
pub fn from_date_and_time(scale: T, date: Date, time: TimeOfDay) -> Result<Self, TimeError> {
let mut seconds = ((date.days_since_j2000() - 0.5) * time::SECONDS_PER_DAY)
let mut seconds = (date.days_since_j2000() * time::SECONDS_PER_DAY)
.to_i64()
.unwrap_or_else(|| {
unreachable!(
Expand Down
7 changes: 7 additions & 0 deletions crates/lox-time/src/transformations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::constants::julian_dates::J77;
use crate::deltas::TimeDelta;
use crate::subsecond::Subsecond;
use crate::time_scales::{Tai, Tcb, Tcg, Tdb, TimeScale, Tt};
use crate::utc::Utc;
use crate::Time;

/// TransformTimeScale transforms a [Time] in [TimeScale] `T` to the corresponding [Time] in
Expand Down Expand Up @@ -196,6 +197,12 @@ fn delta_tdb_tt(time: Time<Tdb>) -> TimeDelta {
})
}

pub trait LeapSecondsProvider {
fn delta_tai_utc(&self, tai: Time<Tai>) -> Option<TimeDelta>;

fn delta_utc_tai(&self, utc: Utc) -> Option<TimeDelta>;
}

#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
Expand Down
Loading