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, DefaultEop, DefaultEphemeris, TransformContext,
};
use crate::coordinates::transform::providers::{
frame_rotation_with, CenterShiftProvider, FrameRotationProvider,
};
use crate::qtty::{LengthUnit, Unit};
use crate::time::JulianDate;
use affn::Rotation3;
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, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Direction<F2>
where
Ctx: TransformContext,
(): 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 ctx: AstroContext<DefaultEphemeris, DefaultEop> = AstroContext::default();
let eop = ctx.eop_at_tt(*jd_tt);
let jd_ut1 = crate::astro::earth_rotation::jd_ut1_from_tt_eop(*jd_tt, &eop);
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>,
{
let ctx: AstroContext = AstroContext::default();
self.to_frame_with(jd, &ctx)
}
fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Direction<F2>
where
Ctx: TransformContext,
(): FrameRotationProvider<F, F2>,
{
let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*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, Ctx>(
&self,
jd: &JulianDate,
ctx: &Ctx,
) -> spherical::Direction<F2>
where
Ctx: TransformContext,
(): 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>,
{
let ctx: AstroContext = AstroContext::default();
self.to_frame_with(jd, &ctx)
}
fn to_frame_with<F2: ReferenceFrame, Ctx>(
&self,
jd: &JulianDate,
ctx: &Ctx,
) -> spherical::Direction<F2>
where
Ctx: TransformContext,
(): 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, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Vector<F2, U>
where
Ctx: TransformContext,
(): 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>,
{
let ctx: AstroContext = AstroContext::default();
self.to_frame_with(jd, &ctx)
}
fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Vector<F2, U>
where
Ctx: TransformContext,
(): FrameRotationProvider<F, F2>,
{
let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*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, Ctx>(
&self,
jd: &JulianDate,
ctx: &Ctx,
) -> Position<C, F2, U>
where
Ctx: TransformContext,
(): 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, Ctx>(
&self,
jd: &JulianDate,
ctx: &Ctx,
) -> Position<C2, F2, U>
where
Ctx: TransformContext,
Ctx::Eph: crate::calculus::ephemeris::Ephemeris,
(): 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>,
{
let ctx: AstroContext = AstroContext::default();
self.to_frame_with(jd, &ctx)
}
fn to_frame_with<F2: ReferenceFrame, Ctx>(
&self,
jd: &JulianDate,
ctx: &Ctx,
) -> Position<C, F2, U>
where
Ctx: TransformContext,
(): FrameRotationProvider<F, F2>,
{
let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*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>,
{
let ctx: AstroContext = AstroContext::default();
self.to_with(jd, &ctx)
}
fn to_with<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame, Ctx>(
&self,
jd: &JulianDate,
ctx: &Ctx,
) -> Position<C2, F2, U>
where
Ctx: TransformContext,
Ctx::Eph: crate::calculus::ephemeris::Ephemeris,
(): CenterShiftProvider<C, C2, F>,
(): FrameRotationProvider<F, F2>,
{
<Self as TransformCenter<C2, F, U>>::to_center_with(self, (), *jd, ctx)
.to_frame_with::<F2, Ctx>(jd, ctx)
}
}
pub struct WithEngine<'a, T, Ctx> {
inner: &'a T,
ctx: &'a Ctx,
}
pub trait UsingEngine: Sized {
fn using<'a, Ctx>(&'a self, engine: &'a Ctx) -> WithEngine<'a, Self, Ctx>
where
Ctx: TransformContext,
{
WithEngine {
inner: self,
ctx: engine,
}
}
}
impl<T> UsingEngine for T {}
impl<'a, F: ReferenceFrame, Ctx> WithEngine<'a, Direction<F>, Ctx>
where
Ctx: TransformContext,
{
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, Ctx> WithEngine<'a, Position<C, F, U>, Ctx>
where
C: ReferenceCenter<Params = ()>,
Ctx: TransformContext,
Ctx::Eph: crate::calculus::ephemeris::Ephemeris,
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 crate::qtty::{AstronomicalUnit, AstronomicalUnits};
const EPSILON: f64 = 1e-10;
const AU_EPS: AstronomicalUnits = AstronomicalUnits::new(EPSILON);
const AU_TIGHT: AstronomicalUnits = AstronomicalUnits::new(1e-15);
#[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 = 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().value().powi(2) + pos.y().value().powi(2) + pos.z().value().powi(2)).sqrt();
let n1 = (pos_ecl.x().value().powi(2)
+ pos_ecl.y().value().powi(2)
+ pos_ecl.z().value().powi(2))
.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.value() > 0.9 && dist.value() < 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() > AU_EPS
|| (result.y() - pos.y()).abs() > AU_EPS
|| (result.z() - pos.z()).abs() > AU_EPS
);
}
#[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() < AU_EPS);
assert!((same_frame.y() - pos.y()).abs() < AU_EPS);
assert!((same_frame.z() - pos.z()).abs() < AU_EPS);
let same_center: Position<Barycentric, ICRS, AstronomicalUnit> = pos.to_center(jd);
assert!((same_center.x() - pos.x()).abs() < AU_EPS);
assert!((same_center.y() - pos.y()).abs() < AU_EPS);
assert!((same_center.z() - pos.z()).abs() < AU_EPS);
}
#[test]
fn test_using_engine() {
let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
let engine: AstroContext = 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);
}
#[test]
fn test_phantom_model_selection_affects_true_of_date_rotation() {
use crate::astro::nutation::{Iau2006, Iau2006A};
let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
let jd = JulianDate::new(2_458_850.0);
let ctx: AstroContext<DefaultEphemeris, DefaultEop> = AstroContext::default();
let with_nutation = dir
.to_frame_with::<crate::coordinates::frames::EquatorialTrueOfDate, _>(
&jd,
&ctx.with_model::<Iau2006A>(),
);
let precession_only = dir
.to_frame_with::<crate::coordinates::frames::EquatorialTrueOfDate, _>(
&jd,
&ctx.with_model::<Iau2006>(),
);
let delta = ((with_nutation.x() - precession_only.x()).powi(2)
+ (with_nutation.y() - precession_only.y()).powi(2)
+ (with_nutation.z() - precession_only.z()).powi(2))
.sqrt();
assert!(
delta > 1e-9,
"model presets should produce distinct rotations"
);
}
#[test]
fn test_spherical_direction_frame_transform() {
use super::SphericalDirectionAstroExt;
use crate::coordinates::spherical;
use crate::qtty::DEG;
let sph_dir = spherical::Direction::<ICRS>::new(45.0 * DEG, 30.0 * DEG);
let jd = JulianDate::J2000;
let sph_ecl: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
assert!(sph_ecl.azimuth.is_finite());
assert!(sph_ecl.polar.is_finite());
}
#[test]
fn test_spherical_direction_roundtrip() {
use super::SphericalDirectionAstroExt;
use crate::coordinates::spherical;
use crate::qtty::DEG;
let sph_dir = spherical::Direction::<ICRS>::new(123.0 * DEG, -45.0 * DEG);
let jd = JulianDate::J2000;
let sph_ecl: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
let sph_back: spherical::Direction<ICRS> = sph_ecl.to_frame(&jd);
assert!((sph_back.azimuth - sph_dir.azimuth).abs().value() < 1e-8);
assert!((sph_back.polar - sph_dir.polar).abs().value() < 1e-8);
}
#[test]
fn test_spherical_direction_with_ctx() {
use super::SphericalDirectionAstroExt;
use crate::coordinates::spherical;
use crate::qtty::DEG;
let sph_dir = spherical::Direction::<ICRS>::new(90.0 * DEG, 0.0 * DEG);
let ctx: AstroContext = AstroContext::default();
let jd = JulianDate::J2000;
let with_ctx: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame_with(&jd, &ctx);
let without_ctx: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
assert!((with_ctx.azimuth - without_ctx.azimuth).abs().value() < 1e-15);
assert!((with_ctx.polar - without_ctx.polar).abs().value() < 1e-15);
}
#[test]
fn test_vector_frame_transform() {
use super::VectorAstroExt;
let vec = Vector::<ICRS, AstronomicalUnit>::new(
AstronomicalUnits::new(1.0),
AstronomicalUnits::new(2.0),
AstronomicalUnits::new(3.0),
);
let jd = JulianDate::J2000;
let vec_ecl: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
assert!(vec_ecl.x().is_finite() && vec_ecl.y().is_finite() && vec_ecl.z().is_finite());
let n0 = (vec.x() * vec.x() + vec.y() * vec.y() + vec.z() * vec.z()).scalar_sqrt();
let n1 =
(vec_ecl.x() * vec_ecl.x() + vec_ecl.y() * vec_ecl.y() + vec_ecl.z() * vec_ecl.z())
.scalar_sqrt();
assert!((n0 - n1).abs() < 1e-12);
}
#[test]
fn test_vector_frame_roundtrip() {
use super::VectorAstroExt;
let vec = Vector::<ICRS, AstronomicalUnit>::new(
AstronomicalUnits::new(0.5),
AstronomicalUnits::new(-0.3),
AstronomicalUnits::new(0.8),
);
let jd = JulianDate::J2000;
let vec_ecl: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
let vec_back: Vector<ICRS, AstronomicalUnit> = vec_ecl.to_frame(&jd);
assert!((vec_back.x() - vec.x()).abs() < AU_EPS);
assert!((vec_back.y() - vec.y()).abs() < AU_EPS);
assert!((vec_back.z() - vec.z()).abs() < AU_EPS);
}
#[test]
fn test_vector_frame_with_ctx() {
use super::VectorAstroExt;
let vec = Vector::<ICRS, AstronomicalUnit>::new(
AstronomicalUnits::new(1.0),
AstronomicalUnits::new(0.0),
AstronomicalUnits::new(0.0),
);
let ctx: AstroContext = AstroContext::default();
let jd = JulianDate::J2000;
let with_ctx: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame_with(&jd, &ctx);
let without_ctx: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
assert!((with_ctx.x() - without_ctx.x()).abs() < AU_TIGHT);
assert!((with_ctx.y() - without_ctx.y()).abs() < AU_TIGHT);
assert!((with_ctx.z() - without_ctx.z()).abs() < AU_TIGHT);
}
#[test]
fn test_using_engine_position_frame() {
let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 0.5, 0.2);
let engine: AstroContext = AstroContext::default();
let jd = JulianDate::J2000;
let via_engine: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
pos.using(&engine).to_frame(&jd);
let direct: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_frame(&jd);
assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
}
#[test]
fn test_using_engine_position_center() {
let pos = Position::<Geocentric, EclipticMeanJ2000, AstronomicalUnit>::new(0.0, 0.0, 0.0);
let engine: AstroContext = AstroContext::default();
let jd = JulianDate::J2000;
let via_engine: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
pos.using(&engine).to_center(&jd);
let direct: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_center(jd);
assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
}
#[test]
fn test_using_engine_position_combined() {
let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
let engine: AstroContext = AstroContext::default();
let jd = JulianDate::J2000;
let via_engine: Position<Geocentric, ICRS, AstronomicalUnit> = pos.using(&engine).to(&jd);
let direct: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
}
#[test]
fn test_position_frame_with_ctx() {
let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 2.0, 3.0);
let ctx: AstroContext = AstroContext::default();
let jd = JulianDate::J2000;
let with_ctx: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
pos.to_frame_with(&jd, &ctx);
let default_ctx: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
pos.to_frame(&jd);
assert!((with_ctx.x() - default_ctx.x()).abs() < AU_TIGHT);
assert!((with_ctx.y() - default_ctx.y()).abs() < AU_TIGHT);
assert!((with_ctx.z() - default_ctx.z()).abs() < AU_TIGHT);
}
#[test]
fn test_position_combined_with_ctx() {
let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
let ctx: AstroContext = AstroContext::default();
let jd = JulianDate::J2000;
let with_ctx: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to_with(&jd, &ctx);
let default_ctx: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
assert!((with_ctx.x() - default_ctx.x()).abs() < AU_TIGHT);
assert!((with_ctx.y() - default_ctx.y()).abs() < AU_TIGHT);
assert!((with_ctx.z() - default_ctx.z()).abs() < AU_TIGHT);
}
}