apsis-rs: add validation examples for geodesy and time
This commit is contained in:
parent
9fd8c9b2fc
commit
a79a6502e0
|
|
@ -25,6 +25,12 @@ version = "0.1.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsis-coord"
|
name = "apsis-coord"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"apsis-core",
|
||||||
|
"apsis-frames",
|
||||||
|
"apsis-geodesy",
|
||||||
|
"apsis-math",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsis-core"
|
name = "apsis-core"
|
||||||
|
|
@ -49,10 +55,18 @@ version = "0.1.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsis-frames"
|
name = "apsis-frames"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"apsis-core",
|
||||||
|
"apsis-math",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsis-geodesy"
|
name = "apsis-geodesy"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"apsis-core",
|
||||||
|
"apsis-math",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsis-handover"
|
name = "apsis-handover"
|
||||||
|
|
@ -195,6 +209,10 @@ version = "0.1.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsis-time"
|
name = "apsis-time"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"apsis-core",
|
||||||
|
"apsis-leap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsis-tle"
|
name = "apsis-tle"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,17 @@ publish = false
|
||||||
name = "apsis"
|
name = "apsis"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
# Examples that produce validation input. Each needs the layers it reads
|
||||||
|
# from, so it is built only when the matching feature is enabled.
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "dump_geodesy"
|
||||||
|
required-features = ["physics"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "dump_time"
|
||||||
|
required-features = ["physics"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
//! Writes a grid of geodetic positions and the Earth-fixed Cartesian
|
||||||
|
//! coordinates this workspace computes for them.
|
||||||
|
//!
|
||||||
|
//! The output is CSV on standard output, with a header row. It is consumed
|
||||||
|
//! by `validation/python/coord/check_geodetic.py`, which recomputes the same
|
||||||
|
//! positions with a reference implementation and compares the two.
|
||||||
|
//!
|
||||||
|
//! Usage:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! cargo run -p apsis-rs --features physics --example dump_geodesy \
|
||||||
|
//! > validation/data/expected/rust_geodetic.csv
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use apsis_coord::{Geodetic, WGS84};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("latitude_degrees,longitude_degrees,height_m,x_m,y_m,z_m");
|
||||||
|
let latitudes = [-89.0_f64, -60.0, -30.0, 0.0, 30.0, 45.0, 60.0, 89.0];
|
||||||
|
let longitudes = [-179.0_f64, -90.0, -45.0, 0.0, 45.0, 90.0, 179.0];
|
||||||
|
let heights = [-1_000.0_f64, 0.0, 100.0, 3_000.0, 35_786_000.0];
|
||||||
|
|
||||||
|
for latitude in latitudes {
|
||||||
|
for longitude in longitudes {
|
||||||
|
for height in heights {
|
||||||
|
let point = Geodetic::new(latitude.to_radians(), longitude.to_radians(), height)
|
||||||
|
.to_ecef_on(WGS84)
|
||||||
|
.vector();
|
||||||
|
println!(
|
||||||
|
"{latitude},{longitude},{height},{:.9},{:.9},{:.9}",
|
||||||
|
point.x, point.y, point.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
//! Writes the leap second offset and the time scale conversions this
|
||||||
|
//! workspace computes over a range of instants.
|
||||||
|
//!
|
||||||
|
//! The output is CSV on standard output, with a header row. It is consumed
|
||||||
|
//! by `validation/python/time/check_time.py`, which recomputes each instant
|
||||||
|
//! with a reference implementation.
|
||||||
|
//!
|
||||||
|
//! The TAI instant is written in two parts, an integer Modified Julian Date
|
||||||
|
//! and a fraction of a day, because a single `f64` cannot hold a Modified
|
||||||
|
//! Julian Date near 60 000 to better than about 40 microseconds. The two
|
||||||
|
//! parts let the checker compare instants to well below a microsecond.
|
||||||
|
//!
|
||||||
|
//! Usage:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! cargo run -p apsis-rs --features physics --example dump_time \
|
||||||
|
//! > validation/data/expected/rust_time.csv
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use apsis_leap::{LEAP_SECONDS, tai_minus_utc_at_mjd};
|
||||||
|
use apsis_time::{Calendar, Epoch, TT_MINUS_TAI, TimeScale};
|
||||||
|
|
||||||
|
/// Splits a Modified Julian Date into its integer day and its fraction.
|
||||||
|
fn split(mjd: f64) -> (i64, f64) {
|
||||||
|
let day = mjd.floor();
|
||||||
|
(day as i64, mjd - day)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit(calendar: Calendar, epoch: Epoch) {
|
||||||
|
let offset = tai_minus_utc_at_mjd(
|
||||||
|
epoch
|
||||||
|
.mjd(TimeScale::Utc)
|
||||||
|
.expect("every emitted instant is in UTC"),
|
||||||
|
)
|
||||||
|
.expect("every emitted instant is covered by the table");
|
||||||
|
let (tai_day, tai_frac) = split(epoch.tai_mjd());
|
||||||
|
println!(
|
||||||
|
"{:04},{:02},{:02},{:02},{:02},{:.3},{:.1},{tai_day},{tai_frac:.17}",
|
||||||
|
calendar.year,
|
||||||
|
calendar.month,
|
||||||
|
calendar.day,
|
||||||
|
calendar.hour,
|
||||||
|
calendar.minute,
|
||||||
|
calendar.second,
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("year,month,day,hour,minute,second,tai_minus_utc,tai_day,tai_frac");
|
||||||
|
|
||||||
|
// Sample every leap second boundary at several times of day, so that
|
||||||
|
// the step behaviour and the interior of each interval are covered.
|
||||||
|
for entry in LEAP_SECONDS {
|
||||||
|
for (hour, minute, second) in [(0, 0, 0.0), (6, 0, 0.0), (12, 0, 0.0), (18, 0, 0.0)] {
|
||||||
|
let calendar = Calendar::new(entry.year, entry.month, entry.day, hour, minute, second);
|
||||||
|
if let Some(epoch) = Epoch::from_calendar(calendar, TimeScale::Utc) {
|
||||||
|
emit(calendar, epoch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last second before a leap second and the first second after it.
|
||||||
|
for (year, month, day, hour, minute, second) in [
|
||||||
|
(2016, 12, 31, 23, 59, 59.0),
|
||||||
|
(2017, 1, 1, 0, 0, 0.0),
|
||||||
|
(2016, 12, 31, 12, 0, 0.0),
|
||||||
|
(1998, 12, 31, 23, 59, 59.0),
|
||||||
|
(1999, 1, 1, 0, 0, 0.0),
|
||||||
|
] {
|
||||||
|
let calendar = Calendar::new(year, month, day, hour, minute, second);
|
||||||
|
if let Some(epoch) = Epoch::from_calendar(calendar, TimeScale::Utc) {
|
||||||
|
emit(calendar, epoch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Terrestrial Time offset is a defining constant, written here so
|
||||||
|
// that the checker can verify it without recomputing the calendar.
|
||||||
|
println!("# tt_minus_tai_seconds={TT_MINUS_TAI:.3}");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue