celestial_coords/frames/
ecliptic_cartesian.rs1use crate::transforms::CartesianFrame;
2use celestial_core::constants::{FRAME_BIAS_PHI_RAD, J2000_OBLIQUITY_RAD};
3use celestial_core::Vector3;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct EclipticCartesian {
7 pub x: f64,
8 pub y: f64,
9 pub z: f64,
10}
11
12impl EclipticCartesian {
13 pub fn new(x: f64, y: f64, z: f64) -> Self {
14 Self { x, y, z }
15 }
16
17 pub fn from_vector3(v: &Vector3) -> Self {
18 Self {
19 x: v.x,
20 y: v.y,
21 z: v.z,
22 }
23 }
24}
25
26impl CartesianFrame for EclipticCartesian {
27 fn to_icrs(&self) -> Vector3 {
28 let eps = J2000_OBLIQUITY_RAD;
29 let phi = FRAME_BIAS_PHI_RAD;
30 let (sin_eps, cos_eps) = libm::sincos(eps);
31 let (sin_phi, cos_phi) = libm::sincos(phi);
32
33 let y1 = self.y * cos_eps - self.z * sin_eps;
34 let z1 = self.y * sin_eps + self.z * cos_eps;
35
36 Vector3::new(
37 self.x * cos_phi + y1 * sin_phi,
38 -self.x * sin_phi + y1 * cos_phi,
39 z1,
40 )
41 }
42
43 fn from_icrs(icrs: &Vector3) -> Self {
44 let eps = J2000_OBLIQUITY_RAD;
45 let phi = FRAME_BIAS_PHI_RAD;
46 let (sin_eps, cos_eps) = libm::sincos(eps);
47 let (sin_phi, cos_phi) = libm::sincos(phi);
48
49 let x1 = icrs.x * cos_phi - icrs.y * sin_phi;
50 let y1 = icrs.x * sin_phi + icrs.y * cos_phi;
51
52 Self {
53 x: x1,
54 y: y1 * cos_eps + icrs.z * sin_eps,
55 z: -y1 * sin_eps + icrs.z * cos_eps,
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_roundtrip() {
66 let ecl = EclipticCartesian::new(-9.8753625435, -27.9588613710, 5.8504463318);
67 let icrs = ecl.to_icrs();
68 let back = EclipticCartesian::from_icrs(&icrs);
69
70 let tol = 1e-14;
71 assert!((ecl.x - back.x).abs() < tol, "X roundtrip error");
72 assert!((ecl.y - back.y).abs() < tol, "Y roundtrip error");
73 assert!((ecl.z - back.z).abs() < tol, "Z roundtrip error");
74 }
75
76 #[test]
77 fn test_pluto_vector_signs() {
78 let ecl = EclipticCartesian::new(-9.8753625435, -27.9588613710, 5.8504463318);
79 let icrs = ecl.to_icrs();
80
81 assert!(icrs.x < 0.0, "X should be negative");
82 assert!(icrs.y < 0.0, "Y should be negative");
83 assert!(icrs.z < 0.0, "Z should be negative in ICRS");
84 }
85
86 #[test]
87 fn test_ecliptic_x_axis() {
88 let ecl = EclipticCartesian::new(1.0, 0.0, 0.0);
89 let icrs = ecl.to_icrs();
90
91 assert!(
92 (icrs.x - 1.0).abs() < 1e-10,
93 "X-axis X component should be ~1"
94 );
95 assert!(icrs.y.abs() < 1e-6, "X-axis Y component should be ~0");
96 assert!(icrs.z.abs() < 1e-10, "X-axis Z component should be ~0");
97 }
98}