Files
astrokit/src/frames.rs
T

137 lines
4.1 KiB
Rust

//! Reference frames: where a state's origin is and how its axes are oriented.
//!
//! This module carries the frame *type*. Transformations between orientations —
//! J2000 to ITRF, topocentric, geodetic site coordinates — are a separate piece of
//! work and will land here on top of ANISE.
use serde::{Deserialize, Serialize};
use crate::body::Body;
/// How a frame's axes are oriented.
///
/// Provisional and deliberately coarse. The distinctions that matter for
/// sub-arcsecond work — ICRF against EME2000, ITRF against a body-fixed IAU frame
/// — arrive with the transformations, which is the point at which getting them
/// wrong would show up. Marked `#[non_exhaustive]` so refining it is not a
/// breaking change.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Orientation {
/// Inertial, aligned with the J2000 mean equator and equinox.
J2000,
/// Rotating with the central body.
BodyFixed,
}
/// A reference frame: a central body and an orientation.
///
/// Held by every [`Cartesian`](crate::cartesian::Cartesian) and every element set,
/// which is what lets conversions read `mu` from the state rather than take it as
/// an argument.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Frame {
/// The body at the origin.
pub center: Body,
/// How the axes are oriented.
pub orientation: Orientation,
}
impl Frame {
/// Earth-centered, J2000-oriented.
pub const EARTH_J2000: Self = Self {
center: Body::EARTH,
orientation: Orientation::J2000,
};
/// Earth-centered, rotating with the Earth.
pub const EARTH_FIXED: Self = Self {
center: Body::EARTH,
orientation: Orientation::BodyFixed,
};
/// Moon-centered, J2000-oriented.
pub const MOON_J2000: Self = Self {
center: Body::MOON,
orientation: Orientation::J2000,
};
/// A J2000-oriented frame centered on `center`.
pub const fn inertial(center: Body) -> Self {
Self {
center,
orientation: Orientation::J2000,
}
}
/// A frame rotating with `center`.
pub const fn body_fixed(center: Body) -> Self {
Self {
center,
orientation: Orientation::BodyFixed,
}
}
/// The central body's gravitational parameter, m^3/s^2.
pub fn mu(&self) -> f64 {
self.center.mu
}
/// Whether orbital motion in this frame is unaccelerated by the frame itself.
///
/// Element conversions and the two-body relations are only meaningful in an
/// inertial frame; in a rotating one the velocity is not the inertial velocity.
pub fn is_inertial(&self) -> bool {
matches!(self.orientation, Orientation::J2000)
}
/// The same frame with a different orientation.
pub fn with_orientation(&self, orientation: Orientation) -> Self {
Self {
orientation,
..*self
}
}
}
impl Default for Frame {
fn default() -> Self {
Self::EARTH_J2000
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_carries_the_central_bodys_mu() {
assert_eq!(Frame::EARTH_J2000.mu(), Body::EARTH.mu);
assert_eq!(Frame::MOON_J2000.mu(), Body::MOON.mu);
}
#[test]
fn test_only_inertial_frames_report_inertial() {
assert!(Frame::EARTH_J2000.is_inertial());
assert!(!Frame::EARTH_FIXED.is_inertial());
assert!(Frame::inertial(Body::SUN).is_inertial());
assert!(!Frame::body_fixed(Body::SUN).is_inertial());
}
#[test]
fn test_orientation_can_be_swapped_keeping_the_center() {
let fixed = Frame::EARTH_J2000.with_orientation(Orientation::BodyFixed);
assert_eq!(fixed, Frame::EARTH_FIXED);
assert_eq!(fixed.center, Body::EARTH);
}
#[test]
fn test_frame_round_trips_through_json() {
let json = serde_json::to_string(&Frame::MOON_J2000).unwrap();
assert_eq!(
serde_json::from_str::<Frame>(&json).unwrap(),
Frame::MOON_J2000
);
}
}