siderust 0.7.0

High-precision astronomy and satellite mechanics in Rust.
Documentation
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Vallés Puig, Ramon

use siderust::bodies::solar_system::Mars;
use siderust::coordinates::{
    cartesian,
    centers::*,
    frames::*,
    spherical,
    transform::{Transform, TransformFrame},
};
use siderust::qtty::*;
use siderust::targets::CoordinateWithPM;
use siderust::time::JulianDate;

const EPS: f64 = 1e-9;

#[test]
fn target_cartesian_position_transform() {
    let jd = JulianDate::J2000;
    let orig: CoordinateWithPM<
        cartesian::Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>,
    > = CoordinateWithPM::new_static(Mars::vsop87a(jd), jd);

    let converted: CoordinateWithPM<
        cartesian::Position<Geocentric, EquatorialMeanJ2000, AstronomicalUnit>,
    > = CoordinateWithPM::from(&orig);

    let step: cartesian::Position<Heliocentric, EquatorialMeanJ2000, AstronomicalUnit> =
        orig.position.transform(jd);
    let expected: cartesian::Position<Geocentric, EquatorialMeanJ2000, AstronomicalUnit> =
        step.transform(jd);

    assert!(converted.position.distance_to(&expected).value() < EPS);
    assert_eq!(converted.time, orig.time);
    assert_eq!(
        converted.proper_motion.is_none(),
        orig.proper_motion.is_none()
    );
}

#[test]
fn target_spherical_position_transform() {
    let jd = JulianDate::J2000;
    let cart_orig: CoordinateWithPM<
        cartesian::Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>,
    > = CoordinateWithPM::new_static(Mars::vsop87a(jd), jd);
    let sph_pos: spherical::Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit> =
        spherical::Position::from_cartesian(&cart_orig.position);
    let orig: CoordinateWithPM<
        spherical::Position<Heliocentric, EclipticMeanJ2000, AstronomicalUnit>,
    > = CoordinateWithPM::new_static(sph_pos, jd);

    let converted: CoordinateWithPM<
        spherical::Position<Geocentric, EquatorialMeanJ2000, AstronomicalUnit>,
    > = CoordinateWithPM::from(&orig);

    let step_cart: cartesian::Position<Heliocentric, EquatorialMeanJ2000, AstronomicalUnit> =
        orig.position.to_cartesian().transform(jd);
    let expected_cart: cartesian::Position<Geocentric, EquatorialMeanJ2000, AstronomicalUnit> =
        step_cart.transform(jd);
    let converted_cart = converted.position.to_cartesian();

    assert!(converted_cart.distance_to(&expected_cart).value() < EPS);
    assert_eq!(converted.time, orig.time);
    assert_eq!(
        converted.proper_motion.is_none(),
        orig.proper_motion.is_none()
    );
}

#[test]
fn cartesian_direction_frame_transform() {
    // Directions are now frame-only (no center parameter).
    // They can only undergo frame transformations, not center transformations.
    let dir = cartesian::Direction::<EclipticMeanJ2000>::normalize(1.0, 0.5, 0.2);

    // Frame transform from EclipticMeanJ2000 to EquatorialMeanJ2000 (rotation only)
    let dir_equatorial: cartesian::Direction<EquatorialMeanJ2000> = TransformFrame::to_frame(&dir);

    // Verify it's still a unit vector
    let norm =
        (dir_equatorial.x().powi(2) + dir_equatorial.y().powi(2) + dir_equatorial.z().powi(2))
            .sqrt();
    assert!((norm - 1.0).abs() < 1e-12);

    // X component is unchanged in ecliptic/equatorial rotation
    assert!((dir_equatorial.x() - dir.x()).abs() < 1e-12);
}

#[test]
fn spherical_direction_frame_transform() {
    // Directions are now frame-only (no center parameter).
    let sph_dir =
        spherical::Direction::<EclipticMeanJ2000>::new(Degrees::new(10.0), Degrees::new(20.0));

    // Convert to cartesian, transform frame, then back
    let cart_dir = sph_dir.to_cartesian();
    let cart_equatorial: cartesian::Direction<EquatorialMeanJ2000> =
        TransformFrame::to_frame(&cart_dir);

    // Verify the Cartesian direction is still unit vector
    let norm =
        (cart_equatorial.x().powi(2) + cart_equatorial.y().powi(2) + cart_equatorial.z().powi(2))
            .sqrt();
    assert!((norm - 1.0).abs() < 1e-12);
}