1use crate::coordinates::cartesian::{Direction, Position, Vector};
75use crate::coordinates::centers::{Geodetic, ReferenceCenter};
76use crate::coordinates::frames::{EquatorialTrueOfDate, Horizontal, 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::horizontal::FromHorizontal;
83use crate::coordinates::transform::providers::{
84 frame_rotation_with, CenterShiftProvider, FrameRotationProvider,
85};
86use crate::qtty::{LengthUnit, Unit};
87#[cfg(not(feature = "std"))]
88use crate::qtty::{Real, Scalar};
89use crate::time::JulianDate;
90use affn::Rotation3;
91
92pub trait DirectionAstroExt<F: ReferenceFrame> {
101 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
111 where
112 (): FrameRotationProvider<F, F2>;
113
114 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Direction<F2>
116 where
117 Ctx: TransformContext,
118 (): FrameRotationProvider<F, F2>;
119
120 fn to_ecliptic_of_date(
124 &self,
125 jd_tt: &JulianDate,
126 ) -> Direction<crate::coordinates::frames::EclipticTrueOfDate>
127 where
128 Self: crate::coordinates::transform::ecliptic_of_date::ToEclipticTrueOfDate,
129 {
130 crate::coordinates::transform::ecliptic_of_date::ToEclipticTrueOfDate::to_ecliptic_of_date(
131 self, jd_tt,
132 )
133 }
134
135 fn to_horizontal(&self, jd_tt: &JulianDate, site: &Geodetic<ECEF>) -> Direction<Horizontal>
145 where
146 Self: crate::coordinates::transform::horizontal::ToHorizontal,
147 {
148 let ctx: AstroContext<DefaultEphemeris, DefaultEop> = AstroContext::default();
149 let eop = ctx.eop_at_tt(*jd_tt);
150 let jd_ut1 = crate::astro::earth_rotation::jd_ut1_from_tt_eop(*jd_tt, &eop);
151 crate::coordinates::transform::horizontal::ToHorizontal::to_horizontal(
152 self, &jd_ut1, jd_tt, site,
153 )
154 }
155
156 fn to_horizontal_precise(
160 &self,
161 jd_tt: &JulianDate,
162 jd_ut1: &JulianDate,
163 site: &Geodetic<ECEF>,
164 ) -> Direction<Horizontal>
165 where
166 Self: crate::coordinates::transform::horizontal::ToHorizontal,
167 {
168 crate::coordinates::transform::horizontal::ToHorizontal::to_horizontal(
169 self, jd_ut1, jd_tt, site,
170 )
171 }
172
173 fn to_equatorial(
180 &self,
181 jd_tt: &JulianDate,
182 site: &Geodetic<ECEF>,
183 ) -> Direction<EquatorialTrueOfDate>
184 where
185 Self: FromHorizontal,
186 {
187 let ctx: AstroContext<DefaultEphemeris, DefaultEop> = AstroContext::default();
188 let eop = ctx.eop_at_tt(*jd_tt);
189 let jd_ut1 = crate::astro::earth_rotation::jd_ut1_from_tt_eop(*jd_tt, &eop);
190 FromHorizontal::to_equatorial(self, &jd_ut1, jd_tt, site)
191 }
192
193 fn to_equatorial_precise(
195 &self,
196 jd_tt: &JulianDate,
197 jd_ut1: &JulianDate,
198 site: &Geodetic<ECEF>,
199 ) -> Direction<EquatorialTrueOfDate>
200 where
201 Self: FromHorizontal,
202 {
203 FromHorizontal::to_equatorial(self, jd_ut1, jd_tt, site)
204 }
205}
206
207impl<F: ReferenceFrame> DirectionAstroExt<F> for Direction<F> {
208 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
209 where
210 (): FrameRotationProvider<F, F2>,
211 {
212 let ctx: AstroContext = AstroContext::default();
213 self.to_frame_with(jd, &ctx)
214 }
215
216 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Direction<F2>
217 where
218 Ctx: TransformContext,
219 (): FrameRotationProvider<F, F2>,
220 {
221 let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*jd, ctx);
222 let [x, y, z] = rot.apply_array([self.x(), self.y(), self.z()]);
223 Direction::new_unchecked(x, y, z)
225 }
226}
227
228pub trait SphericalDirectionAstroExt<F: ReferenceFrame> {
239 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> spherical::Direction<F2>
241 where
242 (): FrameRotationProvider<F, F2>;
243
244 fn to_frame_with<F2: ReferenceFrame, Ctx>(
246 &self,
247 jd: &JulianDate,
248 ctx: &Ctx,
249 ) -> spherical::Direction<F2>
250 where
251 Ctx: TransformContext,
252 (): FrameRotationProvider<F, F2>;
253
254 fn to_horizontal(
256 &self,
257 jd_tt: &JulianDate,
258 site: &Geodetic<ECEF>,
259 ) -> spherical::Direction<Horizontal>
260 where
261 Direction<F>: crate::coordinates::transform::horizontal::ToHorizontal;
262
263 fn to_horizontal_precise(
265 &self,
266 jd_tt: &JulianDate,
267 jd_ut1: &JulianDate,
268 site: &Geodetic<ECEF>,
269 ) -> spherical::Direction<Horizontal>
270 where
271 Direction<F>: crate::coordinates::transform::horizontal::ToHorizontal;
272
273 fn to_equatorial(
275 &self,
276 jd_tt: &JulianDate,
277 site: &Geodetic<ECEF>,
278 ) -> spherical::Direction<EquatorialTrueOfDate>
279 where
280 Direction<F>: FromHorizontal;
281
282 fn to_equatorial_precise(
284 &self,
285 jd_tt: &JulianDate,
286 jd_ut1: &JulianDate,
287 site: &Geodetic<ECEF>,
288 ) -> spherical::Direction<EquatorialTrueOfDate>
289 where
290 Direction<F>: FromHorizontal;
291}
292
293impl<F: ReferenceFrame> SphericalDirectionAstroExt<F> for spherical::Direction<F> {
294 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> spherical::Direction<F2>
295 where
296 (): FrameRotationProvider<F, F2>,
297 {
298 let ctx: AstroContext = AstroContext::default();
299 self.to_frame_with(jd, &ctx)
300 }
301
302 fn to_frame_with<F2: ReferenceFrame, Ctx>(
303 &self,
304 jd: &JulianDate,
305 ctx: &Ctx,
306 ) -> spherical::Direction<F2>
307 where
308 Ctx: TransformContext,
309 (): FrameRotationProvider<F, F2>,
310 {
311 let cart: Direction<F> = self.to_cartesian();
312 let cart_f2: Direction<F2> = cart.to_frame_with(jd, ctx);
313 spherical::Direction::from_cartesian(&cart_f2)
314 }
315
316 fn to_horizontal(
317 &self,
318 jd_tt: &JulianDate,
319 site: &Geodetic<ECEF>,
320 ) -> spherical::Direction<Horizontal>
321 where
322 Direction<F>: crate::coordinates::transform::horizontal::ToHorizontal,
323 {
324 let cart = self.to_cartesian();
325 let horiz_cart = cart.to_horizontal(jd_tt, site);
326 spherical::Direction::from_cartesian(&horiz_cart)
327 }
328
329 fn to_horizontal_precise(
330 &self,
331 jd_tt: &JulianDate,
332 jd_ut1: &JulianDate,
333 site: &Geodetic<ECEF>,
334 ) -> spherical::Direction<Horizontal>
335 where
336 Direction<F>: crate::coordinates::transform::horizontal::ToHorizontal,
337 {
338 let cart = self.to_cartesian();
339 let horiz_cart = cart.to_horizontal_precise(jd_tt, jd_ut1, site);
340 spherical::Direction::from_cartesian(&horiz_cart)
341 }
342
343 fn to_equatorial(
344 &self,
345 jd_tt: &JulianDate,
346 site: &Geodetic<ECEF>,
347 ) -> spherical::Direction<EquatorialTrueOfDate>
348 where
349 Direction<F>: FromHorizontal,
350 {
351 let cart = self.to_cartesian();
352 let equ_cart = DirectionAstroExt::to_equatorial(&cart, jd_tt, site);
353 spherical::Direction::from_cartesian(&equ_cart)
354 }
355
356 fn to_equatorial_precise(
357 &self,
358 jd_tt: &JulianDate,
359 jd_ut1: &JulianDate,
360 site: &Geodetic<ECEF>,
361 ) -> spherical::Direction<EquatorialTrueOfDate>
362 where
363 Direction<F>: FromHorizontal,
364 {
365 let cart = self.to_cartesian();
366 let equ_cart = DirectionAstroExt::to_equatorial_precise(&cart, jd_tt, jd_ut1, site);
367 spherical::Direction::from_cartesian(&equ_cart)
368 }
369}
370
371pub trait VectorAstroExt<F: ReferenceFrame, U: Unit> {
380 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Vector<F2, U>
382 where
383 (): FrameRotationProvider<F, F2>;
384
385 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Vector<F2, U>
387 where
388 Ctx: TransformContext,
389 (): FrameRotationProvider<F, F2>;
390}
391
392impl<F: ReferenceFrame, U: Unit> VectorAstroExt<F, U> for Vector<F, U> {
393 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Vector<F2, U>
394 where
395 (): FrameRotationProvider<F, F2>,
396 {
397 let ctx: AstroContext = AstroContext::default();
398 self.to_frame_with(jd, &ctx)
399 }
400
401 fn to_frame_with<F2: ReferenceFrame, Ctx>(&self, jd: &JulianDate, ctx: &Ctx) -> Vector<F2, U>
402 where
403 Ctx: TransformContext,
404 (): FrameRotationProvider<F, F2>,
405 {
406 let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*jd, ctx);
407 let [x, y, z] = rot * [self.x(), self.y(), self.z()];
408 Vector::new(x, y, z)
409 }
410}
411
412pub trait PositionAstroExt<C: ReferenceCenter, F: ReferenceFrame, U: LengthUnit> {
428 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
430 where
431 (): FrameRotationProvider<F, F2>;
432
433 fn to_frame_with<F2: ReferenceFrame, Ctx>(
435 &self,
436 jd: &JulianDate,
437 ctx: &Ctx,
438 ) -> Position<C, F2, U>
439 where
440 Ctx: TransformContext,
441 (): FrameRotationProvider<F, F2>;
442
443 fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
450 &self,
451 jd: &JulianDate,
452 ) -> Position<C2, F2, U>
453 where
454 (): CenterShiftProvider<C, C2, F>,
455 (): FrameRotationProvider<F, F2>;
456
457 fn to_with<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame, Ctx>(
459 &self,
460 jd: &JulianDate,
461 ctx: &Ctx,
462 ) -> Position<C2, F2, U>
463 where
464 Ctx: TransformContext,
465 Ctx::Eph: crate::ephemeris::Ephemeris,
466 (): CenterShiftProvider<C, C2, F>,
467 (): FrameRotationProvider<F, F2>;
468}
469
470impl<C, F, U> PositionAstroExt<C, F, U> for Position<C, F, U>
471where
472 C: ReferenceCenter<Params = ()>,
473 F: ReferenceFrame,
474 U: LengthUnit,
475{
476 fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
477 where
478 (): FrameRotationProvider<F, F2>,
479 {
480 let ctx: AstroContext = AstroContext::default();
481 self.to_frame_with(jd, &ctx)
482 }
483
484 fn to_frame_with<F2: ReferenceFrame, Ctx>(
485 &self,
486 jd: &JulianDate,
487 ctx: &Ctx,
488 ) -> Position<C, F2, U>
489 where
490 Ctx: TransformContext,
491 (): FrameRotationProvider<F, F2>,
492 {
493 let rot: Rotation3 = frame_rotation_with::<F, F2, Ctx>(*jd, ctx);
494 let [x, y, z] = rot * [self.x(), self.y(), self.z()];
495 Position::new(x, y, z)
496 }
497
498 fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
499 &self,
500 jd: &JulianDate,
501 ) -> Position<C2, F2, U>
502 where
503 (): CenterShiftProvider<C, C2, F>,
504 (): FrameRotationProvider<F, F2>,
505 {
506 let ctx: AstroContext = AstroContext::default();
507 self.to_with(jd, &ctx)
508 }
509
510 fn to_with<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame, Ctx>(
511 &self,
512 jd: &JulianDate,
513 ctx: &Ctx,
514 ) -> Position<C2, F2, U>
515 where
516 Ctx: TransformContext,
517 Ctx::Eph: crate::ephemeris::Ephemeris,
518 (): CenterShiftProvider<C, C2, F>,
519 (): FrameRotationProvider<F, F2>,
520 {
521 <Self as TransformCenter<C2, F, U>>::to_center_with(self, (), *jd, ctx)
523 .to_frame_with::<F2, Ctx>(jd, ctx)
524 }
525}
526
527pub struct WithEngine<'a, T, Ctx> {
542 inner: &'a T,
543 ctx: &'a Ctx,
544}
545
546pub trait UsingEngine: Sized {
548 fn using<'a, Ctx>(&'a self, engine: &'a Ctx) -> WithEngine<'a, Self, Ctx>
551 where
552 Ctx: TransformContext,
553 {
554 WithEngine {
555 inner: self,
556 ctx: engine,
557 }
558 }
559}
560
561impl<T> UsingEngine for T {}
563
564impl<'a, F: ReferenceFrame, Ctx> WithEngine<'a, Direction<F>, Ctx>
567where
568 Ctx: TransformContext,
569{
570 pub fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Direction<F2>
572 where
573 (): FrameRotationProvider<F, F2>,
574 {
575 self.inner.to_frame_with(jd, self.ctx)
576 }
577}
578
579impl<'a, C, F, U, Ctx> WithEngine<'a, Position<C, F, U>, Ctx>
582where
583 C: ReferenceCenter<Params = ()>,
584 Ctx: TransformContext,
585 Ctx::Eph: crate::ephemeris::Ephemeris,
586 F: ReferenceFrame,
587 U: LengthUnit,
588{
589 pub fn to_frame<F2: ReferenceFrame>(&self, jd: &JulianDate) -> Position<C, F2, U>
591 where
592 (): FrameRotationProvider<F, F2>,
593 {
594 self.inner.to_frame_with(jd, self.ctx)
595 }
596
597 pub fn to_center<C2: ReferenceCenter<Params = ()>>(&self, jd: &JulianDate) -> Position<C2, F, U>
599 where
600 (): CenterShiftProvider<C, C2, F>,
601 {
602 <Position<C, F, U> as TransformCenter<C2, F, U>>::to_center_with(
603 self.inner,
604 (),
605 *jd,
606 self.ctx,
607 )
608 }
609
610 pub fn to<C2: ReferenceCenter<Params = ()>, F2: ReferenceFrame>(
612 &self,
613 jd: &JulianDate,
614 ) -> Position<C2, F2, U>
615 where
616 (): CenterShiftProvider<C, C2, F>,
617 (): FrameRotationProvider<F, F2>,
618 {
619 self.inner.to_with(jd, self.ctx)
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626 use crate::coordinates::centers::{Barycentric, Geocentric};
627 use crate::coordinates::frames::{EclipticMeanJ2000, ICRS};
628 use crate::qtty::{AstronomicalUnit, AstronomicalUnits};
629
630 const EPSILON: f64 = 1e-10;
631 const AU_EPS: AstronomicalUnits = AstronomicalUnits::new(EPSILON);
632 const AU_TIGHT: AstronomicalUnits = AstronomicalUnits::new(1e-15);
633
634 #[test]
635 fn test_direction_frame_transform() {
636 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
637 let jd = crate::J2000;
638
639 let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
641
642 assert!(dir_ecl.x().is_finite() && dir_ecl.y().is_finite() && dir_ecl.z().is_finite());
644 let n0 = (dir.x() * dir.x() + dir.y() * dir.y() + dir.z() * dir.z()).sqrt();
645 let n1 =
646 (dir_ecl.x() * dir_ecl.x() + dir_ecl.y() * dir_ecl.y() + dir_ecl.z() * dir_ecl.z())
647 .sqrt();
648 assert!((n0 - n1).abs() < 1e-12);
649 }
650
651 #[test]
652 fn test_direction_frame_transform_with_ctx() {
653 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
654 let ctx: AstroContext = AstroContext::default();
655 let jd = crate::J2000;
656
657 let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame_with(&jd, &ctx);
658 let dir_ecl_default: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
659
660 assert!((dir_ecl.x() - dir_ecl_default.x()).abs() < 1e-15);
661 assert!((dir_ecl.y() - dir_ecl_default.y()).abs() < 1e-15);
662 assert!((dir_ecl.z() - dir_ecl_default.z()).abs() < 1e-15);
663 }
664
665 #[test]
666 fn test_direction_frame_roundtrip() {
667 let dir = Direction::<ICRS>::new(1.0, 2.0, 3.0);
668 let jd = crate::J2000;
669
670 let dir_ecl: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
671 let dir_back: Direction<ICRS> = dir_ecl.to_frame(&jd);
672
673 assert!((dir_back.x() - dir.x()).abs() < EPSILON);
674 assert!((dir_back.y() - dir.y()).abs() < EPSILON);
675 assert!((dir_back.z() - dir.z()).abs() < EPSILON);
676 }
677
678 #[test]
679 fn test_position_frame_transform() {
680 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 0.0, 0.0);
681 let jd = crate::J2000;
682
683 let pos_ecl: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_frame(&jd);
684
685 assert!(pos_ecl.x().is_finite() && pos_ecl.y().is_finite() && pos_ecl.z().is_finite());
686
687 let n0 =
689 (pos.x().value().powi(2) + pos.y().value().powi(2) + pos.z().value().powi(2)).sqrt();
690 let n1 = (pos_ecl.x().value().powi(2)
691 + pos_ecl.y().value().powi(2)
692 + pos_ecl.z().value().powi(2))
693 .sqrt();
694 assert!((n0 - n1).abs() < 1e-12);
695 }
696
697 #[test]
698 fn test_position_center_transform() {
699 let jd = crate::J2000;
700
701 let geo_origin =
703 Position::<Geocentric, EclipticMeanJ2000, AstronomicalUnit>::new(0.0, 0.0, 0.0);
704 let bary: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
705 geo_origin.to_center(jd);
706
707 let dist = bary.distance();
709 assert!(
710 dist.value() > 0.9 && dist.value() < 1.1,
711 "Earth should be ~1 AU from barycenter, got {}",
712 dist
713 );
714 }
715
716 #[test]
717 fn test_position_combined_transform() {
718 let jd = crate::J2000;
719
720 let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
721
722 let result: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
724
725 assert!(
727 (result.x() - pos.x()).abs() > AU_EPS
728 || (result.y() - pos.y()).abs() > AU_EPS
729 || (result.z() - pos.z()).abs() > AU_EPS
730 );
731 }
732
733 #[test]
734 fn test_position_identity_transforms() {
735 let jd = crate::J2000;
736
737 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.5, 2.5, 3.5);
738
739 let same_frame: Position<Barycentric, ICRS, AstronomicalUnit> = pos.to_frame(&jd);
741 assert!((same_frame.x() - pos.x()).abs() < AU_EPS);
742 assert!((same_frame.y() - pos.y()).abs() < AU_EPS);
743 assert!((same_frame.z() - pos.z()).abs() < AU_EPS);
744
745 let same_center: Position<Barycentric, ICRS, AstronomicalUnit> = pos.to_center(jd);
747 assert!((same_center.x() - pos.x()).abs() < AU_EPS);
748 assert!((same_center.y() - pos.y()).abs() < AU_EPS);
749 assert!((same_center.z() - pos.z()).abs() < AU_EPS);
750 }
751
752 #[test]
753 fn test_using_engine() {
754 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
755 let engine: AstroContext = AstroContext::default();
756 let jd = crate::J2000;
757
758 let dir_ecl: Direction<EclipticMeanJ2000> = dir.using(&engine).to_frame(&jd);
759 let dir_ecl_direct: Direction<EclipticMeanJ2000> = dir.to_frame(&jd);
760
761 assert!((dir_ecl.x() - dir_ecl_direct.x()).abs() < 1e-15);
762 assert!((dir_ecl.y() - dir_ecl_direct.y()).abs() < 1e-15);
763 assert!((dir_ecl.z() - dir_ecl_direct.z()).abs() < 1e-15);
764 }
765
766 #[test]
767 fn test_phantom_model_selection_affects_true_of_date_rotation() {
768 use crate::astro::nutation::{Iau2006, Iau2006A};
769
770 let dir = Direction::<ICRS>::new(1.0, 0.0, 0.0);
771 let jd = crate::time::JulianDate::new(2_458_850.0);
772 let ctx: AstroContext<DefaultEphemeris, DefaultEop> = AstroContext::default();
773
774 let with_nutation = dir
775 .to_frame_with::<crate::coordinates::frames::EquatorialTrueOfDate, _>(
776 &jd,
777 &ctx.with_model::<Iau2006A>(),
778 );
779 let precession_only = dir
780 .to_frame_with::<crate::coordinates::frames::EquatorialTrueOfDate, _>(
781 &jd,
782 &ctx.with_model::<Iau2006>(),
783 );
784
785 let delta = ((with_nutation.x() - precession_only.x()).powi(2)
786 + (with_nutation.y() - precession_only.y()).powi(2)
787 + (with_nutation.z() - precession_only.z()).powi(2))
788 .sqrt();
789
790 assert!(
791 delta > 1e-9,
792 "model presets should produce distinct rotations"
793 );
794 }
795
796 #[test]
801 fn test_spherical_direction_frame_transform() {
802 use super::SphericalDirectionAstroExt;
803 use crate::coordinates::spherical;
804 use crate::qtty::DEG;
805
806 let sph_dir = spherical::Direction::<ICRS>::new(45.0 * DEG, 30.0 * DEG);
807 let jd = crate::J2000;
808
809 let sph_ecl: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
810
811 assert!(sph_ecl.azimuth.is_finite());
813 assert!(sph_ecl.polar.is_finite());
814 }
815
816 #[test]
817 fn test_spherical_direction_roundtrip() {
818 use super::SphericalDirectionAstroExt;
819 use crate::coordinates::spherical;
820 use crate::qtty::DEG;
821
822 let sph_dir = spherical::Direction::<ICRS>::new(123.0 * DEG, -45.0 * DEG);
823 let jd = crate::J2000;
824
825 let sph_ecl: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
826 let sph_back: spherical::Direction<ICRS> = sph_ecl.to_frame(&jd);
827
828 assert!((sph_back.azimuth - sph_dir.azimuth).abs().value() < 1e-8);
829 assert!((sph_back.polar - sph_dir.polar).abs().value() < 1e-8);
830 }
831
832 #[test]
833 fn test_spherical_direction_with_ctx() {
834 use super::SphericalDirectionAstroExt;
835 use crate::coordinates::spherical;
836 use crate::qtty::DEG;
837
838 let sph_dir = spherical::Direction::<ICRS>::new(90.0 * DEG, 0.0 * DEG);
839 let ctx: AstroContext = AstroContext::default();
840 let jd = crate::J2000;
841
842 let with_ctx: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame_with(&jd, &ctx);
843 let without_ctx: spherical::Direction<EclipticMeanJ2000> = sph_dir.to_frame(&jd);
844
845 assert!((with_ctx.azimuth - without_ctx.azimuth).abs().value() < 1e-15);
846 assert!((with_ctx.polar - without_ctx.polar).abs().value() < 1e-15);
847 }
848
849 #[test]
854 fn test_vector_frame_transform() {
855 use super::VectorAstroExt;
856
857 let vec = Vector::<ICRS, AstronomicalUnit>::new(
858 AstronomicalUnits::new(1.0),
859 AstronomicalUnits::new(2.0),
860 AstronomicalUnits::new(3.0),
861 );
862 let jd = crate::J2000;
863
864 let vec_ecl: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
865 assert!(vec_ecl.x().is_finite() && vec_ecl.y().is_finite() && vec_ecl.z().is_finite());
866
867 let n0 = (vec.x() * vec.x() + vec.y() * vec.y() + vec.z() * vec.z()).scalar_sqrt();
869 let n1 =
870 (vec_ecl.x() * vec_ecl.x() + vec_ecl.y() * vec_ecl.y() + vec_ecl.z() * vec_ecl.z())
871 .scalar_sqrt();
872 assert!((n0 - n1).abs() < 1e-12);
873 }
874
875 #[test]
876 fn test_vector_frame_roundtrip() {
877 use super::VectorAstroExt;
878
879 let vec = Vector::<ICRS, AstronomicalUnit>::new(
880 AstronomicalUnits::new(0.5),
881 AstronomicalUnits::new(-0.3),
882 AstronomicalUnits::new(0.8),
883 );
884 let jd = crate::J2000;
885
886 let vec_ecl: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
887 let vec_back: Vector<ICRS, AstronomicalUnit> = vec_ecl.to_frame(&jd);
888
889 assert!((vec_back.x() - vec.x()).abs() < AU_EPS);
890 assert!((vec_back.y() - vec.y()).abs() < AU_EPS);
891 assert!((vec_back.z() - vec.z()).abs() < AU_EPS);
892 }
893
894 #[test]
895 fn test_vector_frame_with_ctx() {
896 use super::VectorAstroExt;
897
898 let vec = Vector::<ICRS, AstronomicalUnit>::new(
899 AstronomicalUnits::new(1.0),
900 AstronomicalUnits::new(0.0),
901 AstronomicalUnits::new(0.0),
902 );
903 let ctx: AstroContext = AstroContext::default();
904 let jd = crate::J2000;
905
906 let with_ctx: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame_with(&jd, &ctx);
907 let without_ctx: Vector<EclipticMeanJ2000, AstronomicalUnit> = vec.to_frame(&jd);
908
909 assert!((with_ctx.x() - without_ctx.x()).abs() < AU_TIGHT);
910 assert!((with_ctx.y() - without_ctx.y()).abs() < AU_TIGHT);
911 assert!((with_ctx.z() - without_ctx.z()).abs() < AU_TIGHT);
912 }
913
914 #[test]
919 fn test_using_engine_position_frame() {
920 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 0.5, 0.2);
921 let engine: AstroContext = AstroContext::default();
922 let jd = crate::J2000;
923
924 let via_engine: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
925 pos.using(&engine).to_frame(&jd);
926 let direct: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_frame(&jd);
927
928 assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
929 assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
930 assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
931 }
932
933 #[test]
934 fn test_using_engine_position_center() {
935 let pos = Position::<Geocentric, EclipticMeanJ2000, AstronomicalUnit>::new(0.0, 0.0, 0.0);
936 let engine: AstroContext = AstroContext::default();
937 let jd = crate::J2000;
938
939 let via_engine: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
940 pos.using(&engine).to_center(&jd);
941 let direct: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> = pos.to_center(jd);
942
943 assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
944 assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
945 assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
946 }
947
948 #[test]
949 fn test_using_engine_position_combined() {
950 let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
951 let engine: AstroContext = AstroContext::default();
952 let jd = crate::J2000;
953
954 let via_engine: Position<Geocentric, ICRS, AstronomicalUnit> = pos.using(&engine).to(&jd);
955 let direct: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
956
957 assert!((via_engine.x() - direct.x()).abs() < AU_TIGHT);
958 assert!((via_engine.y() - direct.y()).abs() < AU_TIGHT);
959 assert!((via_engine.z() - direct.z()).abs() < AU_TIGHT);
960 }
961
962 #[test]
967 fn test_position_frame_with_ctx() {
968 let pos = Position::<Barycentric, ICRS, AstronomicalUnit>::new(1.0, 2.0, 3.0);
969 let ctx: AstroContext = AstroContext::default();
970 let jd = crate::J2000;
971
972 let with_ctx: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
973 pos.to_frame_with(&jd, &ctx);
974 let default_ctx: Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> =
975 pos.to_frame(&jd);
976
977 assert!((with_ctx.x() - default_ctx.x()).abs() < AU_TIGHT);
978 assert!((with_ctx.y() - default_ctx.y()).abs() < AU_TIGHT);
979 assert!((with_ctx.z() - default_ctx.z()).abs() < AU_TIGHT);
980 }
981
982 #[test]
983 fn test_position_combined_with_ctx() {
984 let pos = Position::<Barycentric, EclipticMeanJ2000, AstronomicalUnit>::new(1.0, 0.5, 0.2);
985 let ctx: AstroContext = AstroContext::default();
986 let jd = crate::J2000;
987
988 let with_ctx: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to_with(&jd, &ctx);
989 let default_ctx: Position<Geocentric, ICRS, AstronomicalUnit> = pos.to(&jd);
990
991 assert!((with_ctx.x() - default_ctx.x()).abs() < AU_TIGHT);
992 assert!((with_ctx.y() - default_ctx.y()).abs() < AU_TIGHT);
993 assert!((with_ctx.z() - default_ctx.z()).abs() < AU_TIGHT);
994 }
995}