lox_frames/traits.rs
1// SPDX-FileCopyrightText: 2025 Helge Eichhorn <git@helgeeichhorn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use lox_bodies::{CoordinateOrigin, NaifId, UndefinedOriginPropertyError};
6use lox_core::coords::Ellipsoid;
7use thiserror::Error;
8
9use crate::iers::ReferenceSystem;
10
11pub(crate) mod private {
12 /// Internal token to seal `frame_key`.
13 pub struct Internal;
14}
15
16/// Structural identity of a reference frame, used to detect when two frames —
17/// whether expressed concretely or as a [`Frame`](crate::Frame) — are the
18/// same, without rotating.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum FrameKey {
21 /// International Celestial Reference Frame.
22 Icrf,
23 /// J2000 Mean Equator and Equinox.
24 J2000,
25 /// Celestial Intermediate Reference Frame.
26 Cirf,
27 /// Terrestrial Intermediate Reference Frame.
28 Tirf,
29 /// International Terrestrial Reference Frame.
30 Itrf,
31 /// True Equator Mean Equinox.
32 Teme,
33 /// Mean of Date for the given IERS convention.
34 Mod(ReferenceSystem),
35 /// True of Date for the given IERS convention.
36 Tod(ReferenceSystem),
37 /// Pseudo-Earth Fixed for the given IERS convention.
38 Pef(ReferenceSystem),
39 /// IAU body-fixed frame for the given body.
40 Iau(NaifId),
41}
42
43/// A reference frame with a human-readable name and abbreviation.
44pub trait ReferenceFrame {
45 /// Returns the full name of the frame (e.g. "International Celestial Reference Frame").
46 fn name(&self) -> String;
47 /// Returns the abbreviated name (e.g. "ICRF").
48 fn abbreviation(&self) -> String;
49 #[doc(hidden)]
50 fn frame_key(&self, _: private::Internal) -> Option<FrameKey> {
51 None
52 }
53}
54
55/// Returns the frame's identity key, if it has one.
56pub fn frame_key(frame: &impl ReferenceFrame) -> Option<FrameKey> {
57 frame.frame_key(private::Internal)
58}
59
60/// Marker trait for quasi-inertial reference frames.
61pub trait QuasiInertial: ReferenceFrame {}
62
63/// The frame is not quasi-inertial.
64#[derive(Clone, Debug, Error, Eq, PartialEq)]
65#[error("{0} is not a quasi-inertial frame")]
66pub struct NonQuasiInertialFrameError(pub String);
67
68/// Fallible check for quasi-inertial frames (used by dynamic dispatch).
69pub trait TryQuasiInertial: ReferenceFrame {
70 /// Returns `Ok(())` if the frame is quasi-inertial.
71 fn try_quasi_inertial(&self) -> Result<(), NonQuasiInertialFrameError>;
72}
73
74impl<T: QuasiInertial> TryQuasiInertial for T {
75 fn try_quasi_inertial(&self) -> Result<(), NonQuasiInertialFrameError> {
76 Ok(())
77 }
78}
79
80/// Marker trait for body-fixed reference frames.
81pub trait BodyFixed: ReferenceFrame {
82 /// The coordinate origin (central body) of the body-fixed frame.
83 type Origin: CoordinateOrigin + Copy;
84
85 /// Returns the coordinate origin (central body) of the body-fixed frame.
86 fn origin(&self) -> Self::Origin;
87}
88
89/// The frame is not body-fixed.
90#[derive(Clone, Debug, Error)]
91#[error("{0} is not a body-fixed frame")]
92pub struct NonBodyFixedFrameError(pub String);
93
94/// Fallible check for body-fixed frames (used by dynamic dispatch).
95pub trait TryBodyFixed: ReferenceFrame {
96 /// The coordinate origin (central body) of the body-fixed frame.
97 type Origin: CoordinateOrigin + Copy;
98
99 /// Returns `Ok(())` if the frame is body-fixed.
100 fn try_body_fixed(&self) -> Result<(), NonBodyFixedFrameError>;
101
102 /// Returns the coordinate origin (central body) of the body-fixed frame.
103 fn try_origin(&self) -> Result<Self::Origin, NonBodyFixedFrameError>;
104}
105
106impl<T: BodyFixed> TryBodyFixed for T {
107 type Origin = T::Origin;
108
109 fn try_body_fixed(&self) -> Result<(), NonBodyFixedFrameError> {
110 Ok(())
111 }
112
113 fn try_origin(&self) -> Result<Self::Origin, NonBodyFixedFrameError> {
114 Ok(self.origin())
115 }
116}
117
118/// A body-fixed frame with a conventional reference ellipsoid.
119///
120/// The ellipsoid is the one conventionally paired with the frame rather than an
121/// intrinsic property of its origin: the terrestrial frames
122/// ([`Itrf`](crate::Itrf), [`Tirf`](crate::Tirf) and [`Pef`](crate::Pef)) are
123/// paired with GRS80, while [`Iau`](crate::Iau) frames use their body's own
124/// spheroid. Callers needing a different datum supply the ellipsoid explicitly.
125pub trait ReferenceEllipsoid: BodyFixed {
126 /// Returns the reference ellipsoid conventionally paired with this frame.
127 fn reference_ellipsoid(&self) -> Ellipsoid;
128}
129
130/// Fallible accessor for a frame's reference ellipsoid (used by dynamic dispatch).
131pub trait TryReferenceEllipsoid: TryBodyFixed {
132 /// Returns the reference ellipsoid conventionally paired with this frame,
133 /// or an error if the frame is not body-fixed or its origin is not a spheroid.
134 fn try_reference_ellipsoid(&self) -> Result<Ellipsoid, UndefinedReferenceEllipsoidError>;
135}
136
137impl<T> TryReferenceEllipsoid for T
138where
139 T: ReferenceEllipsoid,
140{
141 fn try_reference_ellipsoid(&self) -> Result<Ellipsoid, UndefinedReferenceEllipsoidError> {
142 Ok(self.reference_ellipsoid())
143 }
144}
145
146/// Error returned when a frame has no reference ellipsoid.
147#[derive(Debug, thiserror::Error)]
148pub enum UndefinedReferenceEllipsoidError {
149 /// The frame is not body-fixed, so no datum is associated with it.
150 #[error(transparent)]
151 NotBodyFixed(#[from] NonBodyFixedFrameError),
152 /// The frame's origin has no spheroid, as for a triaxial body.
153 #[error(transparent)]
154 UndefinedSpheroid(#[from] UndefinedOriginPropertyError),
155}