1use crate::coordinates::cartesian::{Direction, Position, Vector};
75use crate::coordinates::centers::{Geodetic, ReferenceCenter};
76use crate::coordinates::frames::{ReferenceFrame, ECEF};
77use crate::coordinates::spherical;
78use crate::coordinates::transform::centers::TransformCenter;
79use crate::coordinates::transform::context::{
80 AstroContext, DefaultEop, DefaultEphemeris, TransformContext,
81};
82use crate::coordinates::transform::providers::{
83 frame_rotation_with, CenterShiftProvider, FrameRotationProvider,
84};
85use crate::qtty::{LengthUnit, Unit};
86use crate::time::JulianDate;
87use affn::Rotation3;
88
89pub trait DirectionAstroExt<F: ReferenceFrame> {
98 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
108 where
109 (): FrameRotationProvider<F, F2>;
110
111 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Direction<F2>
113 where
114 Ctx: TransformContext,
115 (): FrameRotationProvider<F, F2>;
116
117 fn to_ecliptic_of_date(
121 &self,
122 jd_tt: &JulianDate,
123 ) -> Direction<crate::coordinates::frames::EclipticTrueOfDate>
124 where
125 Self: crate::coordinates::transform::ecliptic_of_date::ToEclipticTrueOfDate,
126 {
127 crate::coordinates::transform::ecliptic_of_date::ToEclipticTrueOfDate::to_ecliptic_of_date(
128 self, jd_tt,
129 )
130 }
131
132 fn to_horizontal(
142 &self,
143 jd_tt: &JulianDate,
144 site: &Geodetic<ECEF>,
145 ) -> Direction<crate::coordinates::frames::Horizontal>
146 where
147 Self: crate::coordinates::transform::horizontal::ToHorizontal,
148 {
149 let ctx: AstroContext<DefaultEphemeris, DefaultEop> = AstroContext::default();
150 let eop = ctx.eop_at_tt(*jd_tt);
151 let jd_ut1 = crate::astro::earth_rotation::jd_ut1_from_tt_eop(*jd_tt, &eop);
152 crate::coordinates::transform::horizontal::ToHorizontal::to_horizontal(
153 self, &jd_ut1, jd_tt, site,
154 )
155 }
156
157 fn to_horizontal_precise(
161 &self,
162 jd_tt: &JulianDate,
163 jd_ut1: &JulianDate,
164 site: &Geodetic<ECEF>,
165 ) -> Direction<crate::coordinates::frames::Horizontal>
166 where
167 Self: crate::coordinates::transform::horizontal::ToHorizontal,
168 {
169 crate::coordinates::transform::horizontal::ToHorizontal::to_horizontal(
170 self, jd_ut1, jd_tt, site,
171 )
172 }
173}
174
175impl<F: ReferenceFrame> DirectionAstroExt<F> for Direction<F> {
176 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
177 where
178 (): FrameRotationProvider<F, F2>,
179 {
180 let ctx: AstroContext = AstroContext::default();
181 self.to_frame_with(jd, &ctx)
182 }
183
184 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Direction<F2>
185 where
186 Ctx: TransformContext,
187 (): FrameRotationProvider<F, F2>,
188 {
189 let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*jd, ctx);
190 let [x, y, z] = rot.apply_array([self.x(), self.y(), self.z()]);
191 Direction::new_unchecked(x, y, z)
193 }
194}
195
196pub trait SphericalDirectionAstroExt<F: ReferenceFrame> {
207 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> spherical::Direction<F2>
209 where
210 (): FrameRotationProvider<F, F2>;
211
212 fn to_frame_with<F2: ReferenceFrame, Ctx>(
214 &self,
215 jd: &JulianDate,
216 ctx: &Ctx,
217 ) -> spherical::Direction<F2>
218 where
219 Ctx: TransformContext,
220 (): FrameRotationProvider<F, F2>;
221}
222
223impl<F: ReferenceFrame> SphericalDirectionAstroExt<F> for spherical::Direction<F> {
224 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> spherical::Direction<F2>
225 where
226 (): FrameRotationProvider<F, F2>,
227 {
228 let ctx: AstroContext = AstroContext::default();
229 self.to_frame_with(jd, &ctx)
230 }
231
232 fn to_frame_with<F2: ReferenceFrame, Ctx>(
233 &self,
234 jd: &JulianDate,
235 ctx: &Ctx,
236 ) -> spherical::Direction<F2>
237 where
238 Ctx: TransformContext,
239 (): FrameRotationProvider<F, F2>,
240 {
241 let cart: Direction<F> = self.to_cartesian();
242 let cart_f2: Direction<F2> = cart.to_frame_with(jd, ctx);
243 spherical::Direction::from_cartesian(&cart_f2)
244 }
245}
246
247pub trait VectorAstroExt<F: ReferenceFrame, U: Unit> {
256 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Vector<F2, U>
258 where
259 (): FrameRotationProvider<F, F2>;
260
261 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Vector<F2, U>
263 where
264 Ctx: TransformContext,
265 (): FrameRotationProvider<F, F2>;
266}
267
268impl<F: ReferenceFrame, U: Unit> VectorAstroExt<F, U> for Vector<F, U> {
269 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Vector<F2, U>
270 where
271 (): FrameRotationProvider<F, F2>,
272 {
273 let ctx: AstroContext = AstroContext::default();
274 self.to_frame_with(jd, &ctx)
275 }
276
277 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Vector<F2, U>
278 where
279 Ctx: TransformContext,
280 (): FrameRotationProvider<F, F2>,
281 {
282 let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*jd, ctx);
283 let [x, y, z] = rot * [self.x(), self.y(), self.z()];
284 Vector::new(x, y, z)
285 }
286}
287
288pub trait PositionAstroExt<C: ReferenceCenter, F: ReferenceFrame, U: LengthUnit> {
304 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
306 where
307 (): FrameRotationProvider<F, F2>;
308
309 fn to_frame_with<F2: ReferenceFrame, Ctx>(
311 &self,
312 jd: &JulianDate,
313 ctx: &Ctx,
314 ) -> Position<C, F2, U>
315 where
316 Ctx: TransformContext,
317 (): FrameRotationProvider<F, F2>;
318
319 fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
326 &self,
327 jd: &JulianDate,
328 ) -> Position<C2, F2, U>
329 where
330 (): CenterShiftProvider<C, C2, F>,
331 (): FrameRotationProvider<F, F2>;
332
333 fn to_with<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame, Ctx>(
335 &self,
336 jd: &JulianDate,
337 ctx: &Ctx,
338 ) -> Position<C2, F2, U>
339 where
340 Ctx: TransformContext,
341 Ctx::Eph: crate::ephemeris::Ephemeris,
342 (): CenterShiftProvider<C, C2, F>,
343 (): FrameRotationProvider<F, F2>;
344}
345
346impl<C, F, U> PositionAstroExt<C, F, U> for Position<C, F, U>
347where
348 C: ReferenceCenter<Params = ()>,
349 F: ReferenceFrame,
350 U: LengthUnit,
351{
352 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
353 where
354 (): FrameRotationProvider<F, F2>,
355 {
356 let ctx: AstroContext = AstroContext::default();
357 self.to_frame_with(jd, &ctx)
358 }
359
360 fn to_frame_with<F2: ReferenceFrame, Ctx>(
361 &self,
362 jd: &JulianDate,
363 ctx: &Ctx,
364 ) -> Position<C, F2, U>
365 where
366 Ctx: TransformContext,
367 (): FrameRotationProvider<F, F2>,
368 {
369 let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*jd, ctx);
370 let [x, y, z] = rot * [self.x(), self.y(), self.z()];
371 Position::new(x, y, z)
372 }
373
374 fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
375 &self,
376 jd: &JulianDate,
377 ) -> Position<C2, F2, U>
378 where
379 (): CenterShiftProvider<C, C2, F>,
380 (): FrameRotationProvider<F, F2>,
381 {
382 let ctx: AstroContext = AstroContext::default();
383 self.to_with(jd, &ctx)
384 }
385
386 fn to_with<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame, Ctx>(
387 &self,
388 jd: &JulianDate,
389 ctx: &Ctx,
390 ) -> Position<C2, F2, U>
391 where
392 Ctx: TransformContext,
393 Ctx::Eph: crate::ephemeris::Ephemeris,
394 (): CenterShiftProvider<C, C2, F>,
395 (): FrameRotationProvider<F, F2>,
396 {
397 <Self as TransformCenter<C2, F, U>>::to_center_with(self, (), *jd, ctx)
399 .to_frame_with::<F2, Ctx>(jd, ctx)
400 }
401}
402
403pub struct WithEngine<'a, T, Ctx> {
418 inner: &'a T,
419 ctx: &'a Ctx,
420}
421
422pub trait UsingEngine: Sized {
424 fn using<'a, Ctx>(&'a self, engine: &'a Ctx) -> WithEngine<'a, Self, Ctx>
427 where
428 Ctx: TransformContext,
429 {
430 WithEngine {
431 inner: self,
432 ctx: engine,
433 }
434 }
435}
436
437impl<T> UsingEngine for T {}
439
440impl<'a, F: ReferenceFrame, Ctx> WithEngine<'a, Direction<F>, Ctx>
443where
444 Ctx: TransformContext,
445{
446 pub fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
448 where
449 (): FrameRotationProvider<F, F2>,
450 {
451 self.inner.to_frame_with(jd, self.ctx)
452 }
453}
454
455impl<'a, C, F, U, Ctx> WithEngine<'a, Position<C, F, U>, Ctx>
458where
459 C: ReferenceCenter<Params = ()>,
460 Ctx: TransformContext,
461 Ctx::Eph: crate::ephemeris::Ephemeris,
462 F: ReferenceFrame,
463 U: LengthUnit,
464{
465 pub fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
467 where
468 (): FrameRotationProvider<F, F2>,
469 {
470 self.inner.to_frame_with(jd, self.ctx)
471 }
472
473 pub fn to_center<C2: ReferenceCenter<Params = ()>>(&self, jd: &JulianDate) -> Position<C2, F, U>
475 where
476 (): CenterShiftProvider<C, C2, F>,
477 {
478 <Position<C, F, U> as TransformCenter<C2, F, U>>::to_center_with(
479 self.inner,
480 (),
481 *jd,
482 self.ctx,
483 )
484 }
485
486 pub fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
488 &self,
489 jd: &JulianDate,
490 ) -> Position<C2, F2, U>
491 where
492 (): CenterShiftProvider<C, C2, F>,
493 (): FrameRotationProvider<F, F2>,
494 {
495 self.inner.to_with(jd, self.ctx)
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502 use crate::coordinates::centers::{Barycentric, Geocentric};
503 use crate::coordinates::frames::{EclipticMeanJ2000, ICRS};
504 use crate::qtty::{AstronomicalUnit, AstronomicalUnits};
505
506 const EPSILON: f64 = 1e-10;
507 const AU_EPS: AstronomicalUnits = AstronomicalUnits::new(EPSILON);
508 const AU_TIGHT: AstronomicalUnits = AstronomicalUnits::new(1e-15);
509
510 #[test]
511 fn test_direction_frame_transform() {
512 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
513 let jd = crate::J2000;
514
515 let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
517
518 assert!(dir_ecl.x().is_finite() && dir_ecl.y().is_finite() && dir_ecl.z().is_finite());
520 let n0 = (dir.x() * dir.x() + dir.y() * dir.y() + dir.z() * dir.z()).sqrt();
521 let n1 =
522 (dir_ecl.x() * dir_ecl.x() + dir_ecl.y() * dir_ecl.y() + dir_ecl.z() * dir_ecl.z())
523 .sqrt();
524 assert!((n0 - n1).abs() < 1e-12);
525 }
526
527 #[test]
528 fn test_direction_frame_transform_with_ctx() {
529 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
530 let ctx: AstroContext = AstroContext::default();
531 let jd = crate::J2000;
532
533 let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame_with(&jd, &ctx);
534 let dir_ecl_default: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
535
536 assert!((dir_ecl.x() - dir_ecl_default.x()).abs() < 1e-15);
537 assert!((dir_ecl.y() - dir_ecl_default.y()).abs() < 1e-15);
538 assert!((dir_ecl.z() - dir_ecl_default.z()).abs() < 1e-15);
539 }
540
541 #[test]
542 fn test_direction_frame_roundtrip() {
543 let dir = Direction::<ICRS>::new(1.0, 2.0, 3.0);
544 let jd = crate::J2000;
545
546 let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
547 let dir_back: Direction<ICRS> = dir_ecl.to_frame(&jd);
548
549 assert!((dir_back.x() - dir.x()).abs() < EPSILON);
550 assert!((dir_back.y() - dir.y()).abs() < EPSILON);
551 assert!((dir_back.z() - dir.z()).abs() < EPSILON);
552 }
553
554 #[test]
555 fn test_position_frame_transform() {
556 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 0.0, 0.0);
557 let jd = crate::J2000;
558
559 let pos_ecl: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_frame(&jd);
560
561 assert!(pos_ecl.x().is_finite() && pos_ecl.y().is_finite() && pos_ecl.z().is_finite());
562
563 let n0 =
565 (pos.x().value().powi(2) + pos.y().value().powi(2) + pos.z().value().powi(2)).sqrt();
566 let n1 = (pos_ecl.x().value().powi(2)
567 + pos_ecl.y().value().powi(2)
568 + pos_ecl.z().value().powi(2))
569 .sqrt();
570 assert!((n0 - n1).abs() < 1e-12);
571 }
572
573 #[test]
574 fn test_position_center_transform() {
575 let jd = crate::J2000;
576
577 let geo_origin =
579 Position::<Geocentric, EclipticMeanJ2000, AstronomicalUnit>::new(0.0, 0.0, 0.0);
580 let bary: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
581 geo_origin.to_center(jd);
582
583 let dist = bary.distance();
585 assert!(
586 dist.value() > 0.9 && dist.value() < 1.1,
587 "Earth should be ~1 AU from barycenter, got {}",
588 dist
589 );
590 }
591
592 #[test]
593 fn test_position_combined_transform() {
594 let jd = crate::J2000;
595
596 let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
597
598 let result: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
600
601 assert!(
603 (result.x() - pos.x()).abs() > AU_EPS
604 || (result.y() - pos.y()).abs() > AU_EPS
605 || (result.z() - pos.z()).abs() > AU_EPS
606 );
607 }
608
609 #[test]
610 fn test_position_identity_transforms() {
611 let jd = crate::J2000;
612
613 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.5, 2.5, 3.5);
614
615 let same_frame: Position<Barycentric, ICRS, AstronomicalUnit> = pos.to_frame(&jd);
617 assert!((same_frame.x() - pos.x()).abs() < AU_EPS);
618 assert!((same_frame.y() - pos.y()).abs() < AU_EPS);
619 assert!((same_frame.z() - pos.z()).abs() < AU_EPS);
620
621 let same_center: Position<Barycentric, ICRS, AstronomicalUnit> = pos.to_center(jd);
623 assert!((same_center.x() - pos.x()).abs() < AU_EPS);
624 assert!((same_center.y() - pos.y()).abs() < AU_EPS);
625 assert!((same_center.z() - pos.z()).abs() < AU_EPS);
626 }
627
628 #[test]
629 fn test_using_engine() {
630 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
631 let engine: AstroContext = AstroContext::default();
632 let jd = crate::J2000;
633
634 let dir_ecl: Direction<EclipticMeanJ2000> = dir.using(&engine).to_frame(&jd);
635 let dir_ecl_direct: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
636
637 assert!((dir_ecl.x() - dir_ecl_direct.x()).abs() < 1e-15);
638 assert!((dir_ecl.y() - dir_ecl_direct.y()).abs() < 1e-15);
639 assert!((dir_ecl.z() - dir_ecl_direct.z()).abs() < 1e-15);
640 }
641
642 #[test]
643 fn test_phantom_model_selection_affects_true_of_date_rotation() {
644 use crate::astro::nutation::{Iau2006, Iau2006A};
645
646 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
647 let jd = crate::time::JulianDate::new(2_458_850.0);
648 let ctx: AstroContext<DefaultEphemeris, DefaultEop> = AstroContext::default();
649
650 let with_nutation = dir
651 .to_frame_with::<crate::coordinates::frames::EquatorialTrueOfDate, _>(
652 &jd,
653 &ctx.with_model::<Iau2006A>(),
654 );
655 let precession_only = dir
656 .to_frame_with::<crate::coordinates::frames::EquatorialTrueOfDate, _>(
657 &jd,
658 &ctx.with_model::<Iau2006>(),
659 );
660
661 let delta = ((with_nutation.x() - precession_only.x()).powi(2)
662 + (with_nutation.y() - precession_only.y()).powi(2)
663 + (with_nutation.z() - precession_only.z()).powi(2))
664 .sqrt();
665
666 assert!(
667 delta > 1e-9,
668 "model presets should produce distinct rotations"
669 );
670 }
671
672 #[test]
677 fn test_spherical_direction_frame_transform() {
678 use super::SphericalDirectionAstroExt;
679 use crate::coordinates::spherical;
680 use crate::qtty::DEG;
681
682 let sph_dir = spherical::Direction::<ICRS>::new(45.0 * DEG, 30.0 * DEG);
683 let jd = crate::J2000;
684
685 let sph_ecl: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
686
687 assert!(sph_ecl.azimuth.is_finite());
689 assert!(sph_ecl.polar.is_finite());
690 }
691
692 #[test]
693 fn test_spherical_direction_roundtrip() {
694 use super::SphericalDirectionAstroExt;
695 use crate::coordinates::spherical;
696 use crate::qtty::DEG;
697
698 let sph_dir = spherical::Direction::<ICRS>::new(123.0 * DEG, -45.0 * DEG);
699 let jd = crate::J2000;
700
701 let sph_ecl: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
702 let sph_back: spherical::Direction<ICRS> = sph_ecl.to_frame(&jd);
703
704 assert!((sph_back.azimuth - sph_dir.azimuth).abs().value() < 1e-8);
705 assert!((sph_back.polar - sph_dir.polar).abs().value() < 1e-8);
706 }
707
708 #[test]
709 fn test_spherical_direction_with_ctx() {
710 use super::SphericalDirectionAstroExt;
711 use crate::coordinates::spherical;
712 use crate::qtty::DEG;
713
714 let sph_dir = spherical::Direction::<ICRS>::new(90.0 * DEG, 0.0 * DEG);
715 let ctx: AstroContext = AstroContext::default();
716 let jd = crate::J2000;
717
718 let with_ctx: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame_with(&jd, &ctx);
719 let without_ctx: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
720
721 assert!((with_ctx.azimuth - without_ctx.azimuth).abs().value() < 1e-15);
722 assert!((with_ctx.polar - without_ctx.polar).abs().value() < 1e-15);
723 }
724
725 #[test]
730 fn test_vector_frame_transform() {
731 use super::VectorAstroExt;
732
733 let vec = Vector::<ICRS, AstronomicalUnit>::new(
734 AstronomicalUnits::new(1.0),
735 AstronomicalUnits::new(2.0),
736 AstronomicalUnits::new(3.0),
737 );
738 let jd = crate::J2000;
739
740 let vec_ecl: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
741 assert!(vec_ecl.x().is_finite() && vec_ecl.y().is_finite() && vec_ecl.z().is_finite());
742
743 let n0 = (vec.x() * vec.x() + vec.y() * vec.y() + vec.z() * vec.z()).scalar_sqrt();
745 let n1 =
746 (vec_ecl.x() * vec_ecl.x() + vec_ecl.y() * vec_ecl.y() + vec_ecl.z() * vec_ecl.z())
747 .scalar_sqrt();
748 assert!((n0 - n1).abs() < 1e-12);
749 }
750
751 #[test]
752 fn test_vector_frame_roundtrip() {
753 use super::VectorAstroExt;
754
755 let vec = Vector::<ICRS, AstronomicalUnit>::new(
756 AstronomicalUnits::new(0.5),
757 AstronomicalUnits::new(-0.3),
758 AstronomicalUnits::new(0.8),
759 );
760 let jd = crate::J2000;
761
762 let vec_ecl: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
763 let vec_back: Vector<ICRS, AstronomicalUnit> = vec_ecl.to_frame(&jd);
764
765 assert!((vec_back.x() - vec.x()).abs() < AU_EPS);
766 assert!((vec_back.y() - vec.y()).abs() < AU_EPS);
767 assert!((vec_back.z() - vec.z()).abs() < AU_EPS);
768 }
769
770 #[test]
771 fn test_vector_frame_with_ctx() {
772 use super::VectorAstroExt;
773
774 let vec = Vector::<ICRS, AstronomicalUnit>::new(
775 AstronomicalUnits::new(1.0),
776 AstronomicalUnits::new(0.0),
777 AstronomicalUnits::new(0.0),
778 );
779 let ctx: AstroContext = AstroContext::default();
780 let jd = crate::J2000;
781
782 let with_ctx: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame_with(&jd, &ctx);
783 let without_ctx: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
784
785 assert!((with_ctx.x() - without_ctx.x()).abs() < AU_TIGHT);
786 assert!((with_ctx.y() - without_ctx.y()).abs() < AU_TIGHT);
787 assert!((with_ctx.z() - without_ctx.z()).abs() < AU_TIGHT);
788 }
789
790 #[test]
795 fn test_using_engine_position_frame() {
796 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 0.5, 0.2);
797 let engine: AstroContext = AstroContext::default();
798 let jd = crate::J2000;
799
800 let via_engine: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
801 pos.using(&engine).to_frame(&jd);
802 let direct: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_frame(&jd);
803
804 assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
805 assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
806 assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
807 }
808
809 #[test]
810 fn test_using_engine_position_center() {
811 let pos = Position::<Geocentric, EclipticMeanJ2000, AstronomicalUnit>::new(0.0, 0.0, 0.0);
812 let engine: AstroContext = AstroContext::default();
813 let jd = crate::J2000;
814
815 let via_engine: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
816 pos.using(&engine).to_center(&jd);
817 let direct: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_center(jd);
818
819 assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
820 assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
821 assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
822 }
823
824 #[test]
825 fn test_using_engine_position_combined() {
826 let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
827 let engine: AstroContext = AstroContext::default();
828 let jd = crate::J2000;
829
830 let via_engine: Position<Geocentric, ICRS, AstronomicalUnit> = pos.using(&engine).to(&jd);
831 let direct: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
832
833 assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
834 assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
835 assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
836 }
837
838 #[test]
843 fn test_position_frame_with_ctx() {
844 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 2.0, 3.0);
845 let ctx: AstroContext = AstroContext::default();
846 let jd = crate::J2000;
847
848 let with_ctx: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
849 pos.to_frame_with(&jd, &ctx);
850 let default_ctx: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
851 pos.to_frame(&jd);
852
853 assert!((with_ctx.x() - default_ctx.x()).abs() < AU_TIGHT);
854 assert!((with_ctx.y() - default_ctx.y()).abs() < AU_TIGHT);
855 assert!((with_ctx.z() - default_ctx.z()).abs() < AU_TIGHT);
856 }
857
858 #[test]
859 fn test_position_combined_with_ctx() {
860 let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
861 let ctx: AstroContext = AstroContext::default();
862 let jd = crate::J2000;
863
864 let with_ctx: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to_with(&jd, &ctx);
865 let default_ctx: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
866
867 assert!((with_ctx.x() - default_ctx.x()).abs() < AU_TIGHT);
868 assert!((with_ctx.y() - default_ctx.y()).abs() < AU_TIGHT);
869 assert!((with_ctx.z() - default_ctx.z()).abs() < AU_TIGHT);
870 }
871}