use crate::coordinates::cartesian::{Direction, Position, Vector};
use crate::coordinates::centers::{Geodetic, ReferenceCenter};
use crate::coordinates::frames::{ReferenceFrame, ECEF};
use crate::coordinates::spherical;
use crate::coordinates::transform::centers::TransformCenter;
use crate::coordinates::transform::context::AstroContext;
use crate::coordinates::transform::providers::{CenterShiftProvider, FrameRotationProvider};
use crate::time::JulianDate;
use affn::Rotation3;
use qtty::{LengthUnit, Unit};
pub trait DirectionAstroExt<F: ReferenceFrame> {
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
where
(): FrameRotationProvider<F, F2>;
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Direction<F2>
where
(): FrameRotationProvider<F, F2>;
fn to_ecliptic_of_date(
&self,
jd_tt: &JulianDate,
) -> Direction<crate::coordinates::frames::EclipticTrueOfDate>
where
Self: crate::coordinates::transform::ecliptic_of_date::ToEclipticTrueOfDate,
{
crate::coordinates::transform::ecliptic_of_date::ToEclipticTrueOfDate::to_ecliptic_of_date(
self, jd_tt,
)
}
fn to_horizontal(
&self,
jd_tt: &JulianDate,
site: &Geodetic<ECEF>,
) -> Direction<crate::coordinates::frames::Horizontal>
where
Self: crate::coordinates::transform::horizontal::ToHorizontal,
{
let jd_ut1 = crate::astro::earth_rotation::jd_ut1_from_tt(*jd_tt);
crate::coordinates::transform::horizontal::ToHorizontal::to_horizontal(
self, &jd_ut1, jd_tt, site,
)
}
fn to_horizontal_precise(
&self,
jd_tt: &JulianDate,
jd_ut1: &JulianDate,
site: &Geodetic<ECEF>,
) -> Direction<crate::coordinates::frames::Horizontal>
where
Self: crate::coordinates::transform::horizontal::ToHorizontal,
{
crate::coordinates::transform::horizontal::ToHorizontal::to_horizontal(
self, jd_ut1, jd_tt, site,
)
}
}
impl<F: ReferenceFrame> DirectionAstroExt<F> for Direction<F> {
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
where
(): FrameRotationProvider<F, F2>,
{
self.to_frame_with(jd, &AstroContext::default())
}
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Direction<F2>
where
(): FrameRotationProvider<F, F2>,
{
let rot: Rotation3 = <() as FrameRotationProvider<F, F2>>::rotation(*jd, ctx);
let [x, y, z] = rot.apply_array([self.x(), self.y(), self.z()]);
Direction::new_unchecked(x, y, z)
}
}
pub trait SphericalDirectionAstroExt<F: ReferenceFrame> {
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> spherical::Direction<F2>
where
(): FrameRotationProvider<F, F2>;
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> spherical::Direction<F2>
where
(): FrameRotationProvider<F, F2>;
}
impl<F: ReferenceFrame> SphericalDirectionAstroExt<F> for spherical::Direction<F> {
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> spherical::Direction<F2>
where
(): FrameRotationProvider<F, F2>,
{
self.to_frame_with(jd, &AstroContext::default())
}
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> spherical::Direction<F2>
where
(): FrameRotationProvider<F, F2>,
{
let cart: Direction<F> = self.to_cartesian();
let cart_f2: Direction<F2> = cart.to_frame_with(jd, ctx);
spherical::Direction::from_cartesian(&cart_f2)
}
}
pub trait VectorAstroExt<F: ReferenceFrame, U: Unit> {
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Vector<F2, U>
where
(): FrameRotationProvider<F, F2>;
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Vector<F2, U>
where
(): FrameRotationProvider<F, F2>;
}
impl<F: ReferenceFrame, U: Unit> VectorAstroExt<F, U> for Vector<F, U> {
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Vector<F2, U>
where
(): FrameRotationProvider<F, F2>,
{
self.to_frame_with(jd, &AstroContext::default())
}
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Vector<F2, U>
where
(): FrameRotationProvider<F, F2>,
{
let rot: Rotation3 = <() as FrameRotationProvider<F, F2>>::rotation(*jd, ctx);
let [x, y, z] = rot * [self.x(), self.y(), self.z()];
Vector::new(x, y, z)
}
}
pub trait PositionAstroExt<C: ReferenceCenter, F: ReferenceFrame, U: LengthUnit> {
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
where
(): FrameRotationProvider<F, F2>;
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Position<C, F2, U>
where
(): FrameRotationProvider<F, F2>;
fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
&self,
jd: &JulianDate,
) -> Position<C2, F2, U>
where
(): CenterShiftProvider<C, C2, F>,
(): FrameRotationProvider<F, F2>;
fn to_with<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Position<C2, F2, U>
where
(): CenterShiftProvider<C, C2, F>,
(): FrameRotationProvider<F, F2>;
}
impl<C, F, U> PositionAstroExt<C, F, U> for Position<C, F, U>
where
C: ReferenceCenter<Params = ()>,
F: ReferenceFrame,
U: LengthUnit,
{
fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
where
(): FrameRotationProvider<F, F2>,
{
self.to_frame_with(jd, &AstroContext::default())
}
fn to_frame_with<F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Position<C, F2, U>
where
(): FrameRotationProvider<F, F2>,
{
let rot: Rotation3 = <() as FrameRotationProvider<F, F2>>::rotation(*jd, ctx);
let [x, y, z] = rot * [self.x(), self.y(), self.z()];
Position::new(x, y, z)
}
fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
&self,
jd: &JulianDate,
) -> Position<C2, F2, U>
where
(): CenterShiftProvider<C, C2, F>,
(): FrameRotationProvider<F, F2>,
{
self.to_with(jd, &AstroContext::default())
}
fn to_with<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
&self,
jd: &JulianDate,
ctx: &AstroContext,
) -> Position<C2, F2, U>
where
(): CenterShiftProvider<C, C2, F>,
(): FrameRotationProvider<F, F2>,
{
<Self as TransformCenter<C2, F, U>>::to_center_with(self, (), *jd, ctx)
.to_frame_with::<F2>(jd, ctx)
}
}
pub struct WithEngine<'a, T> {
inner: &'a T,
ctx: &'a AstroContext,
}
pub trait UsingEngine: Sized {
fn using<'a>(&'a self, engine: &'a AstroContext) -> WithEngine<'a, Self> {
WithEngine {
inner: self,
ctx: engine,
}
}
}
impl<T> UsingEngine for T {}
impl<'a, F: ReferenceFrame> WithEngine<'a, Direction<F>> {
pub fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
where
(): FrameRotationProvider<F, F2>,
{
self.inner.to_frame_with(jd, self.ctx)
}
}
impl<'a, C, F, U> WithEngine<'a, Position<C, F, U>>
where
C: ReferenceCenter<Params = ()>,
F: ReferenceFrame,
U: LengthUnit,
{
pub fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
where
(): FrameRotationProvider<F, F2>,
{
self.inner.to_frame_with(jd, self.ctx)
}
pub fn to_center<C2: ReferenceCenter<Params = ()>>(&self, jd: &JulianDate) -> Position<C2, F, U>
where
(): CenterShiftProvider<C, C2, F>,
{
<Position<C, F, U> as TransformCenter<C2, F, U>>::to_center_with(
self.inner,
(),
*jd,
self.ctx,
)
}
pub fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
&self,
jd: &JulianDate,
) -> Position<C2, F2, U>
where
(): CenterShiftProvider<C, C2, F>,
(): FrameRotationProvider<F, F2>,
{
self.inner.to_with(jd, self.ctx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coordinates::centers::{Barycentric, Geocentric};
use crate::coordinates::frames::{EclipticMeanJ2000, ICRS};
use qtty::AstronomicalUnit;
const EPSILON: f64 = 1e-10;
#[test]
fn test_direction_frame_transform() {
let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
let jd = JulianDate::J2000;
let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
assert!(dir_ecl.x().is_finite() && dir_ecl.y().is_finite() && dir_ecl.z().is_finite());
let n0 = (dir.x() * dir.x() + dir.y() * dir.y() + dir.z() * dir.z()).sqrt();
let n1 =
(dir_ecl.x() * dir_ecl.x() + dir_ecl.y() * dir_ecl.y() + dir_ecl.z() * dir_ecl.z())
.sqrt();
assert!((n0 - n1).abs() < 1e-12);
}
#[test]
fn test_direction_frame_transform_with_ctx() {
let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
let ctx = AstroContext::default();
let jd = JulianDate::J2000;
let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame_with(&jd, &ctx);
let dir_ecl_default: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
assert!((dir_ecl.x() - dir_ecl_default.x()).abs() < 1e-15);
assert!((dir_ecl.y() - dir_ecl_default.y()).abs() < 1e-15);
assert!((dir_ecl.z() - dir_ecl_default.z()).abs() < 1e-15);
}
#[test]
fn test_direction_frame_roundtrip() {
let dir = Direction::<ICRS>::new(1.0, 2.0, 3.0);
let jd = JulianDate::J2000;
let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
let dir_back: Direction<ICRS> = dir_ecl.to_frame(&jd);
assert!((dir_back.x() - dir.x()).abs() < EPSILON);
assert!((dir_back.y() - dir.y()).abs() < EPSILON);
assert!((dir_back.z() - dir.z()).abs() < EPSILON);
}
#[test]
fn test_position_frame_transform() {
let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 0.0, 0.0);
let jd = JulianDate::J2000;
let pos_ecl: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_frame(&jd);
assert!(pos_ecl.x().is_finite() && pos_ecl.y().is_finite() && pos_ecl.z().is_finite());
let n0 = (pos.x() * pos.x() + pos.y() * pos.y() + pos.z() * pos.z()).sqrt();
let n1 =
(pos_ecl.x() * pos_ecl.x() + pos_ecl.y() * pos_ecl.y() + pos_ecl.z() * pos_ecl.z())
.sqrt();
assert!((n0 - n1).abs() < 1e-12);
}
#[test]
fn test_position_center_transform() {
let jd = JulianDate::J2000;
let geo_origin =
Position::<Geocentric, EclipticMeanJ2000, AstronomicalUnit>::new(0.0, 0.0, 0.0);
let bary: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
geo_origin.to_center(jd);
let dist = bary.distance();
assert!(
dist > 0.9 && dist < 1.1,
"Earth should be ~1 AU from barycenter, got {}",
dist
);
}
#[test]
fn test_position_combined_transform() {
let jd = JulianDate::J2000;
let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
let result: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
assert!(
(result.x() - pos.x()).abs() > EPSILON
|| (result.y() - pos.y()).abs() > EPSILON
|| (result.z() - pos.z()).abs() > EPSILON
);
}
#[test]
fn test_position_identity_transforms() {
let jd = JulianDate::J2000;
let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.5, 2.5, 3.5);
let same_frame: Position<Barycentric, ICRS, AstronomicalUnit> = pos.to_frame(&jd);
assert!((same_frame.x() - pos.x()).abs() < EPSILON);
assert!((same_frame.y() - pos.y()).abs() < EPSILON);
assert!((same_frame.z() - pos.z()).abs() < EPSILON);
let same_center: Position<Barycentric, ICRS, AstronomicalUnit> = pos.to_center(jd);
assert!((same_center.x() - pos.x()).abs() < EPSILON);
assert!((same_center.y() - pos.y()).abs() < EPSILON);
assert!((same_center.z() - pos.z()).abs() < EPSILON);
}
#[test]
fn test_using_engine() {
let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
let engine = AstroContext::default();
let jd = JulianDate::J2000;
let dir_ecl: Direction<EclipticMeanJ2000> = dir.using(&engine).to_frame(&jd);
let dir_ecl_direct: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
assert!((dir_ecl.x() - dir_ecl_direct.x()).abs() < 1e-15);
assert!((dir_ecl.y() - dir_ecl_direct.y()).abs() < 1e-15);
assert!((dir_ecl.z() - dir_ecl_direct.z()).abs() < 1e-15);
}
}