#[derive(Debug, Clone, Copy)]
pub struct PlanetShape {
pub name: &'static str,
pub mu: f64,
pub r_eq: f64,
pub r_pol: f64,
pub flat_coeff: f64,
}
impl PlanetShape {
pub fn flat_inv(&self) -> f64 {
1.0 / self.flat_coeff
}
pub fn e_ellipsoid(&self) -> f64 {
let f = self.flat_coeff;
(2.0 * f - f * f).sqrt()
}
pub fn e_ellip_sq(&self) -> f64 {
let f = self.flat_coeff;
2.0 * f - f * f
}
pub fn mu_typed(
&self,
) -> astrodyn_quantities::dims::GravParam<astrodyn_quantities::frame::SelfPlanet> {
astrodyn_quantities::dims::GravParam::<astrodyn_quantities::frame::SelfPlanet>::from_si(
self.mu,
)
}
pub fn r_eq_typed(&self) -> uom::si::f64::Length {
use astrodyn_quantities::ext::F64Ext;
self.r_eq.m()
}
pub fn r_pol_typed(&self) -> uom::si::f64::Length {
use astrodyn_quantities::ext::F64Ext;
self.r_pol.m()
}
}
#[cfg(test)]
mod tests {
use crate::presets::*;
#[test]
fn earth_mu_typed_matches_f64_field() {
let mu = EARTH.mu_typed();
assert_eq!(mu.value, EARTH.mu);
}
#[test]
fn earth_r_eq_typed_matches_f64_field() {
use uom::si::length::meter;
let r_eq = EARTH.r_eq_typed();
assert_eq!(r_eq.get::<meter>(), EARTH.r_eq);
}
#[test]
fn earth_r_pol_typed_matches_f64_field() {
use uom::si::length::meter;
let r_pol = EARTH.r_pol_typed();
assert_eq!(r_pol.get::<meter>(), EARTH.r_pol);
}
#[test]
fn polar_radius_consistent_with_flattening() {
for planet in [EARTH, MOON, SUN, MARS] {
let expected_r_pol = planet.r_eq * (1.0 - planet.flat_coeff);
let err = (planet.r_pol - expected_r_pol).abs();
assert!(
err < 1.0, "{}: r_pol={} vs r_eq*(1-f)={}, err={}",
planet.name,
planet.r_pol,
expected_r_pol,
err
);
}
}
#[test]
fn all_values_positive() {
for planet in [EARTH, MOON, SUN, MARS] {
assert!(planet.mu > 0.0, "{}: mu must be positive", planet.name);
assert!(planet.r_eq > 0.0, "{}: r_eq must be positive", planet.name);
assert!(
planet.r_pol > 0.0,
"{}: r_pol must be positive",
planet.name
);
assert!(
planet.flat_coeff > 0.0,
"{}: flattening must be positive",
planet.name
);
assert!(
planet.flat_coeff < 1.0,
"{}: flattening must be < 1",
planet.name
);
assert!(
planet.r_eq >= planet.r_pol,
"{}: r_eq must be >= r_pol",
planet.name
);
}
}
#[test]
fn earth_inverse_flattening() {
let inv_f = EARTH.flat_inv();
assert!(
(inv_f - 298.257223563).abs() < 1e-6,
"Earth 1/f: expected 298.257223563, got {}",
inv_f
);
}
#[test]
fn eccentricity_positive() {
for planet in [EARTH, MOON, SUN, MARS] {
let e = planet.e_ellipsoid();
assert!(e > 0.0 && e < 1.0, "{}: e={}", planet.name, e);
}
}
use crate::planet::PlanetShape;
const TEST_SHAPE: PlanetShape = PlanetShape {
name: "Testopia",
mu: 1.5e14,
r_eq: 6_400_000.0,
r_pol: 6_400_000.0 * (1.0 - 1.0 / 300.0),
flat_coeff: 1.0 / 300.0,
};
#[test]
fn shape_struct_field_access_round_trip() {
assert_eq!(TEST_SHAPE.name, "Testopia");
assert_eq!(TEST_SHAPE.mu, 1.5e14);
assert_eq!(TEST_SHAPE.r_eq, 6_400_000.0);
assert_eq!(TEST_SHAPE.flat_coeff, 1.0 / 300.0);
}
#[test]
fn radius_accessors_distinct_and_consistent() {
for planet in [TEST_SHAPE, EARTH, MOON, SUN, MARS] {
assert!(
planet.r_eq > planet.r_pol,
"{}: r_eq={} not > r_pol={}",
planet.name,
planet.r_eq,
planet.r_pol
);
let derived_pol = planet.r_eq * (1.0 - planet.flat_coeff);
let err = (derived_pol - planet.r_pol).abs();
assert!(
err < 1.0,
"{}: r_pol drifted by {err} m from r_eq*(1-f)",
planet.name
);
}
}
#[test]
fn flattening_inverse_round_trip() {
let f = TEST_SHAPE.flat_coeff;
let inv = TEST_SHAPE.flat_inv();
assert!(
(inv * f - 1.0).abs() < 1e-14,
"flat_inv({f}) * f != 1: got {}",
inv * f
);
}
#[test]
fn mu_accessor_matches_typed_and_preserves_value() {
for planet in [TEST_SHAPE, EARTH, MOON, SUN, MARS] {
assert_eq!(planet.mu_typed().value, planet.mu);
}
}
#[test]
fn eccentricity_squared_matches_definition() {
for planet in [TEST_SHAPE, EARTH, MOON, SUN, MARS] {
let f = planet.flat_coeff;
let expected = 2.0 * f - f * f;
assert!((planet.e_ellip_sq() - expected).abs() < 1e-15);
let e = planet.e_ellipsoid();
assert!((e * e - planet.e_ellip_sq()).abs() < 1e-15);
}
}
#[test]
fn shape_struct_is_copy() {
fn take_by_value(s: PlanetShape) -> f64 {
s.r_eq
}
let s = TEST_SHAPE;
assert_eq!(take_by_value(s), TEST_SHAPE.r_eq);
assert_eq!(s.name, "Testopia");
}
}