siderust/coordinates/transform/
to_spherical.rs

1use crate::units::Degrees;
2use crate::coordinates::{
3    CartesianCoord, SphericalCoord,
4    centers::*, frames::*, SphericalBuilder
5};
6
7/// Implements conversion from Cartesian to Spherical coordinates
8/// by borrowing a `&CartesianCoord` reference.
9///
10/// The conversion uses the following formulas:
11/// - `r = sqrt(x² + y² + z²)`
12/// - `polar = atan2(y, x)` (angle in the XY plane)
13/// - `azimuth = asin(z / r)` (angle from the XY plane to the vector)
14///
15/// Returns zero values when the Cartesian vector has zero length.
16///
17/// # Type Parameters
18/// - `Center`: The reference center (e.g., Geocentric).
19/// - `Frame`: The reference frame (e.g., ICRS).
20impl<Center, Frame> From<&CartesianCoord<Center, Frame>> for SphericalCoord<Center, Frame>
21where
22    Center: ReferenceCenter,
23    Frame: ReferenceFrame,
24    SphericalCoord<Center, Frame>: SphericalBuilder<Center, Frame>,
25{
26    fn from(cart: &CartesianCoord<Center, Frame>) -> Self {
27        let r = cart.distance_from_origin();
28        if r == 0.0 {
29            return SphericalCoord::<Center, Frame>::build(
30                Degrees::new(0.0),
31                Degrees::new(0.0),
32                0.0
33            );
34        }
35
36        let polar = Degrees::new(cart.y().atan2(cart.x()).to_degrees());
37        let azimuth = Degrees::new((cart.z() / r).asin().to_degrees());
38
39        SphericalCoord::<Center, Frame>::build(polar, azimuth, r)
40    }
41}
42
43impl<Center, Frame> CartesianCoord<Center, Frame>
44where
45    Center: ReferenceCenter,
46    Frame: ReferenceFrame,
47    SphericalCoord<Center, Frame>: SphericalBuilder<Center, Frame>,
48{
49    /// Converts this Cartesian coordinate into its equivalent spherical representation.
50    pub fn to_spherical(&self) -> SphericalCoord<Center, Frame> {
51        self.into()
52    }
53
54    /// Constructs a Cartesian coordinate from a spherical one.
55    pub fn from_spherical(sph: &SphericalCoord<Center, Frame>) -> Self {
56        sph.into()
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::units::Degrees;
64
65    #[test]
66    fn test_cartesian_to_spherical() {
67        let cart = CartesianCoord::<Geocentric, ICRS>::new(1.0, 1.0, 1.0);
68        let sph: SphericalCoord<Geocentric, ICRS> = cart.to_spherical();
69
70        assert!((sph.radial_distance - 1.7320508075688772).abs() < 1e-6);
71        assert!((sph.azimuth - Degrees::new(45.0)).abs() < Degrees::new(1e-6));
72        assert!((sph.polar - Degrees::new(35.26438968275466)).abs() < Degrees::new(1e-6));
73    }
74
75    #[test]
76    fn test_spherical_to_cartesian() {
77        let sph = SphericalCoord::<Geocentric, ICRS>::new(
78            Degrees::new(45.0),  
79            Degrees::new(35.26438968275466), 
80            1.7320508075688772,
81        );
82        let cart: CartesianCoord<Geocentric, ICRS> = CartesianCoord::from_spherical(&sph);
83
84        assert!((cart.x() - 1.0).abs() < 1e-6);
85        assert!((cart.y() - 1.0).abs() < 1e-6);
86        assert!((cart.z() - 1.0).abs() < 1e-6);
87    }
88
89    #[test]
90    fn test_cartesian_spherical_round_trip() {
91        let cart_original = CartesianCoord::<Geocentric, ICRS>::new(2.0, 3.0, 4.0,);
92        let sph = cart_original.to_spherical();
93        let cart_converted = CartesianCoord::from_spherical(&sph);
94
95        assert!((cart_original.x() - cart_converted.x()).abs() < 1e-6);
96        assert!((cart_original.y() - cart_converted.y()).abs() < 1e-6);
97        assert!((cart_original.z() - cart_converted.z()).abs() < 1e-6);
98    }
99
100    #[test]
101    fn test_spherical_cartesian_round_trip() {
102        let sph_original = SphericalCoord::<Geocentric, ICRS>::new(
103            Degrees::new(30.0),
104            Degrees::new(60.0),
105            5.0,
106        );
107        let cart = CartesianCoord::from_spherical(&sph_original);
108        let sph_converted = cart.to_spherical();
109
110        assert!((sph_original.radial_distance - sph_converted.radial_distance).abs() < 1e-6);
111        assert!((sph_original.azimuth - sph_converted.azimuth).abs() < Degrees::new(1e-6));
112        assert!((sph_original.polar - sph_converted.polar).abs() < Degrees::new(1e-6));
113    }
114}