1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.attitudes;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.complex.Complex;
22  import org.hipparchus.complex.ComplexField;
23  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
24  import org.hipparchus.geometry.euclidean.threed.Rotation;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.util.Binary64Field;
27  import org.hipparchus.util.FastMath;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.BeforeEach;
30  import org.junit.jupiter.api.Test;
31  import org.orekit.Utils;
32  import org.orekit.bodies.CelestialBody;
33  import org.orekit.bodies.CelestialBodyFactory;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.FramesFactory;
36  import org.orekit.orbits.*;
37  import org.orekit.propagation.FieldSpacecraftState;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.time.DateComponents;
41  import org.orekit.time.FieldAbsoluteDate;
42  import org.orekit.time.TimeComponents;
43  import org.orekit.time.TimeScalesFactory;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.PVCoordinates;
46  import org.orekit.utils.PVCoordinatesProvider;
47  
48  class CelestialBodyPointedTest {
49  
50      @Test
51      void templateTestGetAttitudeRotationSameFrame() {
52          templateTestGetAttitudeRotation(FramesFactory.getEME2000());
53      }
54  
55      @Test
56      void templateTestGetAttitudeRotationDifferentFrame() {
57          templateTestGetAttitudeRotation(FramesFactory.getGCRF());
58      }
59  
60      void templateTestGetAttitudeRotation(final Frame frame) {
61          // GIVEN
62          final Frame celestialFrame = FramesFactory.getEME2000();
63          final CelestialBodyPointed celestialBodyPointed = new CelestialBodyPointed(celestialFrame,
64                  CelestialBodyFactory.getSun(), Vector3D.PLUS_K, Vector3D.PLUS_J, Vector3D.PLUS_K);
65          final PVCoordinates pv =
66                  new PVCoordinates(new Vector3D(28812595.32120171334, 5948437.45881852374, 0.0),
67                          new Vector3D(0, 0, 3680.853673522056));
68          final Orbit orbit = new CartesianOrbit(pv, frame, AbsoluteDate.ARBITRARY_EPOCH, Constants.EIGEN5C_EARTH_MU);
69          // WHEN
70          final Rotation actualRotation = celestialBodyPointed.getAttitudeRotation(orbit, orbit.getDate(), frame);
71          // THEN
72          final Rotation expectedRotation = celestialBodyPointed.getAttitude(orbit, orbit.getDate(), frame).getRotation();
73          Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation));
74      }
75  
76      @Test
77      void testFieldGetAttitudeRotationSameFrame() {
78          templateTestFieldGetAttitudeRotation(FramesFactory.getEME2000());
79      }
80  
81      @Test
82      void testFieldGetAttitudeRotationDifferentFrame() {
83          templateTestFieldGetAttitudeRotation(FramesFactory.getGCRF());
84      }
85  
86      void templateTestFieldGetAttitudeRotation(final Frame frame) {
87          // GIVEN
88          final Frame celestialFrame = FramesFactory.getEME2000();
89          final CelestialBodyPointed celestialBodyPointed = new CelestialBodyPointed(celestialFrame,
90                  CelestialBodyFactory.getSun(), Vector3D.PLUS_K, Vector3D.PLUS_J, Vector3D.PLUS_K);
91          final PVCoordinates pv =
92                  new PVCoordinates(new Vector3D(28812595.32120171334, 5948437.45881852374, 0.0),
93                          new Vector3D(0, 0, 3680.853673522056));
94          final Orbit orbit = new CartesianOrbit(pv, frame, AbsoluteDate.ARBITRARY_EPOCH, Constants.EIGEN5C_EARTH_MU);
95          final FieldOrbit<Complex> fieldOrbit = new FieldCartesianOrbit<>(ComplexField.getInstance(), orbit);
96          // WHEN
97          final FieldRotation<Complex> actualRotation = celestialBodyPointed.getAttitudeRotation(fieldOrbit, fieldOrbit.getDate(), frame);
98          // THEN
99          final FieldRotation<Complex> expectedRotation = celestialBodyPointed.getAttitude(fieldOrbit, fieldOrbit.getDate(), frame).getRotation();
100         Assertions.assertEquals(0., Rotation.distance(expectedRotation.toRotation(), actualRotation.toRotation()));
101     }
102 
103     @Test
104     void testSunPointing() {
105         CelestialBody sun = CelestialBodyFactory.getSun();
106 
107         final Frame frame = FramesFactory.getGCRF();
108         AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 01, 01),
109                                              new TimeComponents(3, 25, 45.6789),
110                                              TimeScalesFactory.getTAI());
111         AttitudeProvider sunPointing =
112             new CelestialBodyPointed(frame, sun, Vector3D.PLUS_K,
113                                      Vector3D.PLUS_I, Vector3D.PLUS_K);
114         PVCoordinates pv =
115             new PVCoordinates(new Vector3D(28812595.32120171334, 5948437.45881852374, 0.0),
116                               new Vector3D(0, 0, 3680.853673522056));
117         Orbit orbit = new KeplerianOrbit(pv, frame, date, 3.986004415e14);
118         Attitude attitude   = sunPointing.getAttitude(orbit, date, frame);
119 
120         checkField(Binary64Field.getInstance(), sunPointing, orbit, date, frame);
121 
122         Vector3D xDirection = attitude.getRotation().applyInverseTo(Vector3D.PLUS_I);
123         Vector3D zDirection = attitude.getRotation().applyInverseTo(Vector3D.PLUS_K);
124         Assertions.assertEquals(0,
125                      Vector3D.dotProduct(zDirection, Vector3D.crossProduct(xDirection, Vector3D.PLUS_K)),
126                      1.0e-15);
127 
128         // the following statement checks we take parallax into account
129         // Sun-Earth-Sat are in quadrature, with distance (Earth, Sat) == distance(Sun, Earth) / 5000
130         Assertions.assertEquals(FastMath.atan(1.0 / 5000.0),
131                             Vector3D.angle(xDirection,
132                                            sun.getPosition(date, frame)),
133                                            1.0e-15);
134 
135         double h = 0.1;
136         Attitude aMinus = sunPointing.getAttitude(orbit.shiftedBy(-h), date.shiftedBy(-h), frame);
137         Attitude a0     = sunPointing.getAttitude(orbit, date, frame);
138         Attitude aPlus  = sunPointing.getAttitude(orbit.shiftedBy(h), date.shiftedBy(h), frame);
139 
140         // check spin is consistent with attitude evolution
141         double errorAngleMinus     = Rotation.distance(aMinus.shiftedBy(h).getRotation(),
142                                                        a0.getRotation());
143         double evolutionAngleMinus = Rotation.distance(aMinus.getRotation(),
144                                                        a0.getRotation());
145         Assertions.assertEquals(0.0, errorAngleMinus, 1.0e-6 * evolutionAngleMinus);
146         double errorAnglePlus      = Rotation.distance(a0.getRotation(),
147                                                        aPlus.shiftedBy(-h).getRotation());
148         double evolutionAnglePlus  = Rotation.distance(a0.getRotation(),
149                                                        aPlus.getRotation());
150         Assertions.assertEquals(0.0, errorAnglePlus, 1.0e-6 * evolutionAnglePlus);
151 
152     }
153 
154     private <T extends CalculusFieldElement<T>> void checkField(final Field<T> field, final AttitudeProvider provider,
155                                                             final Orbit orbit, final AbsoluteDate date,
156                                                             final Frame frame)
157         {
158         Attitude attitudeD = provider.getAttitude(orbit, date, frame);
159         final FieldOrbit<T> orbitF = new FieldSpacecraftState<>(field, new SpacecraftState(orbit)).getOrbit();
160         final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, date);
161         FieldAttitude<T> attitudeF = provider.getAttitude(orbitF, dateF, frame);
162         Assertions.assertEquals(0.0, Rotation.distance(attitudeD.getRotation(), attitudeF.getRotation().toRotation()), 1.0e-15);
163         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getSpin(), attitudeF.getSpin().toVector3D()), 1.0e-15);
164         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getRotationAcceleration(), attitudeF.getRotationAcceleration().toVector3D()), 1.0e-15);
165     }
166 
167     @BeforeEach
168     public void setUp() {
169         Utils.setDataRoot("regular-data");
170     }
171 
172 }
173