1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
use anyhow::bail;
/// Reference frame identifier.
///
/// Used throughout satkit to tag vectors with the coordinate system in which
/// they are expressed. For Earth-centred inertial and Earth-fixed frames the
/// definitions follow the IERS conventions; for spacecraft-local orbital
/// frames see the individual variant docs.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Frame {
/// International Terrestrial Reference Frame
ITRF,
/// Terrestrial Intermediate Reference System
TIRS,
/// Celestial Intermediate Reference System
CIRS,
/// Geocentric Celestial Reference Frame
GCRF,
/// True Equator Mean Equinox
TEME,
/// Earth Mean Equator 2000
EME2000,
/// International Celestial Reference Frame
ICRF,
/// Local Vertical Local Horizontal
///
/// * z axis = -r (nadir)
/// * y axis = -h (opposite angular momentum, h = r × v)
/// * x axis completes the right-handed system (approximately velocity direction for circular orbits)
LVLH,
/// Radial / Tangential / Normal — CCSDS OEM/OMM/ODM convention.
///
/// Also known as **RSW** (Vallado's *Fundamentals of Astrodynamics*)
/// or **RIC** (Radial / In-track / Cross-track, older NASA usage and
/// Clohessy-Wiltshire literature). The three names refer to the
/// same axes; different communities use different letters. Compile-
/// time aliases [`Frame::RSW`] and [`Frame::RIC`] are available so
/// user code can spell the frame whichever way matches the source
/// it's transcribing from — all three resolve to the same enum
/// variant.
///
/// * **R** (radial): unit vector along position, outward from Earth centre
/// * **T** (tangential / in-track): perpendicular to R in the orbit
/// plane, in the prograde direction. **Not** parallel to the
/// velocity vector unless the flight-path angle is zero (circular
/// orbit, or perigee/apogee of an eccentric orbit). For eccentric
/// orbits at non-apsidal true anomaly, use [`Frame::NTW`] if you
/// want an axis parallel to velocity.
/// * **N** (normal / cross-track): along the angular momentum
/// direction h = r × v, completing the right-handed system
///
/// This frame is the standard choice for **CCSDS OEM / OMM / ODM
/// orbit data messages** (which label it RTN), for **relative
/// motion** formulations (Hill / Clohessy-Wiltshire equations, often
/// written in RIC), and for **radial/normal burn components** whose
/// physical meaning is tied to the position vector. `satkit`'s
/// state-vector uncertainty API accepts RTN as one of several
/// frames — see
/// [`SatState::set_pos_uncertainty`](crate::orbitprop::SatState::set_pos_uncertainty).
RTN,
/// Velocity-aligned orbital frame (Vallado §3.3).
///
/// * **N** (normal-to-velocity, in-plane): T̂ × Ŵ. For a circular orbit
/// this is the outward radial direction; for an eccentric orbit it
/// leans away from the radial by the flight-path angle.
/// * **T** (tangent): v̂, the unit velocity vector
/// * **W** (cross-track, out of plane): (r × v) / |r × v|, same as
/// RIC's C axis
///
/// This is the natural frame for **thrust-along-velocity maneuvers**:
/// a pure +T delta-v of magnitude Δv adds *exactly* Δv to |v| and
/// changes orbital energy by v · Δv. For eccentric orbits at non-apsidal
/// true anomalies, NTW and RIC differ by the flight-path angle, so the
/// choice matters. Use NTW when planning prograde/retrograde burns,
/// Hohmann transfers, or anything else where "along velocity" is the
/// physically meaningful direction. Use [`Frame::RTN`] when you want
/// "perpendicular to position" semantics or when interoperating with
/// CCSDS OEM / OMM covariance messages (which use this frame under
/// the RTN name).
NTW,
}
impl Frame {
/// Compile-time alias for [`Frame::RTN`] — Vallado's name for the same
/// Radial / S=(W×R) / W=(R×V) orbital frame. Provided so code
/// transcribing formulas from Vallado's *Fundamentals of Astrodynamics*
/// can use the source's variable names verbatim. Semantically identical
/// to `Frame::RTN`; `Display` always renders as `RTN`.
pub const RSW: Self = Self::RTN;
/// Compile-time alias for [`Frame::RTN`] — the older NASA /
/// Clohessy-Wiltshire literature name for the same axes (Radial /
/// In-track / Cross-track). Provided for backward compatibility with
/// code that was written against the previous canonical name in
/// earlier satkit versions, and for readers of papers that use
/// "RIC" rather than "RTN". Semantically identical to `Frame::RTN`;
/// `Display` always renders as `RTN`.
pub const RIC: Self = Self::RTN;
}
impl std::fmt::Display for Frame {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::ITRF => write!(f, "ITRF"),
Self::TIRS => write!(f, "TIRS"),
Self::CIRS => write!(f, "CIRS"),
Self::GCRF => write!(f, "GCRF"),
Self::TEME => write!(f, "TEME"),
Self::EME2000 => write!(f, "EME2000"),
Self::ICRF => write!(f, "ICRF"),
Self::LVLH => write!(f, "LVLH"),
Self::RTN => write!(f, "RTN"),
Self::NTW => write!(f, "NTW"),
}
}
}
impl std::str::FromStr for Frame {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ITRF" => Ok(Self::ITRF),
"TIRS" => Ok(Self::TIRS),
"CIRS" => Ok(Self::CIRS),
"GCRF" => Ok(Self::GCRF),
"TEME" => Ok(Self::TEME),
"EME2000" => Ok(Self::EME2000),
"ICRF" => Ok(Self::ICRF),
"LVLH" => Ok(Self::LVLH),
// RTN / RSW / RIC are three names for the same frame;
// canonical is RTN.
"RTN" | "RSW" | "RIC" => Ok(Self::RTN),
"NTW" => Ok(Self::NTW),
_ => bail!("Invalid Frame"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn ric_rsw_are_aliases_for_rtn() {
// Associated-constant aliases resolve to the same enum variant
assert!(matches!(Frame::RIC, Frame::RTN));
assert!(matches!(Frame::RSW, Frame::RTN));
// The three are equal as values (Frame derives PartialEq)
assert_eq!(Frame::RIC, Frame::RTN);
assert_eq!(Frame::RSW, Frame::RTN);
// Display is always the canonical name (RTN)
assert_eq!(Frame::RTN.to_string(), "RTN");
assert_eq!(Frame::RIC.to_string(), "RTN");
assert_eq!(Frame::RSW.to_string(), "RTN");
}
#[test]
fn from_str_accepts_aliases() {
assert!(matches!(Frame::from_str("RTN").unwrap(), Frame::RTN));
assert!(matches!(Frame::from_str("RSW").unwrap(), Frame::RTN));
assert!(matches!(Frame::from_str("RIC").unwrap(), Frame::RTN));
assert!(matches!(Frame::from_str("NTW").unwrap(), Frame::NTW));
}
}