1use std::f64::consts::PI;
38
39use super::{
40 gcrf_to_itrf_state, gcrf_to_itrf_state_approx, itrf_to_gcrf_state, itrf_to_gcrf_state_approx,
41};
42use super::{
43 qcirs2gcrs, qitrf2gcrf, qitrf2gcrf_approx, qitrf2tirs, qteme2itrf, qtirs2cirs, Error, Result,
44};
45use crate::frames::Frame;
46use crate::mathtypes::{Quaternion, Vector3};
47use crate::TimeLike;
48
49const ASEC2RAD: f64 = PI / 180.0 / 3600.0;
50
51const FRAME_BIAS_DALPHA0_AS: f64 = -0.014600;
70const FRAME_BIAS_XI0_AS: f64 = -0.016617;
71const FRAME_BIAS_ETA0_AS: f64 = -0.006819;
72
73fn qeme2000_to_gcrf() -> Quaternion {
79 let dalpha0 = FRAME_BIAS_DALPHA0_AS * ASEC2RAD;
80 let xi0 = FRAME_BIAS_XI0_AS * ASEC2RAD;
81 let eta0 = FRAME_BIAS_ETA0_AS * ASEC2RAD;
82 Quaternion::rotz(dalpha0) * Quaternion::roty(xi0) * Quaternion::rotx(-eta0)
83}
84
85fn is_earth_rotating(f: Frame) -> bool {
91 match f {
92 Frame::ITRF | Frame::TIRS => true,
93 Frame::CIRS
94 | Frame::GCRF
95 | Frame::TEME
96 | Frame::EME2000
97 | Frame::ICRF
98 | Frame::LVLH
99 | Frame::RTN
100 | Frame::NTW => false,
101 }
102}
103
104fn is_orbit_dependent(f: Frame) -> bool {
108 match f {
109 Frame::LVLH | Frame::RTN | Frame::NTW => true,
110 Frame::ITRF
111 | Frame::TIRS
112 | Frame::CIRS
113 | Frame::GCRF
114 | Frame::TEME
115 | Frame::EME2000
116 | Frame::ICRF => false,
117 }
118}
119
120fn frame_order(f: Frame) -> u8 {
126 match f {
127 Frame::ITRF => 0,
128 Frame::TIRS => 1,
129 Frame::CIRS => 2,
130 Frame::GCRF => 3,
131 Frame::TEME => 4,
132 Frame::EME2000 => 5,
133 Frame::ICRF => 6,
134 Frame::LVLH => 7,
135 Frame::RTN => 8,
136 Frame::NTW => 9,
137 }
138}
139
140fn canonicalise(from: Frame, to: Frame) -> (Frame, Frame, bool) {
142 if frame_order(from) <= frame_order(to) {
143 (from, to, false)
144 } else {
145 (to, from, true)
146 }
147}
148
149pub fn rotation<T: TimeLike>(from: Frame, to: Frame, t: &T) -> Result<Quaternion> {
169 if from == to {
170 return Ok(Quaternion::identity());
171 }
172 let (a, b, reversed) = canonicalise(from, to);
173 let q = canonical_rotation(a, b, t)?;
174 Ok(if reversed { q.conjugate() } else { q })
175}
176
177pub fn rotation_approx<T: TimeLike>(from: Frame, to: Frame, t: &T) -> Result<Quaternion> {
187 if from == to {
188 return Ok(Quaternion::identity());
189 }
190 reject_for_approx(from)?;
191 reject_for_approx(to)?;
192 let (a, b, reversed) = canonicalise(from, to);
193 let q = canonical_rotation_approx(a, b, t)?;
194 Ok(if reversed { q.conjugate() } else { q })
195}
196
197pub fn transform_state<T: TimeLike>(
211 from: Frame,
212 to: Frame,
213 t: &T,
214 pos: &Vector3,
215 vel: &Vector3,
216) -> Result<(Vector3, Vector3)> {
217 if from == to {
218 return Ok((*pos, *vel));
219 }
220 state_dispatch(from, to, t, pos, vel, false)
221}
222
223pub fn transform_state_approx<T: TimeLike>(
230 from: Frame,
231 to: Frame,
232 t: &T,
233 pos: &Vector3,
234 vel: &Vector3,
235) -> Result<(Vector3, Vector3)> {
236 if from == to {
237 return Ok((*pos, *vel));
238 }
239 reject_for_approx(from)?;
240 reject_for_approx(to)?;
241 state_dispatch(from, to, t, pos, vel, true)
242}
243
244fn reject_for_approx(frame: Frame) -> Result<()> {
249 match frame {
250 Frame::TIRS | Frame::CIRS => Err(Error::ApproxNotSupportedForFrame { frame }),
251 _ => Ok(()),
252 }
253}
254
255fn canonical_rotation<T: TimeLike>(from: Frame, to: Frame, t: &T) -> Result<Quaternion> {
258 use Frame::*;
259 let q = match (from, to) {
260 (ITRF, TIRS) => qitrf2tirs(t),
262 (TIRS, CIRS) => qtirs2cirs(t),
263 (CIRS, GCRF) => qcirs2gcrs(t),
264 (ITRF, TEME) => qteme2itrf(t).conjugate(),
265 (GCRF, EME2000) => qeme2000_to_gcrf().conjugate(),
266 (GCRF, ICRF) => Quaternion::identity(),
267
268 (ITRF, GCRF) => qitrf2gcrf(t),
270
271 (ITRF, CIRS) => qtirs2cirs(t) * qitrf2tirs(t),
273 (TIRS, GCRF) => qcirs2gcrs(t) * qtirs2cirs(t),
274 (TIRS, TEME) => (qitrf2tirs(t) * qteme2itrf(t)).conjugate(),
278 (TIRS, ICRF) => qcirs2gcrs(t) * qtirs2cirs(t),
279 (CIRS, ICRF) => qcirs2gcrs(t),
280 (EME2000, ICRF) => qeme2000_to_gcrf(),
281
282 (CIRS, TEME) => (qtirs2cirs(t) * qitrf2tirs(t) * qteme2itrf(t)).conjugate(),
286 (ITRF, EME2000) => qeme2000_to_gcrf().conjugate() * qitrf2gcrf(t),
287 (ITRF, ICRF) => qitrf2gcrf(t),
288 (TIRS, EME2000) => qeme2000_to_gcrf().conjugate() * qcirs2gcrs(t) * qtirs2cirs(t),
289 (CIRS, EME2000) => qeme2000_to_gcrf().conjugate() * qcirs2gcrs(t),
290 (GCRF, TEME) => (qitrf2gcrf(t) * qteme2itrf(t)).conjugate(),
296
297 (TEME, EME2000) => qeme2000_to_gcrf().conjugate() * qitrf2gcrf(t) * qteme2itrf(t),
299 (TEME, ICRF) => qitrf2gcrf(t) * qteme2itrf(t),
300
301 (LVLH | RTN | NTW, _) | (_, LVLH | RTN | NTW) => {
303 return Err(Error::OrbitFrameRequiresState { from, to });
304 }
305
306 _ => unreachable!("non-canonical pair reached canonical_rotation: ({from}, {to})"),
309 };
310 Ok(q)
311}
312
313fn canonical_rotation_approx<T: TimeLike>(from: Frame, to: Frame, t: &T) -> Result<Quaternion> {
316 use Frame::*;
317 let q = match (from, to) {
318 (ITRF, GCRF) => qitrf2gcrf_approx(t),
319 (ITRF, TEME) => qteme2itrf(t).conjugate(),
320 (ITRF, EME2000) => qeme2000_to_gcrf().conjugate() * qitrf2gcrf_approx(t),
321 (ITRF, ICRF) => qitrf2gcrf_approx(t),
322
323 (GCRF, EME2000) => qeme2000_to_gcrf().conjugate(),
324 (GCRF, ICRF) => Quaternion::identity(),
325 (GCRF, TEME) => (qitrf2gcrf_approx(t) * qteme2itrf(t)).conjugate(),
327
328 (EME2000, ICRF) => qeme2000_to_gcrf(),
329 (TEME, EME2000) => qeme2000_to_gcrf().conjugate() * qitrf2gcrf_approx(t) * qteme2itrf(t),
330 (TEME, ICRF) => qitrf2gcrf_approx(t) * qteme2itrf(t),
331
332 (LVLH | RTN | NTW, _) | (_, LVLH | RTN | NTW) => {
335 return Err(Error::OrbitFrameRequiresState { from, to });
336 }
337
338 _ => unreachable!("non-canonical pair reached canonical_rotation_approx: ({from}, {to})"),
339 };
340 Ok(q)
341}
342
343fn state_dispatch<T: TimeLike>(
363 from: Frame,
364 to: Frame,
365 t: &T,
366 pos: &Vector3,
367 vel: &Vector3,
368 approx: bool,
369) -> Result<(Vector3, Vector3)> {
370 use Frame::*;
371
372 if is_orbit_dependent(from) || is_orbit_dependent(to) {
375 return Err(Error::OrbitFrameRequiresState { from, to });
376 }
377
378 let is_rotating = is_earth_rotating;
379
380 if !is_rotating(from) && !is_rotating(to) {
382 let q = if approx {
383 rotation_approx(from, to, t)?
384 } else {
385 rotation(from, to, t)?
386 };
387 return Ok((q * *pos, q * *vel));
388 }
389
390 if is_rotating(from) && is_rotating(to) {
393 let q = rotation(from, to, t)?;
397 return Ok((q * *pos, q * *vel));
398 }
399
400 if is_rotating(from) {
402 let (p_itrf, v_itrf) = if from == ITRF {
405 (*pos, *vel)
406 } else {
407 let q = rotation(TIRS, ITRF, t)?;
409 (q * *pos, q * *vel)
410 };
411 let (p_gcrf, v_gcrf) = if approx {
413 itrf_to_gcrf_state_approx(&p_itrf, &v_itrf, t)
414 } else {
415 itrf_to_gcrf_state(&p_itrf, &v_itrf, t)
416 };
417 let q = if approx {
419 rotation_approx(GCRF, to, t)?
420 } else {
421 rotation(GCRF, to, t)?
422 };
423 return Ok((q * p_gcrf, q * v_gcrf));
424 }
425 let q = if approx {
428 rotation_approx(from, GCRF, t)?
429 } else {
430 rotation(from, GCRF, t)?
431 };
432 let p_gcrf = q * *pos;
433 let v_gcrf = q * *vel;
434 let (p_itrf, v_itrf) = if approx {
436 gcrf_to_itrf_state_approx(&p_gcrf, &v_gcrf, t)
437 } else {
438 gcrf_to_itrf_state(&p_gcrf, &v_gcrf, t)
439 };
440 if to == ITRF {
442 Ok((p_itrf, v_itrf))
443 } else {
444 let q_itrf_to_tirs = rotation(ITRF, TIRS, t)?;
446 Ok((q_itrf_to_tirs * p_itrf, q_itrf_to_tirs * v_itrf))
447 }
448}
449
450#[cfg(test)]
453mod tests {
454 use super::super::qgcrf2itrf;
455 use super::*;
456 use crate::Instant;
457
458 fn t() -> Instant {
459 Instant::from_datetime(2026, 5, 22, 12, 0, 0.0).unwrap()
460 }
461
462 #[test]
463 fn identity_pairs() {
464 let tm = t();
465 for f in [
466 Frame::ITRF,
467 Frame::TIRS,
468 Frame::CIRS,
469 Frame::GCRF,
470 Frame::TEME,
471 Frame::EME2000,
472 Frame::ICRF,
473 ] {
474 let q = rotation(f, f, &tm).unwrap();
475 assert!((q.w - 1.0).abs() < 1e-15, "{f}: w={}", q.w);
476 }
477 }
478
479 #[test]
480 fn matches_qitrf2gcrf() {
481 let tm = t();
482 let q_dispatch = rotation(Frame::ITRF, Frame::GCRF, &tm).unwrap();
483 let q_direct = qitrf2gcrf(&tm);
484 let v = numeris::vector![1000.0, 2000.0, 3000.0];
485 let v_dispatch = q_dispatch * v;
486 let v_direct = q_direct * v;
487 assert!(
488 (v_dispatch - v_direct).norm() < 1e-9,
489 "dispatch={v_dispatch:?} direct={v_direct:?}"
490 );
491 }
492
493 #[test]
494 fn matches_qgcrf2itrf() {
495 let tm = t();
496 let q_dispatch = rotation(Frame::GCRF, Frame::ITRF, &tm).unwrap();
497 let q_direct = qgcrf2itrf(&tm);
498 let v = numeris::vector![1000.0, 2000.0, 3000.0];
499 let v_dispatch = q_dispatch * v;
500 let v_direct = q_direct * v;
501 assert!((v_dispatch - v_direct).norm() < 1e-9);
502 }
503
504 #[test]
505 fn matches_qitrf2tirs_direct_path() {
506 let tm = t();
508 let q_dispatch = rotation(Frame::ITRF, Frame::TIRS, &tm).unwrap();
509 let q_direct = qitrf2tirs(&tm);
510 let v = numeris::vector![1000.0, 2000.0, 3000.0];
511 assert!((q_dispatch * v - q_direct * v).norm() < 1e-12);
512 }
513
514 #[test]
515 fn matches_qteme2itrf() {
516 let tm = t();
517 let q_dispatch = rotation(Frame::TEME, Frame::ITRF, &tm).unwrap();
518 let q_direct = qteme2itrf(&tm);
519 let v = numeris::vector![1000.0, 2000.0, 3000.0];
520 assert!((q_dispatch * v - q_direct * v).norm() < 1e-12);
521 }
522
523 #[test]
529 fn dispatch_teme_pairs_have_correct_direction() {
530 use super::super::qteme2gcrf;
531 let tm = t();
532 let v = numeris::vector![7000e3_f64, 1000e3, 2000e3];
533
534 let q_teme_to_gcrf_ref = qteme2gcrf(&tm);
540
541 let q_dispatch = rotation_approx(Frame::TEME, Frame::GCRF, &tm).unwrap();
544 let lhs = q_dispatch * v;
545 let rhs = q_teme_to_gcrf_ref * v;
546 assert!(
547 (lhs - rhs).norm() / v.norm() < 1e-12,
548 "rotation_approx(TEME,GCRF) direction mismatch: dispatch={lhs:?} ref={rhs:?}"
549 );
550
551 let q_dispatch = rotation_approx(Frame::GCRF, Frame::TEME, &tm).unwrap();
553 let lhs = q_dispatch * v;
554 let rhs = q_teme_to_gcrf_ref.conjugate() * v;
555 assert!(
556 (lhs - rhs).norm() / v.norm() < 1e-12,
557 "rotation_approx(GCRF,TEME) direction mismatch: dispatch={lhs:?} ref={rhs:?}"
558 );
559
560 let q_dispatch = rotation(Frame::TEME, Frame::GCRF, &tm).unwrap();
565 let lhs = q_dispatch * v;
566 let rhs = q_teme_to_gcrf_ref * v;
567 assert!(
568 (lhs - rhs).norm() < 100.0,
569 "rotation(TEME,GCRF) direction mismatch (>100 m): \
570 dispatch={lhs:?} approx_ref={rhs:?}"
571 );
572
573 let q_dispatch = rotation(Frame::GCRF, Frame::TEME, &tm).unwrap();
575 let lhs = q_dispatch * v;
576 let rhs = q_teme_to_gcrf_ref.conjugate() * v;
577 assert!(
578 (lhs - rhs).norm() < 100.0,
579 "rotation(GCRF,TEME) direction mismatch (>100 m): \
580 dispatch={lhs:?} approx_ref={rhs:?}"
581 );
582
583 let v_teme = v;
591 let lhs = rotation(Frame::TEME, Frame::TIRS, &tm).unwrap() * v_teme;
592 let rhs = qitrf2tirs(&tm) * (qteme2itrf(&tm) * v_teme);
593 assert!(
594 (lhs - rhs).norm() / v.norm() < 1e-12,
595 "rotation(TEME,TIRS) direction mismatch: dispatch={lhs:?} ref={rhs:?}"
596 );
597
598 let lhs = rotation(Frame::TEME, Frame::CIRS, &tm).unwrap() * v_teme;
599 let rhs = qtirs2cirs(&tm) * (qitrf2tirs(&tm) * (qteme2itrf(&tm) * v_teme));
600 assert!(
601 (lhs - rhs).norm() / v.norm() < 1e-12,
602 "rotation(TEME,CIRS) direction mismatch: dispatch={lhs:?} ref={rhs:?}"
603 );
604 }
605
606 #[test]
607 fn roundtrip_all_pairs() {
608 let tm = t();
609 let v = numeris::vector![6378.0, 2000.0, 3000.0];
610 let frames = [
611 Frame::ITRF,
612 Frame::TIRS,
613 Frame::CIRS,
614 Frame::GCRF,
615 Frame::TEME,
616 Frame::EME2000,
617 Frame::ICRF,
618 ];
619 for &a in &frames {
620 for &b in &frames {
621 let q_ab = rotation(a, b, &tm).unwrap();
622 let q_ba = rotation(b, a, &tm).unwrap();
623 let v_round = q_ba * (q_ab * v);
624 let err = (v_round - v).norm() / v.norm();
625 assert!(err < 1e-12, "({a} → {b} → {a}) error {err}");
626 }
627 }
628 }
629
630 #[test]
631 fn approx_rejects_intermediate_frames() {
632 let tm = t();
633 for f in [Frame::TIRS, Frame::CIRS] {
634 let err = rotation_approx(f, Frame::GCRF, &tm).unwrap_err();
635 assert!(matches!(err, Error::ApproxNotSupportedForFrame { frame } if frame == f));
636 let err = rotation_approx(Frame::GCRF, f, &tm).unwrap_err();
637 assert!(matches!(err, Error::ApproxNotSupportedForFrame { frame } if frame == f));
638 }
639 }
640
641 #[test]
642 fn approx_matches_qitrf2gcrf_approx() {
643 let tm = t();
644 let q_dispatch = rotation_approx(Frame::ITRF, Frame::GCRF, &tm).unwrap();
645 let q_direct = qitrf2gcrf_approx(&tm);
646 let v = numeris::vector![1000.0, 2000.0, 3000.0];
647 assert!((q_dispatch * v - q_direct * v).norm() < 1e-9);
648 }
649
650 #[test]
651 fn orbit_frames_rejected() {
652 let tm = t();
653 for of in [Frame::LVLH, Frame::RTN, Frame::NTW] {
654 assert!(matches!(
655 rotation(of, Frame::GCRF, &tm),
656 Err(Error::OrbitFrameRequiresState { .. })
657 ));
658 assert!(matches!(
659 rotation(Frame::GCRF, of, &tm),
660 Err(Error::OrbitFrameRequiresState { .. })
661 ));
662 }
663 }
664
665 #[test]
666 fn icrf_eme2000_constant_bias() {
667 let t1 = Instant::from_datetime(2000, 1, 1, 0, 0, 0.0).unwrap();
669 let t2 = Instant::from_datetime(2026, 5, 22, 0, 0, 0.0).unwrap();
670 let q1 = rotation(Frame::ICRF, Frame::EME2000, &t1).unwrap();
671 let q2 = rotation(Frame::ICRF, Frame::EME2000, &t2).unwrap();
672 assert!((q1.w - q2.w).abs() < 1e-15);
673 }
674
675 #[test]
676 fn eme2000_bias_matches_iers_2010() {
677 let t = Instant::from_datetime(2000, 1, 1, 12, 0, 0.0).unwrap();
681 let q = rotation(Frame::EME2000, Frame::GCRF, &t).unwrap();
682 let e1 = numeris::vector![1.0_f64, 0.0, 0.0];
683 let e2 = numeris::vector![0.0_f64, 1.0, 0.0];
684 let e3 = numeris::vector![0.0_f64, 0.0, 1.0];
685 let c0 = q * e1;
690 let c1 = q * e2;
691 let c2 = q * e3;
692 assert!((c0[0] - 1.0).abs() < 1e-14);
694 assert!((c0[1] - (-7.07827974e-8)).abs() < 1e-15);
695 assert!((c0[2] - 8.05614894e-8).abs() < 1e-15);
696 assert!((c1[0] - 7.07827948e-8).abs() < 1e-15);
698 assert!((c1[1] - 1.0).abs() < 1e-14);
699 assert!((c1[2] - 3.30594449e-8).abs() < 1e-15);
700 assert!((c2[0] - (-8.05614917e-8)).abs() < 1e-15);
702 assert!((c2[1] - (-3.30594392e-8)).abs() < 1e-15);
703 assert!((c2[2] - 1.0).abs() < 1e-14);
704 }
705
706 #[test]
707 fn transform_state_itrf_to_gcrf_matches_direct() {
708 let tm = t();
709 let p_itrf = numeris::vector![6378137.0, 0.0, 0.0];
710 let v_itrf = numeris::vector![0.0, 0.0, 0.0];
711 let (p_dispatch, v_dispatch) =
712 transform_state(Frame::ITRF, Frame::GCRF, &tm, &p_itrf, &v_itrf).unwrap();
713 let (p_direct, v_direct) = itrf_to_gcrf_state(&p_itrf, &v_itrf, &tm);
714 assert!((p_dispatch - p_direct).norm() < 1e-9);
715 assert!((v_dispatch - v_direct).norm() < 1e-12);
716 }
717
718 #[test]
719 fn transform_state_roundtrip_itrf_gcrf() {
720 let tm = t();
721 let p = numeris::vector![6378137.0, 0.0, 0.0];
722 let v = numeris::vector![0.0, 7600.0, 0.0];
723 let (p2, v2) = transform_state(Frame::ITRF, Frame::GCRF, &tm, &p, &v).unwrap();
724 let (p3, v3) = transform_state(Frame::GCRF, Frame::ITRF, &tm, &p2, &v2).unwrap();
725 assert!((p3 - p).norm() / p.norm() < 1e-10);
726 assert!((v3 - v).norm() / v.norm() < 1e-10);
727 }
728
729 #[test]
730 fn transform_state_all_non_orbit_pairs_roundtrip() {
731 let tm = t();
734 let p = numeris::vector![7000000.0, 0.0, 0.0];
735 let v = numeris::vector![0.0, 7600.0, 0.0];
736 let frames = [
737 Frame::ITRF,
738 Frame::TIRS,
739 Frame::CIRS,
740 Frame::GCRF,
741 Frame::TEME,
742 Frame::EME2000,
743 Frame::ICRF,
744 ];
745 for &a in &frames {
746 for &b in &frames {
747 let (pa, va) = transform_state(a, b, &tm, &p, &v).unwrap();
748 let (pr, vr) = transform_state(b, a, &tm, &pa, &va).unwrap();
749 let pos_err = (pr - p).norm() / p.norm();
750 let vel_err = (vr - v).norm() / v.norm();
751 assert!(pos_err < 1e-10, "({a}↔{b}) pos roundtrip err {pos_err}");
752 assert!(vel_err < 1e-10, "({a}↔{b}) vel roundtrip err {vel_err}");
753 }
754 }
755 }
756
757 #[test]
758 fn transform_state_inertial_pair_no_sweep() {
759 let tm = t();
762 let p = numeris::vector![7000000.0, 0.0, 0.0];
763 let v = numeris::vector![0.0, 7600.0, 0.0];
764 let (_, v_teme) = transform_state(Frame::GCRF, Frame::TEME, &tm, &p, &v).unwrap();
765 assert!(
766 (v_teme.norm() - v.norm()).abs() < 1e-9,
767 "inertial pair preserved |v|: |v|={}, |v_teme|={}",
768 v.norm(),
769 v_teme.norm()
770 );
771 }
772
773 #[test]
774 fn transform_state_tirs_via_itrf_chain() {
775 let tm = t();
778 let p_tirs = numeris::vector![7000000.0, 0.0, 0.0];
779 let v_tirs = numeris::vector![0.0, 0.0, 0.0];
780 let (p_a, v_a) = transform_state(Frame::TIRS, Frame::GCRF, &tm, &p_tirs, &v_tirs).unwrap();
781 let q_tirs_to_itrf = rotation(Frame::TIRS, Frame::ITRF, &tm).unwrap();
783 let p_itrf = q_tirs_to_itrf * p_tirs;
784 let v_itrf = q_tirs_to_itrf * v_tirs;
785 let (p_b, v_b) = itrf_to_gcrf_state(&p_itrf, &v_itrf, &tm);
786 assert!((p_a - p_b).norm() < 1e-9);
787 assert!((v_a - v_b).norm() < 1e-12);
788 }
789
790 #[test]
791 fn transform_state_itrf_tirs_no_sweep() {
792 let tm = t();
795 let p = numeris::vector![7000000.0, 0.0, 0.0];
796 let v = numeris::vector![0.0, 7600.0, 0.0];
797 let (_, v_tirs) = transform_state(Frame::ITRF, Frame::TIRS, &tm, &p, &v).unwrap();
798 assert!((v_tirs.norm() - v.norm()).abs() < 1e-9);
799 }
800
801 #[test]
802 fn transform_state_approx_rejects_intermediates() {
803 let tm = t();
804 let p = numeris::vector![7000000.0, 0.0, 0.0];
805 let v = numeris::vector![0.0, 7600.0, 0.0];
806 for f in [Frame::TIRS, Frame::CIRS] {
807 assert!(matches!(
808 transform_state_approx(f, Frame::GCRF, &tm, &p, &v),
809 Err(Error::ApproxNotSupportedForFrame { .. })
810 ));
811 }
812 }
813
814 #[test]
815 fn transform_state_orbit_frames_rejected() {
816 let tm = t();
817 let p = numeris::vector![7000000.0, 0.0, 0.0];
818 let v = numeris::vector![0.0, 7600.0, 0.0];
819 for of in [Frame::LVLH, Frame::RTN, Frame::NTW] {
820 assert!(matches!(
821 transform_state(of, Frame::GCRF, &tm, &p, &v),
822 Err(Error::OrbitFrameRequiresState { .. })
823 ));
824 }
825 }
826}