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.geometry.euclidean.threed.Rotation;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.hipparchus.util.Binary64;
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.annotation.DefaultDataContext;
32  import org.orekit.bodies.OneAxisEllipsoid;
33  import org.orekit.errors.OrekitException;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.FramesFactory;
36  import org.orekit.orbits.CircularOrbit;
37  import org.orekit.orbits.FieldOrbit;
38  import org.orekit.orbits.Orbit;
39  import org.orekit.orbits.PositionAngleType;
40  import org.orekit.propagation.FieldSpacecraftState;
41  import org.orekit.propagation.Propagator;
42  import org.orekit.propagation.SpacecraftState;
43  import org.orekit.propagation.analytical.KeplerianPropagator;
44  import org.orekit.propagation.sampling.OrekitFixedStepHandler;
45  import org.orekit.time.AbsoluteDate;
46  import org.orekit.time.DateComponents;
47  import org.orekit.time.FieldAbsoluteDate;
48  import org.orekit.time.TimeComponents;
49  import org.orekit.time.TimeScalesFactory;
50  import org.orekit.utils.AngularDerivativesFilter;
51  import org.orekit.utils.FieldPVCoordinates;
52  import org.orekit.utils.IERSConventions;
53  import org.orekit.utils.PVCoordinates;
54  import org.orekit.utils.TimeStampedAngularCoordinates;
55  import org.orekit.utils.TimeStampedFieldPVCoordinates;
56  import org.orekit.utils.TimeStampedPVCoordinates;
57  
58  import java.util.ArrayList;
59  import java.util.List;
60  
61  public class TabulatedProviderTest {
62  
63      // Computation date
64      private AbsoluteDate date;
65  
66      // Reference frame = ITRF
67      private Frame itrf;
68  
69      // Satellite position
70      CircularOrbit circOrbit;
71  
72      // Earth shape
73      OneAxisEllipsoid earthShape;
74  
75      @Test
76      @DefaultDataContext
77      void testDifferentFrames() {
78          double             samplingRate      = 10.0;
79          int                n                 = 8;
80          AttitudeProvider   referenceProvider = new NadirPointing(circOrbit.getFrame(), earthShape);
81          List<TimeStampedAngularCoordinates> sample = createSample(samplingRate, referenceProvider);
82          TabulatedProvider  provider          = new TabulatedProvider(circOrbit.getFrame(), sample, n,
83                                                                       AngularDerivativesFilter.USE_R);
84          Attitude attE = provider.getAttitude((date, frame) -> new TimeStampedPVCoordinates(date, PVCoordinates.ZERO),
85                                               date,
86                                               circOrbit.getFrame());
87          Assertions.assertEquals(circOrbit.getFrame().getName(), attE.getReferenceFrame().getName());
88          Frame gcrf = FramesFactory.getGCRF();
89          Attitude attG = provider.getAttitude((date, frame) -> new TimeStampedPVCoordinates(date, PVCoordinates.ZERO),
90                                               date,
91                                               gcrf);
92          Assertions.assertEquals(gcrf.getName(), attG.getReferenceFrame().getName());
93  
94          Assertions.assertEquals(1.12e-7,
95                              Rotation.distance(attE.getRotation(), attG.getRotation()), 1.0e-9);
96          Assertions.assertEquals(circOrbit.getFrame().getTransformTo(gcrf, date).getRotation().getAngle(),
97                              Rotation.distance(attE.getRotation(), attG.getRotation()),
98                              1.0e-14);
99  
100         FieldAttitude<Binary64> attG64 =
101                         provider.getAttitude((date, frame) -> new TimeStampedFieldPVCoordinates<>(date,
102                                                                         FieldPVCoordinates.getZero(Binary64Field.getInstance())),
103                                              new FieldAbsoluteDate<>(Binary64Field.getInstance(), date),
104                                              gcrf);
105         Assertions.assertEquals(gcrf.getName(), attG64.getReferenceFrame().getName());
106 
107     }
108 
109     @Test
110     void testWithoutRate() {
111         double             samplingRate      = 10.0;
112         double             checkingRate      = 1.0;
113         int                n                 = 8;
114         AttitudeProvider   referenceProvider = new NadirPointing(circOrbit.getFrame(), earthShape);
115         List<TimeStampedAngularCoordinates> sample = createSample(samplingRate, referenceProvider);
116         final double       margin            = samplingRate * n / 2;
117         final AbsoluteDate start             = sample.get(0).getDate().shiftedBy(margin);
118         final AbsoluteDate end               = sample.get(sample.size() - 1).getDate().shiftedBy(-margin);
119         TabulatedProvider  provider          = new TabulatedProvider(circOrbit.getFrame(), sample, n,
120                                                                      AngularDerivativesFilter.USE_R);
121         Assertions.assertEquals(0.0, checkError(start, end, checkingRate, referenceProvider, provider), 2.2e-14);
122     }
123 
124     @Test
125     void testWithRate() {
126         double             samplingRate      = 10.0;
127         double             checkingRate      = 1.0;
128         int                n                 = 8;
129         AttitudeProvider   referenceProvider = new NadirPointing(circOrbit.getFrame(), earthShape);
130         List<TimeStampedAngularCoordinates> sample = createSample(samplingRate, referenceProvider);
131         final double       margin            = samplingRate * n / 2;
132         final AbsoluteDate start             = sample.get(0).getDate().shiftedBy(margin);
133         final AbsoluteDate end               = sample.get(sample.size() - 1).getDate().shiftedBy(-margin);
134         TabulatedProvider  provider          = new TabulatedProvider(circOrbit.getFrame(), sample, n,
135                                                                      AngularDerivativesFilter.USE_RR);
136         Assertions.assertEquals(0.0, checkError(start, end, checkingRate, referenceProvider, provider), 1.3e-11);
137     }
138 
139     @Test
140     void testWithAcceleration() {
141         double             samplingRate      = 10.0;
142         double             checkingRate      = 1.0;
143         int                n                 = 8;
144         AttitudeProvider   referenceProvider = new NadirPointing(circOrbit.getFrame(), earthShape);
145         List<TimeStampedAngularCoordinates> sample = createSample(samplingRate, referenceProvider);
146         final double       margin            = samplingRate * n / 2;
147         final AbsoluteDate start             = sample.get(0).getDate().shiftedBy(margin);
148         final AbsoluteDate end               = sample.get(sample.size() - 1).getDate().shiftedBy(-margin);
149         TabulatedProvider  provider          = new TabulatedProvider(circOrbit.getFrame(), sample, n,
150                                                                      AngularDerivativesFilter.USE_RRA);
151         Assertions.assertEquals(0.0, checkError(start, end, checkingRate, referenceProvider, provider), 4.3e-9);
152         checkField(Binary64Field.getInstance(), provider, circOrbit, circOrbit.getDate(), circOrbit.getFrame());
153     }
154 
155     private List<TimeStampedAngularCoordinates> createSample(double samplingRate, AttitudeProvider referenceProvider) {
156 
157         // reference propagator, using a yaw compensation law
158         final KeplerianPropagator referencePropagator = new KeplerianPropagator(circOrbit);
159         referencePropagator.setAttitudeProvider(referenceProvider);
160 
161         // create sample
162         final List<TimeStampedAngularCoordinates> sample = new ArrayList<>();
163         referencePropagator.setStepHandler(samplingRate, currentState -> sample.add(currentState.getAttitude().getOrientation()));
164         referencePropagator.propagate(circOrbit.getDate().shiftedBy(2 * circOrbit.getKeplerianPeriod()));
165 
166         return sample;
167 
168     }
169 
170     private double checkError(final AbsoluteDate start, AbsoluteDate end, double checkingRate,
171                               final AttitudeProvider referenceProvider, TabulatedProvider provider) {
172 
173         // prepare an interpolating provider, using only internal steps
174         // (i.e. ignoring interpolation near boundaries)
175         Propagator interpolatingPropagator = new KeplerianPropagator(circOrbit.shiftedBy(start.durationFrom(circOrbit.getDate())));
176         interpolatingPropagator.setAttitudeProvider(provider);
177 
178         // compute interpolation error on the internal steps .
179         final double[] error = new double[1];
180         interpolatingPropagator.setStepHandler(checkingRate, new OrekitFixedStepHandler() {
181 
182             public void init(SpacecraftState s0, AbsoluteDate t, double step) {
183                 error[0] = 0.0;
184             }
185 
186             public void handleStep(SpacecraftState currentState) {
187                 Attitude interpolated = currentState.getAttitude();
188                 Attitude reference    = referenceProvider.getAttitude(currentState.getOrbit(),
189                                                                       currentState.getDate(),
190                                                                       currentState.getFrame());
191                 double localError = Rotation.distance(interpolated.getRotation(), reference.getRotation());
192                 error[0] = FastMath.max(error[0], localError);
193             }
194 
195         });
196 
197         interpolatingPropagator.propagate(end);
198 
199         return error[0];
200 
201     }
202 
203     private <T extends CalculusFieldElement<T>> void checkField(final Field<T> field, final AttitudeProvider provider,
204                                                             final Orbit orbit, final AbsoluteDate date,
205                                                             final Frame frame) {
206         Attitude attitudeD = provider.getAttitude(orbit, date, frame);
207         final FieldOrbit<T> orbitF = new FieldSpacecraftState<>(field, new SpacecraftState(orbit)).getOrbit();
208         final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, date);
209         FieldAttitude<T> attitudeF = provider.getAttitude(orbitF, dateF, frame);
210         Assertions.assertEquals(0.0, Rotation.distance(attitudeD.getRotation(), attitudeF.getRotation().toRotation()), 1.0e-15);
211         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getSpin(), attitudeF.getSpin().toVector3D()), 1.0e-15);
212         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getRotationAcceleration(), attitudeF.getRotationAcceleration().toVector3D()), 1.0e-15);
213     }
214 
215     @BeforeEach
216     @DefaultDataContext
217     public void setUp() {
218         try {
219             Utils.setDataRoot("regular-data");
220 
221             // Computation date
222             date = new AbsoluteDate(new DateComponents(2008, 4, 7),
223                                     TimeComponents.H00,
224                                     TimeScalesFactory.getUTC());
225 
226             // Body mu
227             final double mu = 3.9860047e14;
228 
229             // Reference frame = ITRF
230             itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
231 
232             //  Satellite position
233             circOrbit =
234                 new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(50.), FastMath.toRadians(270.),
235                                        FastMath.toRadians(5.300), PositionAngleType.MEAN,
236                                        FramesFactory.getEME2000(), date, mu);
237 
238             // Elliptic earth shape
239             earthShape =
240                 new OneAxisEllipsoid(6378136.460, 1 / 298.257222101, itrf);
241 
242         } catch (OrekitException oe) {
243             Assertions.fail(oe.getMessage());
244         }
245 
246     }
247 
248     @AfterEach
249     public void tearDown() {
250         date = null;
251         itrf = null;
252         circOrbit = null;
253         earthShape = null;
254     }
255 
256 }
257