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.*;
24  import org.hipparchus.util.Binary64Field;
25  import org.hipparchus.util.FastMath;
26  import org.junit.jupiter.api.AfterEach;
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.BeforeEach;
29  import org.junit.jupiter.api.Test;
30  import org.orekit.Utils;
31  import org.orekit.bodies.OneAxisEllipsoid;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitMessages;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.FramesFactory;
36  import org.orekit.frames.LOFType;
37  import org.orekit.orbits.*;
38  import org.orekit.propagation.FieldSpacecraftState;
39  import org.orekit.propagation.Propagator;
40  import org.orekit.propagation.SpacecraftState;
41  import org.orekit.propagation.analytical.KeplerianPropagator;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.DateComponents;
44  import org.orekit.time.FieldAbsoluteDate;
45  import org.orekit.time.TimeComponents;
46  import org.orekit.time.TimeScalesFactory;
47  import org.orekit.utils.AngularCoordinates;
48  import org.orekit.utils.IERSConventions;
49  import org.orekit.utils.TimeStampedFieldPVCoordinates;
50  import org.orekit.utils.TimeStampedPVCoordinates;
51  
52  
53  class LofOffsetPointingTest {
54  
55      // Computation date
56      private AbsoluteDate date;
57  
58      // Body mu
59      private double mu;
60  
61      // Reference frame = ITRF
62      private Frame frameItrf;
63  
64      // Earth shape
65      OneAxisEllipsoid earthSpheric;
66  
67      /** Test if both constructors are equivalent
68       */
69      @Test
70      void testLof() {
71  
72          //  Satellite position
73          final CircularOrbit circ =
74              new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(0.), FastMath.toRadians(270.),
75                                     FastMath.toRadians(5.300), PositionAngleType.MEAN,
76                                     FramesFactory.getEME2000(), date, mu);
77  
78          // Create lof aligned law
79          //************************
80          final LofOffset lofLaw = new LofOffset(circ.getFrame(), LOFType.LVLH_CCSDS);
81          final LofOffsetPointing lofPointing = new LofOffsetPointing(circ.getFrame(), earthSpheric, lofLaw, Vector3D.PLUS_K);
82          final Rotation
83              lofRot = lofPointing.getAttitude(circ, date, circ.getFrame()).getRotation();
84  
85          // Compare to body center pointing law
86          //*************************************
87          final BodyCenterPointing centerLaw = new BodyCenterPointing(circ.getFrame(), earthSpheric);
88          final Rotation centerRot = centerLaw.getAttitude(circ, date, circ.getFrame()).getRotation();
89          final double angleBodyCenter = centerRot.composeInverse(lofRot, RotationConvention.VECTOR_OPERATOR).getAngle();
90          Assertions.assertEquals(0., angleBodyCenter, Utils.epsilonAngle);
91  
92          // Compare to nadir pointing law
93          //*******************************
94          final NadirPointing nadirLaw = new NadirPointing(circ.getFrame(), earthSpheric);
95          final Rotation nadirRot = nadirLaw.getAttitude(circ, date, circ.getFrame()).getRotation();
96          final double angleNadir = nadirRot.composeInverse(lofRot, RotationConvention.VECTOR_OPERATOR).getAngle();
97          Assertions.assertEquals(0., angleNadir, Utils.epsilonAngle);
98  
99      }
100 
101     @Test
102     void testMiss() {
103         final CircularOrbit circ =
104             new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(0.), FastMath.toRadians(270.),
105                                    FastMath.toRadians(5.300), PositionAngleType.MEAN,
106                                    FramesFactory.getEME2000(), date, mu);
107         final LofOffset upsideDown = new LofOffset(circ.getFrame(), LOFType.LVLH_CCSDS, RotationOrder.XYX, FastMath.PI, 0, 0);
108         final LofOffsetPointing pointing = new LofOffsetPointing(circ.getFrame(), earthSpheric, upsideDown, Vector3D.PLUS_K);
109         try {
110             pointing.getTargetPV(circ, date, circ.getFrame());
111             Assertions.fail("an exception should have been thrown");
112         } catch (OrekitException oe) {
113             Assertions.assertEquals(OrekitMessages.ATTITUDE_POINTING_LAW_DOES_NOT_POINT_TO_GROUND, oe.getSpecifier());
114         }
115     }
116 
117     @Test
118     void testSpin() {
119 
120         AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 1, 1),
121                                              new TimeComponents(3, 25, 45.6789),
122                                              TimeScalesFactory.getUTC());
123         KeplerianOrbit orbit =
124             new KeplerianOrbit(7178000.0, 1.e-4, FastMath.toRadians(50.),
125                               FastMath.toRadians(10.), FastMath.toRadians(20.),
126                               FastMath.toRadians(30.), PositionAngleType.MEAN,
127                               FramesFactory.getEME2000(), date, 3.986004415e14);
128 
129         final AttitudeProvider law =
130             new LofOffsetPointing(orbit.getFrame(), earthSpheric,
131                                   new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS, RotationOrder.XYX, 0.1, 0.2, 0.3),
132                                   Vector3D.PLUS_K);
133 
134         Propagator propagator = new KeplerianPropagator(orbit, law);
135 
136         double h = 0.01;
137         SpacecraftState sMinus = propagator.propagate(date.shiftedBy(-h));
138         SpacecraftState s0     = propagator.propagate(date);
139         SpacecraftState sPlus  = propagator.propagate(date.shiftedBy(h));
140 
141         // check spin is consistent with attitude evolution
142         double errorAngleMinus     = Rotation.distance(sMinus.shiftedBy(h).getAttitude().getRotation(),
143                                                        s0.getAttitude().getRotation());
144         double evolutionAngleMinus = Rotation.distance(sMinus.getAttitude().getRotation(),
145                                                        s0.getAttitude().getRotation());
146         Assertions.assertEquals(0.0, errorAngleMinus, 1.0e-6 * evolutionAngleMinus);
147         double errorAnglePlus      = Rotation.distance(s0.getAttitude().getRotation(),
148                                                        sPlus.shiftedBy(-h).getAttitude().getRotation());
149         double evolutionAnglePlus  = Rotation.distance(s0.getAttitude().getRotation(),
150                                                        sPlus.getAttitude().getRotation());
151         Assertions.assertEquals(0.0, errorAnglePlus, 1.0e-6 * evolutionAnglePlus);
152 
153         Vector3D spin0 = s0.getAttitude().getSpin();
154         Vector3D reference = AngularCoordinates.estimateRate(sMinus.getAttitude().getRotation(),
155                                                              sPlus.getAttitude().getRotation(),
156                                                              2 * h);
157         Assertions.assertTrue(spin0.getNorm() > 1.0e-3);
158         Assertions.assertEquals(0.0, spin0.subtract(reference).getNorm(), 1.0e-10);
159 
160     }
161 
162     @Test
163     void testTypesField() {
164         AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 1, 1),
165                                              new TimeComponents(3, 25, 45.6789),
166                                              TimeScalesFactory.getUTC());
167         KeplerianOrbit orbit =
168             new KeplerianOrbit(7178000.0, 1.e-4, FastMath.toRadians(50.),
169                               FastMath.toRadians(10.), FastMath.toRadians(20.),
170                               FastMath.toRadians(30.), PositionAngleType.MEAN,
171                               FramesFactory.getEME2000(), date, 3.986004415e14);
172 
173         for (final LOFType type : LOFType.values()) {
174             RotationOrder order = RotationOrder.ZXY;
175             double alpha1 = 0.123;
176             double alpha2 = 0.456;
177             double alpha3 = 0.789;
178             LofOffset law = new LofOffset(orbit.getFrame(), type, order, alpha1, alpha2, alpha3);
179             final Vector3D dir;
180             switch (type) {
181                 case TNW:
182                     dir = Vector3D.PLUS_J;
183                     break;
184                 case QSW:
185                     dir = Vector3D.MINUS_I;
186                     break;
187                 case LVLH:
188                     dir = Vector3D.MINUS_I;
189                     break;
190                 case LVLH_CCSDS:
191                     dir = Vector3D.PLUS_K;
192                     break;
193                 case VNC:
194                     dir = Vector3D.MINUS_K;
195                     break;
196                 case NTW:
197                     dir = Vector3D.MINUS_I;
198                     break;
199                 default :
200                     // EQW and deprecated VVLH, not used in this test
201                     continue;
202             }
203             LofOffsetPointing lop = new LofOffsetPointing(orbit.getFrame(), earthSpheric, law, dir);
204             checkField(Binary64Field.getInstance(), lop, orbit, date, orbit.getFrame());
205         }
206     }
207 
208     private <T extends CalculusFieldElement<T>> void checkField(final Field<T> field, final GroundPointing provider,
209                                                             final Orbit orbit, final AbsoluteDate date,
210                                                             final Frame frame)
211         {
212 
213         final Attitude attitudeD = provider.getAttitude(orbit, date, frame);
214         final FieldOrbit<T> orbitF = new FieldSpacecraftState<>(field, new SpacecraftState(orbit)).getOrbit();
215         final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, date);
216         final FieldAttitude<T> attitudeF = provider.getAttitude(orbitF, dateF, frame);
217         final double tolerance = 1e-15;
218         Assertions.assertEquals(0.0, Rotation.distance(attitudeD.getRotation(), attitudeF.getRotation().toRotation()), tolerance);
219         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getSpin(), attitudeF.getSpin().toVector3D()), tolerance);
220         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getRotationAcceleration(), attitudeF.getRotationAcceleration().toVector3D()), tolerance);
221 
222         final Rotation rotationD = provider.getAttitudeRotation(orbit, date, frame);
223         final FieldRotation<T> rotationF = provider.getAttitudeRotation(orbitF, dateF, frame);
224         Assertions.assertEquals(0.0, Rotation.distance(rotationD, rotationF.toRotation()), tolerance);
225 
226         final TimeStampedPVCoordinates         pvD = provider.getTargetPV(orbit, date, frame);
227         final TimeStampedFieldPVCoordinates<T> pvF = provider.getTargetPV(orbitF, dateF, frame);
228 
229         Assertions.assertEquals(0.0, Vector3D.distance(pvD.getPosition(),     pvF.getPosition().toVector3D()),     2.0e-8);
230         Assertions.assertEquals(0.0, Vector3D.distance(pvD.getVelocity(),     pvF.getVelocity().toVector3D()),     3.0e-8);
231         Assertions.assertEquals(0.0, Vector3D.distance(pvD.getAcceleration(), pvF.getAcceleration().toVector3D()), 2.0e-5);
232     }
233 
234     @Test
235     void testGetAttitudeRotation() {
236         // GIVEN
237         final CircularOrbit circ =
238                 new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(0.), FastMath.toRadians(270.),
239                         FastMath.toRadians(5.300), PositionAngleType.MEAN,
240                         FramesFactory.getEME2000(), date, mu);
241         final LofOffset upsideDown = new LofOffset(circ.getFrame(), LOFType.LVLH_CCSDS, RotationOrder.XYX, FastMath.PI, 0, 0);
242         final LofOffsetPointing pointing = new LofOffsetPointing(circ.getFrame(), earthSpheric, upsideDown, Vector3D.PLUS_K);
243         // WHEN
244         final Rotation actualRotation = pointing.getAttitudeRotation(circ, circ.getDate(), circ.getFrame());
245         // THEN
246         final Rotation expectedRotation = pointing.getAttitude(circ, circ.getDate(), circ.getFrame()).getRotation();
247         Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation));
248     }
249 
250     @Test
251     void testGetTargetPosition() {
252         // GIVEN
253         final CircularOrbit circ =
254                 new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(0.), FastMath.toRadians(270.),
255                         FastMath.toRadians(5.300), PositionAngleType.MEAN,
256                         FramesFactory.getEME2000(), date, mu);
257         final LofOffset upsideDown = new LofOffset(circ.getFrame(), LOFType.QSW, RotationOrder.XYX, -1., 2., 3.);
258         final LofOffsetPointing pointing = new LofOffsetPointing(circ.getFrame(), earthSpheric, upsideDown, Vector3D.PLUS_K);
259         // WHEN
260         final Vector3D targetPosition = pointing.getTargetPosition(circ, circ.getDate(), circ.getFrame());
261         // THEN
262         final Vector3D expectedPosition = pointing.getTargetPV(circ, circ.getDate(), circ.getFrame()).getPosition();
263         Assertions.assertEquals(expectedPosition, targetPosition);
264     }
265 
266     @Test
267     void testGetTargetPositionField() {
268         // GIVEN
269         final CircularOrbit circ =
270                 new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(0.), FastMath.toRadians(270.),
271                         FastMath.toRadians(5.300), PositionAngleType.MEAN,
272                         FramesFactory.getEME2000(), date, mu);
273         final LofOffset upsideDown = new LofOffset(circ.getFrame(), LOFType.QSW, RotationOrder.XYX, -1., 2., 3.);
274         final LofOffsetPointing pointing = new LofOffsetPointing(circ.getFrame(), earthSpheric, upsideDown, Vector3D.PLUS_K);
275         final FieldCircularOrbit<Complex> fieldOrbit = new FieldCircularOrbit<>(ComplexField.getInstance(), circ);
276         // WHEN
277         final FieldVector3D<Complex> targetPosition = pointing.getTargetPosition(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame());
278         // THEN
279         final FieldVector3D<Complex> expectedPosition = pointing.getTargetPV(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame()).getPosition();
280         Assertions.assertEquals(0., expectedPosition.subtract(targetPosition).getNorm().getReal(), 1.4e-9);
281     }
282 
283     @BeforeEach
284     public void setUp() {
285         try {
286 
287             Utils.setDataRoot("regular-data");
288 
289             // Computation date
290             date = new AbsoluteDate(new DateComponents(2008, 4, 7),
291                                     TimeComponents.H00,
292                                     TimeScalesFactory.getUTC());
293 
294             // Body mu
295             mu = 3.9860047e14;
296 
297             // Reference frame = ITRF
298             frameItrf = FramesFactory.getITRF(IERSConventions.IERS_2010, false);
299 
300             // Elliptic earth shape
301             earthSpheric =
302                 new OneAxisEllipsoid(6378136.460, 0., frameItrf);
303 
304         } catch (OrekitException oe) {
305             Assertions.fail(oe.getMessage());
306         }
307 
308     }
309 
310     @AfterEach
311     public void tearDown() {
312         date = null;
313         frameItrf = null;
314         earthSpheric = null;
315     }
316 }
317