//! 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::(&json).unwrap(), Frame::MOON_J2000 ); } }