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.FieldRotation;
22  import org.hipparchus.geometry.euclidean.threed.Rotation;
23  import org.hipparchus.util.Binary64;
24  import org.hipparchus.util.Binary64Field;
25  import org.junit.jupiter.api.Assertions;
26  import org.junit.jupiter.api.BeforeEach;
27  import org.junit.jupiter.api.Test;
28  import org.mockito.Mockito;
29  import org.orekit.Utils;
30  import org.orekit.annotation.DefaultDataContext;
31  import org.orekit.data.DataSource;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitMessages;
34  import org.orekit.files.ccsds.ndm.ParserBuilder;
35  import org.orekit.files.ccsds.ndm.adm.aem.Aem;
36  import org.orekit.files.ccsds.ndm.adm.aem.AemSatelliteEphemeris;
37  import org.orekit.frames.Frame;
38  import org.orekit.propagation.events.*;
39  import org.orekit.propagation.events.handlers.FieldResetDerivativesOnEvent;
40  import org.orekit.propagation.events.handlers.ResetDerivativesOnEvent;
41  import org.orekit.time.AbsoluteDate;
42  import org.orekit.time.FieldAbsoluteDate;
43  import org.orekit.time.TimeScalesFactory;
44  import org.orekit.utils.AngularCoordinates;
45  import org.orekit.utils.FieldPVCoordinatesProvider;
46  import org.orekit.utils.PVCoordinatesProvider;
47  import org.orekit.utils.ParameterDriver;
48  
49  import java.util.ArrayList;
50  import java.util.Collections;
51  import java.util.List;
52  import java.util.stream.Collectors;
53  import java.util.stream.Stream;
54  
55  public class AggregateBoundedAttitudeProviderTest {
56  
57      @BeforeEach
58      public void setUp() {
59          Utils.setDataRoot("regular-data:ccsds");
60      }
61  
62      @Test
63      void testEmptyList() {
64          try {
65              new AggregateBoundedAttitudeProvider(Collections.emptyList());
66          } catch (OrekitException oe) {
67              Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_ATTITUDE_PROVIDERS, oe.getSpecifier());
68          }
69      }
70  
71      @Test
72      @DefaultDataContext
73      void testAEM() {
74  
75          final String ex = "/ccsds/adm/aem/AEMExample10.txt";
76          final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
77          final Aem file = new ParserBuilder().buildAemParser().parseMessage(source);
78  
79          final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
80          final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
81  
82          // Verify dates
83          Assertions.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getStart()), 1.0e-10);
84          Assertions.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getStop()),  1.0e-10);
85          Assertions.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getSegments().get(0).getStart()), 1.0e-10);
86          Assertions.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getSegments().get(1).getStop()), 1.0e-10);
87  
88          // Verify computation with data in first segment
89          Attitude attitude = provider.getAttitude(null, new AbsoluteDate("1996-11-28T22:08:04.555", TimeScalesFactory.getUTC()), null);
90          Rotation rotation = attitude.getRotation();
91          Assertions.assertEquals(0.45652, rotation.getQ0(), 0.00001);
92          Assertions.assertEquals(-0.84532, rotation.getQ1(), 0.00001);
93          Assertions.assertEquals(0.26974, rotation.getQ2(), 0.00001);
94          Assertions.assertEquals(-0.06532, rotation.getQ3(), 0.00001);
95  
96      }
97  
98      @Test
99      @DefaultDataContext
100     void testFieldAEM() {
101         doTestFieldAEM(Binary64Field.getInstance());
102     }
103 
104     @DefaultDataContext
105     private <T extends CalculusFieldElement<T>> void doTestFieldAEM(final Field<T> field) {
106 
107         final String ex = "/ccsds/adm/aem/AEMExample10.txt";
108         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
109         final Aem file = new ParserBuilder().buildAemParser().parseMessage(source);
110 
111         final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
112         final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
113 
114         // Verify dates
115         Assertions.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getStart()), 1.0e-10);
116         Assertions.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getStop()),  1.0e-10);
117         Assertions.assertEquals(0.0, provider.getMinDate().durationFrom(ephemeris.getSegments().get(0).getStart()), 1.0e-10);
118         Assertions.assertEquals(0.0, provider.getMaxDate().durationFrom(ephemeris.getSegments().get(1).getStop()), 1.0e-10);
119 
120         // Verify computation with data in first segment
121         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field,
122                 new AbsoluteDate("1996-11-28T22:08:04.555", TimeScalesFactory.getUTC()));
123         FieldAttitude<T> attitude = provider.getAttitude(null, date, null);
124         FieldRotation<T> rotation = attitude.getRotation();
125         Assertions.assertEquals(0.45652, rotation.getQ0().getReal(), 0.00001);
126         Assertions.assertEquals(-0.84532, rotation.getQ1().getReal(), 0.00001);
127         Assertions.assertEquals(0.26974, rotation.getQ2().getReal(), 0.00001);
128         Assertions.assertEquals(-0.06532, rotation.getQ3().getReal(), 0.00001);
129 
130         // Verify getAttitudeRotation
131         FieldRotation<T> rotation2 = provider.getAttitudeRotation(null, date, null);
132         Assertions.assertEquals(0., Rotation.distance(rotation.toRotation(), rotation2.toRotation()));
133     }
134 
135     @Test
136     @DefaultDataContext
137     void testOutsideBounds() {
138 
139         final String ex = "/ccsds/adm/aem/AEMExample10.txt";
140         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
141         final Aem file = new ParserBuilder().withSimpleEOP(true).buildAemParser().parseMessage(source);
142 
143         final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
144         final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
145 
146         // before bound of first attitude provider
147         try {
148             provider.getAttitude(null, provider.getMinDate().shiftedBy(-60.0), null);
149         } catch (OrekitException oe) {
150             Assertions.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE, oe.getSpecifier());
151         }
152 
153         // after bound of last attitude provider
154         try {
155             provider.getAttitude(null, provider.getMaxDate().shiftedBy(60.0), null);
156         } catch (OrekitException oe) {
157             Assertions.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER, oe.getSpecifier());
158         }
159 
160     }
161 
162     @Test
163     @DefaultDataContext
164     void testFieldOutsideBounds() {
165         doTestFieldOutsideBounds(Binary64Field.getInstance());
166     }
167 
168     @DefaultDataContext
169     private <T extends CalculusFieldElement<T>> void doTestFieldOutsideBounds(final Field<T> field) {
170 
171         final String ex = "/ccsds/adm/aem/AEMExample10.txt";
172         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
173         final Aem file = new ParserBuilder().withSimpleEOP(true).buildAemParser().parseMessage(source);
174 
175         final AemSatelliteEphemeris ephemeris = file.getSatellites().get("1996-062A");
176         final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
177 
178         // before bound of first attitude provider
179         try {
180             provider.getAttitude(null, new FieldAbsoluteDate<>(provider.getMinDate(), field.getZero().subtract(60.0)), null);
181         } catch (OrekitException oe) {
182             Assertions.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE, oe.getSpecifier());
183         }
184 
185         // after bound of last attitude provider
186         try {
187             provider.getAttitude(null, new FieldAbsoluteDate<>(provider.getMinDate(), field.getZero().add(60.0)), null);
188         } catch (OrekitException oe) {
189             Assertions.assertEquals(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER, oe.getSpecifier());
190         }
191 
192     }
193 
194     @Test
195     void testGetEventDetectorsList() {
196         // GIVEN
197         final TestBoundedAttitudeProvider boundedAttitudeProvider = new TestBoundedAttitudeProvider();
198         final List<BoundedAttitudeProvider> boundedAttitudeProviderList = new ArrayList<>();
199         boundedAttitudeProviderList.add(boundedAttitudeProvider);
200         final AggregateBoundedAttitudeProvider aggregateBoundedAttitudeProvider = new AggregateBoundedAttitudeProvider(
201                 boundedAttitudeProviderList);
202         // WHEN & THEN
203         final int expectedSize = 0;
204         Assertions.assertEquals(expectedSize, aggregateBoundedAttitudeProvider.getEventDetectors(new ArrayList<>()).count());
205         Assertions.assertEquals(expectedSize, aggregateBoundedAttitudeProvider.getFieldEventDetectors(Binary64Field.getInstance(),
206                 new ArrayList<>()).count());
207     }
208 
209     @Test
210     void testGetParametersDrivers() {
211         // GIVEN
212         final TestBoundedAttitudeProvider boundedAttitudeProvider = new TestBoundedAttitudeProvider();
213         final List<BoundedAttitudeProvider> boundedAttitudeProviderList = new ArrayList<>();
214         boundedAttitudeProviderList.add(boundedAttitudeProvider);
215         final AggregateBoundedAttitudeProvider aggregateBoundedAttitudeProvider = new AggregateBoundedAttitudeProvider(
216                 boundedAttitudeProviderList);
217         // WHEN
218         final List<ParameterDriver> parameterDrivers = aggregateBoundedAttitudeProvider.getParametersDrivers();
219         // THEN
220         Assertions.assertEquals(boundedAttitudeProvider.getParametersDrivers().size(), parameterDrivers.size());
221     }
222 
223     @Test
224     void testGetEventDetectors() {
225         // GIVEN
226         final TestBoundedAttitudeProvider boundedAttitudeProvider = new TestBoundedAttitudeProvider();
227         final List<BoundedAttitudeProvider> boundedAttitudeProviderList = new ArrayList<>();
228         boundedAttitudeProviderList.add(boundedAttitudeProvider);
229         final AggregateBoundedAttitudeProvider aggregateBoundedAttitudeProvider = new AggregateBoundedAttitudeProvider(
230                 boundedAttitudeProviderList);
231         // WHEN
232         final Stream<EventDetector> eventDetectorStream = aggregateBoundedAttitudeProvider.getEventDetectors();
233         // THEN
234         final List<EventDetector> eventDetectorList = eventDetectorStream.collect(Collectors.toList());
235         Assertions.assertEquals(1, eventDetectorList.size());
236         Assertions.assertInstanceOf(DateDetector.class, eventDetectorList.get(0));
237         Assertions.assertInstanceOf(ResetDerivativesOnEvent.class, eventDetectorList.get(0).getHandler());
238         final DateDetector dateDetector = (DateDetector) eventDetectorList.get(0);
239         Assertions.assertEquals(boundedAttitudeProvider.getMinDate(), dateDetector.getDates().get(0).getDate());
240     }
241 
242     @Test
243     void testGetFieldEventDetectors() {
244         // GIVEN
245         final TestBoundedAttitudeProvider boundedAttitudeProvider = new TestBoundedAttitudeProvider();
246         final List<BoundedAttitudeProvider> boundedAttitudeProviderList = new ArrayList<>();
247         boundedAttitudeProviderList.add(boundedAttitudeProvider);
248         final AggregateBoundedAttitudeProvider aggregateBoundedAttitudeProvider = new AggregateBoundedAttitudeProvider(
249                 boundedAttitudeProviderList);
250         // WHEN
251         final Stream<FieldEventDetector<Binary64>> fieldEventDetectorStream = aggregateBoundedAttitudeProvider
252                 .getFieldEventDetectors(Binary64Field.getInstance());
253         // THEN
254         final List<FieldEventDetector<Binary64>> fieldEventDetectorList = fieldEventDetectorStream.collect(Collectors.toList());
255         final Stream<EventDetector> eventDetectorStream = aggregateBoundedAttitudeProvider.getEventDetectors();
256         final List<EventDetector> eventDetectorList = eventDetectorStream.collect(Collectors.toList());
257         Assertions.assertEquals(eventDetectorList.size(), fieldEventDetectorList.size());
258         Assertions.assertInstanceOf(FieldDateDetector.class, fieldEventDetectorList.get(0));
259         Assertions.assertInstanceOf(FieldResetDerivativesOnEvent.class, fieldEventDetectorList.get(0).getHandler());
260     }
261 
262     @Test
263     void testGetAttitudeRotation() {
264         // GIVEN
265         final TestBoundedAttitudeProvider boundedAttitudeProvider = new TestBoundedAttitudeProvider();
266         final List<BoundedAttitudeProvider> boundedAttitudeProviderList = new ArrayList<>();
267         boundedAttitudeProviderList.add(boundedAttitudeProvider);
268         final AggregateBoundedAttitudeProvider aggregateBoundedAttitudeProvider = new AggregateBoundedAttitudeProvider(
269                 boundedAttitudeProviderList);
270         final PVCoordinatesProvider mockPvCoordinatesProvider = Mockito.mock(PVCoordinatesProvider.class);
271         final Frame mockFrame = Mockito.mock(Frame.class);
272         final AbsoluteDate minDate = boundedAttitudeProvider.getMinDate();
273         // WHEN
274         final Rotation actualRotation = aggregateBoundedAttitudeProvider.getAttitudeRotation(mockPvCoordinatesProvider,
275                 minDate, mockFrame);
276         // THEN
277         final Rotation expectedRotation = aggregateBoundedAttitudeProvider.getAttitude(mockPvCoordinatesProvider,
278                 minDate, mockFrame).getRotation();
279         Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation));
280     }
281 
282     private static class TestBoundedAttitudeProvider implements BoundedAttitudeProvider {
283 
284         TestBoundedAttitudeProvider() {
285             // nothing to do
286         }
287 
288         @Override
289         public Attitude getAttitude(PVCoordinatesProvider pvProv, AbsoluteDate date, Frame frame) {
290             return new Attitude(date, frame, new AngularCoordinates());
291         }
292 
293         @Override
294         public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(FieldPVCoordinatesProvider<T> pvProv,
295                                                                                 FieldAbsoluteDate<T> date, Frame frame) {
296             return new FieldAttitude<>(date.getField(), new Attitude(date.toAbsoluteDate(), frame,
297                     new AngularCoordinates()));
298         }
299 
300         @Override
301         public AbsoluteDate getMinDate() {
302             return AbsoluteDate.ARBITRARY_EPOCH;
303         }
304 
305         @Override
306         public AbsoluteDate getMaxDate() {
307             return getMinDate().shiftedBy(100000.);
308         }
309 
310     }
311 
312 }