apsis-rs/crates/orbit/apsis-orbit/tests/orbit.rs

173 lines
6.0 KiB
Rust

//! Tests for the combined orbit type.
use apsis_orbit::{GravityModel, Orbit};
use apsis_tle::TwoLineElement;
const ISS: &str = concat!(
"1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\r\n",
"2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537\r\n",
);
const DEEP_SPACE: &str = concat!(
"1 09880U 77021A 06174.85710247 .00000090 00000-0 10000-3 0 7322\r\n",
"2 09880 64.1526 313.1296 6546925 103.4683 305.3980 2.00754276 13047\r\n",
);
fn orbit(text: &str) -> Orbit {
let element = TwoLineElement::parse(text).expect("test element set parses");
Orbit::from_element(&element, GravityModel::WGS72).expect("test element set is propagatable")
}
#[test]
fn construction_succeeds_for_a_valid_element_set() {
let orbit = orbit(ISS);
assert!(!orbit.is_deep_space());
assert_eq!(
orbit.gravity_parameter(),
apsis_orbit::GravityParameter::WGS72
);
}
#[test]
fn deep_space_orbits_are_reported() {
assert!(orbit(DEEP_SPACE).is_deep_space());
}
#[test]
fn propagation_matches_the_underlying_propagator() {
let orbit = orbit(ISS);
let via_orbit = orbit.propagate_minutes(360.0).expect("state exists");
let via_satellite = orbit
.satellite()
.propagate_minutes(360.0)
.expect("state exists");
assert_eq!(via_orbit, via_satellite);
}
#[test]
fn position_and_radius_accessors_agree_with_the_state() {
let orbit = orbit(ISS);
let state = orbit.propagate_minutes(120.0).expect("state exists");
assert_eq!(
orbit.position_at(120.0).expect("state exists"),
state.position
);
assert!((orbit.radius_at(120.0).expect("state exists") - state.position.norm()).abs() < 1e-12);
}
#[test]
fn propagate_at_uses_the_element_epoch() {
let orbit = orbit(ISS);
let epoch = orbit.epoch_mjd();
let at_epoch = orbit.propagate_at(epoch).expect("state exists");
let at_zero = orbit.propagate_minutes(0.0).expect("state exists");
assert!((at_epoch.position - at_zero.position).norm() < 1e-6);
// One day later is 1440 minutes.
let next_day = orbit.propagate_at(epoch + 1.0).expect("state exists");
let one_day = orbit.propagate_minutes(1440.0).expect("state exists");
assert!((next_day.position - one_day.position).norm() < 1e-3);
}
#[test]
fn osculating_elements_describe_a_low_earth_orbit() {
let orbit = orbit(ISS);
let elements = orbit.osculating_elements(0.0).expect("state exists");
// These values were checked against an independent computation from the
// same state vector. The inclination agrees with the catalogued value
// closely, because the oblateness has little effect on it, while the
// eccentricity differs from the catalogued 0.0006703. That difference
// is expected: the element set stores mean elements and the osculating
// eccentricity includes the short period variation the two-body model
// does not reproduce.
assert!(
(elements.semi_major_axis - 6_725.542).abs() < 0.01,
"semi-major axis {}",
elements.semi_major_axis
);
assert!(
(elements.eccentricity - 0.000_832_1).abs() < 1e-6,
"eccentricity {}",
elements.eccentricity
);
assert!(
(elements.inclination - 51.6217_f64.to_radians()).abs() < 1e-4,
"inclination {}",
elements.inclination.to_degrees()
);
}
#[test]
fn osculating_elements_reproduce_the_state() {
let orbit = orbit(ISS);
let state = orbit.propagate_minutes(45.0).expect("state exists");
let elements = orbit.osculating_elements(45.0).expect("state exists");
let rebuilt = elements
.to_state(orbit.gravity_parameter())
.expect("elements are valid");
assert!((rebuilt.position - state.position).norm() < 1e-6);
assert!((rebuilt.velocity - state.velocity).norm() < 1e-9);
}
#[test]
fn osculating_elements_change_along_the_orbit() {
// The osculating elements are not the mean elements, so they vary
// because the perturbations SGP4 models are absent from the two-body
// reconstruction.
let orbit = orbit(ISS);
let start = orbit.osculating_elements(0.0).expect("state exists");
let later = orbit.osculating_elements(720.0).expect("state exists");
assert!((start.eccentricity - later.eccentricity).abs() > 1e-7);
}
#[test]
fn osculating_elements_work_for_a_deep_space_orbit() {
let orbit = orbit(DEEP_SPACE);
let elements = orbit.osculating_elements(0.0).expect("state exists");
// The catalogued mean values are an eccentricity of 0.6546925 and an
// inclination of 64.1526 degrees. The osculating values are close but
// not identical for the reason given in the low Earth orbit test.
assert!(
(elements.eccentricity - 0.655_871_3).abs() < 1e-6,
"eccentricity {}",
elements.eccentricity
);
assert!(
(elements.inclination - 64.1696_f64.to_radians()).abs() < 1e-4,
"inclination {}",
elements.inclination.to_degrees()
);
assert!(
(elements.semi_major_axis - 26_543.982).abs() < 0.01,
"semi-major axis {}",
elements.semi_major_axis
);
}
#[test]
fn the_gravity_parameter_matches_the_model() {
let element = TwoLineElement::parse(ISS).expect("parses");
let wgs72 = Orbit::from_element(&element, GravityModel::WGS72).expect("propagatable");
assert_eq!(
wgs72.gravity_parameter(),
apsis_orbit::GravityParameter::WGS72
);
let wgs84 = Orbit::from_element(&element, GravityModel::WGS84).expect("propagatable");
assert_eq!(
wgs84.gravity_parameter(),
apsis_orbit::GravityParameter::WGS84
);
}
#[test]
fn the_facade_reexports_the_layer() {
// The facade must make the element parser and the propagator types
// reachable without a separate dependency.
let element = apsis_orbit::TwoLineElement::parse(ISS).expect("parses");
assert_eq!(element.satellite_number, 25544);
let satellite =
apsis_orbit::Satellite::from_element(&element, GravityModel::WGS72).expect("propagatable");
assert!(satellite.propagate_minutes(0.0).is_ok());
}