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.files.ccsds.ndm.adm.aem;
18  
19  import java.net.URISyntaxException;
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.function.Function;
23  
24  import org.hamcrest.MatcherAssert;
25  import org.hamcrest.Matchers;
26  import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
27  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
28  import org.hipparchus.geometry.euclidean.threed.Rotation;
29  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
30  import org.hipparchus.geometry.euclidean.threed.RotationOrder;
31  import org.hipparchus.geometry.euclidean.threed.Vector3D;
32  import org.hipparchus.util.Binary64;
33  import org.hipparchus.util.Binary64Field;
34  import org.hipparchus.util.FastMath;
35  import org.hipparchus.util.MathUtils;
36  import org.junit.jupiter.api.Assertions;
37  import org.junit.jupiter.api.BeforeEach;
38  import org.junit.jupiter.api.Test;
39  import org.orekit.Utils;
40  import org.orekit.attitudes.Attitude;
41  import org.orekit.attitudes.BoundedAttitudeProvider;
42  import org.orekit.attitudes.FieldAttitude;
43  import org.orekit.bodies.CelestialBodyFactory;
44  import org.orekit.data.DataContext;
45  import org.orekit.data.DataSource;
46  import org.orekit.errors.OrekitException;
47  import org.orekit.errors.OrekitMessages;
48  import org.orekit.files.ccsds.definitions.BodyFacade;
49  import org.orekit.files.ccsds.definitions.CcsdsFrameMapper;
50  import org.orekit.files.ccsds.definitions.CelestialBodyFrame;
51  import org.orekit.files.ccsds.definitions.FrameFacade;
52  import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
53  import org.orekit.files.ccsds.definitions.OrekitCcsdsFrameMapper;
54  import org.orekit.files.ccsds.definitions.SpacecraftBodyFrame;
55  import org.orekit.files.ccsds.definitions.TimeSystem;
56  import org.orekit.files.ccsds.ndm.ParserBuilder;
57  import org.orekit.files.ccsds.ndm.adm.AttitudeEndpoints;
58  import org.orekit.files.ccsds.ndm.adm.AttitudeType;
59  import org.orekit.files.ccsds.section.Segment;
60  import org.orekit.frames.Frame;
61  import org.orekit.frames.FramesFactory;
62  import org.orekit.frames.ITRFVersion;
63  import org.orekit.frames.Transform;
64  import org.orekit.orbits.CircularOrbit;
65  import org.orekit.orbits.FieldCircularOrbit;
66  import org.orekit.orbits.PositionAngleType;
67  import org.orekit.time.AbsoluteDate;
68  import org.orekit.time.FieldAbsoluteDate;
69  import org.orekit.time.TimeOffset;
70  import org.orekit.time.TimeScale;
71  import org.orekit.time.TimeScalesFactory;
72  import org.orekit.utils.AngularDerivativesFilter;
73  import org.orekit.utils.Constants;
74  import org.orekit.utils.IERSConventions;
75  import org.orekit.utils.TimeStampedAngularCoordinates;
76  
77  public class AEMParserTest {
78  
79      @BeforeEach
80      public void setUp()
81          throws Exception {
82          Utils.setDataRoot("regular-data");
83      }
84  
85      @Test
86      public void testParseAEM01() {
87          final String ex = "/ccsds/adm/aem/AEMExample01.txt";
88          final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
89          final Aem file = new ParserBuilder().buildAemParser().parseMessage(source);
90          final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
91          final Segment<AemMetadata, AemData> segment1 = file.getSegments().get(1);
92          final AbsoluteDate start = new AbsoluteDate("1996-11-28T22:08:02.5555", TimeScalesFactory.getUTC());
93          Assertions.assertEquals(0.0, start.durationFrom(file.getSatellites().get("1996-062A").getStart()), Double.MIN_VALUE);
94          final AbsoluteDate end = new AbsoluteDate("1996-12-28T21:23:00.5555", TimeScalesFactory.getUTC());
95          Assertions.assertEquals(0.0, end.durationFrom(file.getSatellites().get("1996-062A").getStop()), Double.MIN_VALUE);
96          Assertions.assertEquals("1996-062A", file.getSatellites().get("1996-062A").getId());
97          Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), Double.MIN_VALUE);
98          Assertions.assertEquals(new AbsoluteDate(2002, 11, 4, 17, 22, 31.0, TimeScalesFactory.getUTC()),
99                              file.getHeader().getCreationDate());
100         Assertions.assertEquals("NASA/JPL", file.getHeader().getOriginator());
101         Assertions.assertEquals("UTC",     segment0.getMetadata().getTimeSystem().name());
102         Assertions.assertEquals("MARS GLOBAL SURVEYOR", segment0.getMetadata().getObjectName());
103         Assertions.assertEquals("1996-062A",            segment0.getMetadata().getObjectID());
104         Assertions.assertEquals("MARS BARYCENTER",      segment0.getMetadata().getCenter().getName());
105         Assertions.assertEquals(1996,                   segment0.getMetadata().getLaunchYear());
106         Assertions.assertEquals(62,                     segment0.getMetadata().getLaunchNumber());
107         Assertions.assertEquals("A",                    segment0.getMetadata().getLaunchPiece());
108         Assertions.assertFalse(segment0.getMetadata().getHasCreatableBody());
109         Assertions.assertNull(segment0.getMetadata().getCenter().getBody());
110         Assertions.assertEquals(new AbsoluteDate(1996, 11, 28, 21, 29, new TimeOffset(7, TimeOffset.SECOND,
111                                                                                       255500, TimeOffset.MICROSECOND),
112                                                  TimeScalesFactory.getUTC()),
113                             segment0.getMetadata().getStartTime());
114         Assertions.assertEquals(new AbsoluteDate(1996, 11, 30, 1, 28, new TimeOffset(2, TimeOffset.SECOND,
115                                                                                      555500, TimeOffset.MICROSECOND),
116                                                  TimeScalesFactory.getUTC()),
117                             segment0.getMetadata().getStopTime());
118         Assertions.assertEquals(new AbsoluteDate(1996, 11, 28, 22, 8, new TimeOffset(2, TimeOffset.SECOND,
119                                                                                      555500, TimeOffset.MICROSECOND),
120                                                  TimeScalesFactory.getUTC()),
121                             segment0.getMetadata().getUseableStartTime());
122         Assertions.assertEquals(new AbsoluteDate(1996, 11, 30, 1, 18, new TimeOffset(2, TimeOffset.SECOND,
123                                                                                      555500, TimeOffset.MICROSECOND),
124                                                  TimeScalesFactory.getUTC()),
125                             segment0.getMetadata().getUseableStopTime());
126         Assertions.assertEquals("HERMITE", segment0.getMetadata().getInterpolationMethod());
127         Assertions.assertEquals(7,         segment0.getMetadata().getInterpolationDegree());
128         Assertions.assertFalse(segment0.getMetadata().isFirst());
129         Assertions.assertEquals("EME2000",       segment0.getMetadata().getEndpoints().getFrameA().getName());
130         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
131                             segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
132         Assertions.assertEquals("1",
133                             segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
134         Assertions.assertTrue(segment0.getMetadata().getEndpoints().isA2b());
135         Assertions.assertEquals(AttitudeType.QUATERNION, segment1.getMetadata().getAttitudeType());
136         Assertions.assertEquals(AngularDerivativesFilter.USE_R, segment0.getMetadata().getAttitudeType().getAngularDerivativesFilter());
137         verifyAngularCoordinates(new TimeStampedAngularCoordinates(new AbsoluteDate(1996, 11, 28, 21, 29,
138                                                                                     new TimeOffset(7, TimeOffset.SECOND,
139                                                                                                    255500, TimeOffset.MICROSECOND),
140                                                                                     TimeScalesFactory.getUTC()),
141                                                                    new Rotation(0.68427, 0.56748, 0.03146, 0.45689, false),
142                                                                    Vector3D.ZERO,
143                                                                    Vector3D.ZERO),
144                                  segment0.getData().getAngularCoordinates().get(0), 1.0e-5);
145         verifyAngularCoordinates(new TimeStampedAngularCoordinates(new AbsoluteDate(1996, 11, 28, 22, 8,
146                                                                                     new TimeOffset(3, TimeOffset.SECOND,
147                                                                                                    555500, TimeOffset.MICROSECOND),
148                                                                                     TimeScalesFactory.getUTC()),
149                                                                    new Rotation(0.74533, 0.42319, -0.45697, 0.23784, false),
150                                                                    Vector3D.ZERO,
151                                                                    Vector3D.ZERO),
152                                  segment0.getData().getAngularCoordinates().get(1), 1.0e-5);
153         verifyAngularCoordinates(new TimeStampedAngularCoordinates(new AbsoluteDate(1996, 11, 28, 22, 8,
154                                                                                     new TimeOffset(4, TimeOffset.SECOND,
155                                                                                                    555500, TimeOffset.MICROSECOND),
156                                                                                     TimeScalesFactory.getUTC()),
157                                                                    new Rotation(0.45652, -0.84532, 0.26974, -0.06532, false),
158                                                                    Vector3D.ZERO,
159                                                                    Vector3D.ZERO),
160                                  segment0.getData().getAngularCoordinates().get(2), 1.0e-5);
161         ArrayList<String> ephemeridesDataLinesComment = new ArrayList<>();
162         ephemeridesDataLinesComment.add("This file was produced by M.R. Somebody, MSOO NAV/JPL, 2002 OCT 04.");
163         ephemeridesDataLinesComment.add("It is to be used for attitude reconstruction only. The relative accuracy of these");
164         ephemeridesDataLinesComment.add("attitudes is 0.1 degrees per axis.");
165         Assertions.assertEquals(ephemeridesDataLinesComment, segment0.getMetadata().getComments());
166 
167         Assertions.assertEquals("UTC",                      segment1.getMetadata().getTimeSystem().name());
168         Assertions.assertEquals("MARS GLOBAL SURVEYOR",     segment1.getMetadata().getObjectName());
169         Assertions.assertEquals("1996-062A",                segment1.getMetadata().getObjectID());
170         Assertions.assertEquals("MARS BARYCENTER",          segment1.getMetadata().getCenter().getName());
171         Assertions.assertEquals(1996,                       segment1.getMetadata().getLaunchYear());
172         Assertions.assertEquals(62,                         segment1.getMetadata().getLaunchNumber());
173         Assertions.assertEquals("A",                        segment1.getMetadata().getLaunchPiece());
174         Assertions.assertFalse(segment1.getMetadata().getHasCreatableBody());
175         Assertions.assertNull(segment1.getMetadata().getCenter().getBody());
176         Assertions.assertEquals(new AbsoluteDate(1996, 12, 18, 12, 5, new TimeOffset(555500, TimeOffset.MICROSECOND), TimeScalesFactory.getUTC()),
177                             segment1.getMetadata().getStartTime());
178         Assertions.assertEquals(new AbsoluteDate(1996, 12, 28, 21, 28, new TimeOffset(555500, TimeOffset.MICROSECOND), TimeScalesFactory.getUTC()),
179                             segment1.getMetadata().getStopTime());
180         Assertions.assertEquals(new AbsoluteDate(1996, 12, 18, 12, 10, new TimeOffset(555500, TimeOffset.MICROSECOND), TimeScalesFactory.getUTC()),
181                             segment1.getMetadata().getUseableStartTime());
182         Assertions.assertEquals(new AbsoluteDate(1996, 12, 28, 21, 23, new TimeOffset(555500, TimeOffset.MICROSECOND), TimeScalesFactory.getUTC()),
183                             segment1.getMetadata().getUseableStopTime());
184         Assertions.assertFalse(segment1.getMetadata().isFirst());
185         Assertions.assertEquals("EME2000",       segment0.getMetadata().getEndpoints().getFrameA().getName());
186         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
187                             segment0.getMetadata().getEndpoints().getSpacecraftBodyFrame().asSpacecraftBodyFrame().getBaseEquipment());
188         Assertions.assertEquals("1",
189                             segment0.getMetadata().getEndpoints().getSpacecraftBodyFrame().asSpacecraftBodyFrame().getLabel());
190         Assertions.assertTrue(segment0.getMetadata().getEndpoints().isA2b());
191         Assertions.assertEquals(AttitudeType.QUATERNION, segment1.getMetadata().getAttitudeType());
192         Assertions.assertEquals(AngularDerivativesFilter.USE_R, segment0.getMetadata().getAttitudeType().getAngularDerivativesFilter());
193         verifyAngularCoordinates(new TimeStampedAngularCoordinates(new AbsoluteDate(1996, 12, 18, 12, 5, new TimeOffset(555500, TimeOffset.MICROSECOND),
194                                                                                     TimeScalesFactory.getUTC()),
195                                                                    new Rotation(0.72501, -0.64585, 0.018542, -0.23854, false),
196                                                                    Vector3D.ZERO,
197                                                                    Vector3D.ZERO),
198                                  segment1.getData().getAngularCoordinates().get(0), 1.0e-5);
199         verifyAngularCoordinates(new TimeStampedAngularCoordinates(new AbsoluteDate(1996, 12, 18, 12, 10, new TimeOffset(5, TimeOffset.SECOND,
200                                                                                                                          555500,
201                                                                                                                          TimeOffset.MICROSECOND),
202                                                                                     TimeScalesFactory.getUTC()),
203                                                                    new Rotation(-0.16767, 0.87451, -0.43475, 0.13458, false),
204                                                                    Vector3D.ZERO,
205                                                                    Vector3D.ZERO),
206                                  segment1.getData().getAngularCoordinates().get(1), 1.0e-5);
207         verifyAngularCoordinates(new TimeStampedAngularCoordinates(new AbsoluteDate(1996, 12, 18, 12, 10, new TimeOffset(10, TimeOffset.SECOND,
208                                                                                                                          555500, TimeOffset.MICROSECOND),
209                                                                                     TimeScalesFactory.getUTC()),
210                                                                    new Rotation(-0.71418, 0.03125, -0.65874, 0.23458, false),
211                                                                    Vector3D.ZERO,
212                                                                    Vector3D.ZERO),
213                                  segment1.getData().getAngularCoordinates().get(2), 1.0e-5);
214         ArrayList<String> ephemeridesDataLinesComment2 = new ArrayList<>();
215         ephemeridesDataLinesComment2.add("This block begins after trajectory correction maneuver TCM-3.");
216         Assertions.assertEquals(ephemeridesDataLinesComment2, segment1.getMetadata().getComments());
217     }
218 
219     @Test
220     public void testParseAEM02() {
221         final String name = "/ccsds/adm/aem/AEMExample02.txt";
222         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
223         AemParser parser = new ParserBuilder().
224                            withMissionReferenceDate(new AbsoluteDate("1996-12-17T00:00:00.000",
225                                                                      TimeScalesFactory.getUTC())).
226                            buildAemParser();
227 
228         final Aem file = parser.parse(source); // using generic API here
229         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
230         final List<String> headerComment = new ArrayList<>();
231         headerComment.add("comment");
232         Assertions.assertEquals(headerComment, file.getHeader().getComments());
233         final List<String> metadataComment = new ArrayList<>();
234         metadataComment.add("This file was produced by M.R. Somebody, MSOO NAV/JPL, 2002 OCT 04.");
235         metadataComment.add("It is to be used for attitude reconstruction only. The relative accuracy of these");
236         metadataComment.add("attitudes is 0.1 degrees per axis.");
237         Assertions.assertEquals(metadataComment, segment0.getMetadata().getComments());
238         Assertions.assertEquals("EME2000",       segment0.getMetadata().getEndpoints().getFrameA().getName());
239         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
240                             segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
241         Assertions.assertEquals("1",
242                             segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
243         List<AemSegment> blocks = file.getSegments();
244         Assertions.assertEquals(1, blocks.size());
245         Assertions.assertEquals(IERSConventions.IERS_2010, parser.getConventions());
246         Assertions.assertTrue(parser.isSimpleEOP());
247         Assertions.assertEquals(0.0, parser.getMissionReferenceDate().durationFrom(new AbsoluteDate(1996, 12, 17, 0, 0, 0.0,
248                                                                                                     TimeScalesFactory.getUTC())), 1.0e-5);
249         Assertions.assertEquals(DataContext.getDefault(), parser.getDataContext());
250         Assertions.assertEquals((new AbsoluteDate("1996-12-17T00:00:00.000",
251                                               TimeScalesFactory.getUTC())),
252                             parser.getMissionReferenceDate());
253     }
254 
255     @Test
256     public void testParseKvnAEM03() {
257         final String ex = "/ccsds/adm/aem/AEMExample03.txt";
258         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
259         validateAEM03(new ParserBuilder().buildAemParser().parseMessage(source));
260     }
261 
262     @Test
263     public void testParseXmlAEM03() {
264         final String ex = "/ccsds/adm/aem/AEMExample03.xml";
265         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
266         final AemParser parser  = new ParserBuilder().buildAemParser();
267         validateAEM03(parser.parse(source));
268     }
269 
270     private void validateAEM03(final Aem file) {
271 
272         final TimeScale utc = TimeScalesFactory.getUTC();
273         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-15);
274         Assertions.assertEquals(0,
275                             file.getHeader().getCreationDate().durationFrom(new AbsoluteDate("2008-071T17:09:49", utc)),
276                             1.0e-12);
277         Assertions.assertEquals("GSFC FDF", file.getHeader().getOriginator());
278         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
279         Assertions.assertEquals("ST5-224", segment0.getMetadata().getObjectName());
280         Assertions.assertEquals("2006224", segment0.getMetadata().getObjectID());
281         Assertions.assertEquals("J2000",   segment0.getMetadata().getEndpoints().getFrameA().getName());
282         Assertions.assertEquals("SC_BODY_1", segment0.getMetadata().getEndpoints().getFrameB().getName());
283         Assertions.assertTrue(segment0.getMetadata().getEndpoints().isA2b());
284         Assertions.assertEquals(TimeSystem.UTC, segment0.getMetadata().getTimeSystem());
285         Assertions.assertEquals(0,
286                             segment0.getMetadata().getStartTime().durationFrom(new AbsoluteDate("2006-090T05:00:00.071", utc)),
287                             1.0e-12);
288         Assertions.assertEquals(0,
289                             segment0.getMetadata().getUseableStartTime().durationFrom(new AbsoluteDate("2006-090T05:00:00.071", utc)),
290                             1.0e-12);
291         Assertions.assertEquals(0,
292                             segment0.getMetadata().getUseableStopTime().durationFrom(new AbsoluteDate("2006-090T05:00:00.946", utc)),
293                             1.0e-12);
294         Assertions.assertEquals(0,
295                             segment0.getMetadata().getStopTime().durationFrom(new AbsoluteDate("2006-090T05:00:00.946", utc)),
296                             1.0e-12);
297         Assertions.assertEquals(AttitudeType.SPIN, segment0.getMetadata().getAttitudeType());
298         Assertions.assertEquals(1, segment0.getData().getComments().size());
299         Assertions.assertEquals("Spin KF ground solution, SPINKF rates", segment0.getData().getComments().get(0));
300         Assertions.assertEquals(8, segment0.getData().getAngularCoordinates().size());
301         TimeStampedAngularCoordinates prev = null;
302         for (TimeStampedAngularCoordinates tac : segment0.getData().getAngularCoordinates()) {
303             if (prev != null) {
304                 double dt = tac.getDate().durationFrom(prev.getDate());
305                 double dR = Rotation.distance(tac.getRotation(), prev.getRotation());
306                 double meanRate = 0.5 * (prev.getRotationRate().getNorm() + tac.getRotationRate().getNorm());
307                 Assertions.assertEquals(dR, dt * meanRate, 1.3e-3);
308             }
309             prev = tac;
310         }
311 
312     }
313 
314     @Test
315     public void testParseAEM04() {
316         final TimeScale utc = TimeScalesFactory.getUTC();
317         final String ex = "/ccsds/adm/aem/AEMExample04.txt";
318         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
319         final AemParser parser  = new ParserBuilder().buildAemParser();
320         final Aem file = parser.parseMessage(source);
321         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-15);
322         Assertions.assertEquals(0,
323                             file.getHeader().getCreationDate().durationFrom(new AbsoluteDate("2021-04-13T08:41:42", utc)),
324                             1.0e-12);
325         Assertions.assertEquals("CS GROUP", file.getHeader().getOriginator());
326         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
327         Assertions.assertEquals("COPIHUE",   segment0.getMetadata().getObjectName());
328         Assertions.assertEquals("2100-017F", segment0.getMetadata().getObjectID());
329         Assertions.assertEquals(2100,        segment0.getMetadata().getLaunchYear());
330         Assertions.assertEquals(17,          segment0.getMetadata().getLaunchNumber());
331         Assertions.assertEquals("F",         segment0.getMetadata().getLaunchPiece());
332         Assertions.assertEquals("EME2000",   segment0.getMetadata().getEndpoints().getFrameA().getName());
333         Assertions.assertEquals("SC_BODY_1", segment0.getMetadata().getEndpoints().getFrameB().getName());
334         Assertions.assertTrue(segment0.getMetadata().getEndpoints().isA2b());
335         Assertions.assertEquals(TimeSystem.UTC, segment0.getMetadata().getTimeSystem());
336         Assertions.assertEquals(0,
337                             segment0.getMetadata().getStartTime().durationFrom(new AbsoluteDate("2021-12-31T00:00:00.000", utc)),
338                             1.0e-12);
339         Assertions.assertEquals(0,
340                             segment0.getMetadata().getUseableStartTime().durationFrom(new AbsoluteDate("2021-12-31T00:00:00.500", utc)),
341                             1.0e-12);
342         Assertions.assertEquals(0,
343                             segment0.getMetadata().getUseableStopTime().durationFrom(new AbsoluteDate("2021-12-31T00:00:05.500", utc)),
344                             1.0e-12);
345         Assertions.assertEquals(0,
346                             segment0.getMetadata().getStopTime().durationFrom(new AbsoluteDate("2021-12-31T00:00:06.000", utc)),
347                             1.0e-12);
348         Assertions.assertEquals(AttitudeType.QUATERNION_DERIVATIVE, segment0.getMetadata().getAttitudeType());
349         Assertions.assertEquals("HERMITE", segment0.getMetadata().getInterpolationMethod());
350         Assertions.assertEquals(3, segment0.getMetadata().getInterpolationDegree());
351         Assertions.assertEquals(1, segment0.getData().getComments().size());
352         Assertions.assertEquals(13, segment0.getData().getAngularCoordinates().size());
353         TimeStampedAngularCoordinates prev = null;
354         for (TimeStampedAngularCoordinates tac : segment0.getData().getAngularCoordinates()) {
355             if (prev != null) {
356                 double dt = tac.getDate().durationFrom(prev.getDate());
357                 double dR = Rotation.distance(tac.getRotation(), prev.getRotation());
358                 double meanRate = 0.5 * (prev.getRotationRate().getNorm() + tac.getRotationRate().getNorm());
359                 Assertions.assertEquals(dR, dt * meanRate, 1.5e-6);
360             }
361             prev = tac;
362         }
363 
364     }
365 
366     @Test
367     public void testParseAEM05() {
368         final String ex = "/ccsds/adm/aem/AEMExample05.txt";
369         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
370         final AemParser parser  = new ParserBuilder().buildAemParser();
371         final Aem file = parser.parseMessage(source);
372         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
373         final List<String> headerComment = new ArrayList<>();
374         headerComment.add("comment");
375         Assertions.assertEquals(headerComment, file.getHeader().getComments());
376         final List<String> metadataComment = new ArrayList<>();
377         metadataComment.add("This file was produced by M.R. Somebody, MSOO NAV/JPL, 2002 OCT 04.");
378         metadataComment.add("It is to be used for attitude reconstruction only. The relative accuracy of these");
379         metadataComment.add("attitudes is 0.1 degrees per axis.");
380         Assertions.assertEquals(metadataComment,        segment0.getMetadata().getComments());
381         Assertions.assertEquals("UTC",                  segment0.getMetadata().getTimeSystem().name());
382         Assertions.assertEquals("MARS GLOBAL SURVEYOR", segment0.getMetadata().getObjectName());
383         Assertions.assertEquals("1996-062A",            segment0.getMetadata().getObjectID());
384         Assertions.assertEquals("MARS BARYCENTER",      segment0.getMetadata().getCenter().getName());
385         Assertions.assertEquals(1996,                   segment0.getMetadata().getLaunchYear());
386         Assertions.assertEquals(62,                     segment0.getMetadata().getLaunchNumber());
387         Assertions.assertEquals("A",                    segment0.getMetadata().getLaunchPiece());
388         Assertions.assertEquals(RotationOrder.ZXY,      segment0.getMetadata().getEulerRotSeq());
389         Assertions.assertTrue(segment0.getMetadata().rateFrameIsA());
390         Assertions.assertFalse(segment0.getMetadata().getHasCreatableBody());
391         Assertions.assertNull(segment0.getMetadata().getCenter().getBody());
392 
393         // Reference values
394         final AbsoluteDate refDate = new AbsoluteDate(1996, 11, 28, 21, 29, 7.2555, TimeScalesFactory.getUTC());
395 
396         // Computed angular coordinates
397         final TimeStampedAngularCoordinates ac = segment0.getData().getAngularCoordinates().get(0);
398         final FieldRotation<UnivariateDerivative1> r = ac.toUnivariateDerivative1Rotation();
399         final UnivariateDerivative1[] angles = r.getAngles(segment0.getMetadata().getEulerRotSeq(),
400                                                            RotationConvention.FRAME_TRANSFORM);
401         Assertions.assertEquals(0.0,     refDate.durationFrom(ac.getDate()),                 1.0e-5);
402         Assertions.assertEquals(0.0,     ac.getRotationAcceleration().getNorm(),             1.0e-5);
403         Assertions.assertEquals(-26.78,  FastMath.toDegrees(angles[0].getValue()),           1.0e-2);
404         Assertions.assertEquals(46.26,   FastMath.toDegrees(angles[1].getValue()),           1.0e-2);
405         Assertions.assertEquals(144.10,  FastMath.toDegrees(angles[2].getValue()),           1.0e-2);
406         Assertions.assertEquals(0.10450, FastMath.toDegrees(angles[0].getFirstDerivative()), 1.0e-5);
407         Assertions.assertEquals(0.03214, FastMath.toDegrees(angles[1].getFirstDerivative()), 1.0e-5);
408         Assertions.assertEquals(0.02156, FastMath.toDegrees(angles[2].getFirstDerivative()), 1.0e-5);
409     }
410 
411     @Test
412     public void testParseAEM06a() {
413         final String ex = "/ccsds/adm/aem/AEMExample06a.txt";
414         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
415         final AemParser parser  = new ParserBuilder().buildAemParser();
416 
417         final Aem file = parser.parseMessage(source);
418         final TimeStampedAngularCoordinates ac = file.getSegments().get(0).getAngularCoordinates().get(7);
419         final Vector3D lastSpin = ac.getRotation().applyInverseTo(Vector3D.PLUS_K);
420         Assertions.assertEquals(268.45119, FastMath.toDegrees(MathUtils.normalizeAngle(lastSpin.getAlpha(), FastMath.PI)), 1.0e-5);
421         Assertions.assertEquals(68.317275, FastMath.toDegrees(lastSpin.getDelta()), 1.0e-5);
422     }
423 
424     @Test
425     public void testParseAEM06b() {
426         final String ex = "/ccsds/adm/aem/AEMExample06b.txt";
427         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
428         final AemParser parser  = new ParserBuilder().buildAemParser();
429 
430         final Aem file = parser.parseMessage(source);
431         final TimeStampedAngularCoordinates ac = file.getSegments().get(0).getAngularCoordinates().get(7);
432         final Vector3D lastSpin = ac.getRotation().applyInverseTo(Vector3D.PLUS_K);
433         Assertions.assertEquals(268.45119, FastMath.toDegrees(MathUtils.normalizeAngle(lastSpin.getAlpha(), FastMath.PI)), 1.0e-5);
434         Assertions.assertEquals(68.317275, FastMath.toDegrees(lastSpin.getDelta()), 1.0e-5);
435     }
436 
437     @Test
438     public void testParseAEM07() {
439         final String ex = "/ccsds/adm/aem/AEMExample07.txt";
440         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
441         final AemParser parser  = new ParserBuilder().buildAemParser();
442         final Aem file = parser.parseMessage(source);
443         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
444         final List<String> headerComment = new ArrayList<>();
445         headerComment.add("comment");
446         Assertions.assertEquals(headerComment, file.getHeader().getComments());
447         final List<String> metadataComment = new ArrayList<>();
448         metadataComment.add("This file was produced by M.R. Somebody, MSOO NAV/JPL, 2002 OCT 04.");
449         metadataComment.add("It is to be used for attitude reconstruction only. The relative accuracy of these");
450         metadataComment.add("attitudes is 0.1 degrees per axis.");
451         Assertions.assertEquals(metadataComment,        segment0.getMetadata().getComments());
452         Assertions.assertEquals(TimeSystem.UTC,         segment0.getMetadata().getTimeSystem());
453         Assertions.assertEquals("MARS GLOBAL SURVEYOR", segment0.getMetadata().getObjectName());
454         Assertions.assertEquals("1996-062A",            segment0.getMetadata().getObjectID());
455         Assertions.assertEquals("MARS BARYCENTER",      segment0.getMetadata().getCenter().getName());
456         Assertions.assertEquals(1996,                   segment0.getMetadata().getLaunchYear());
457         Assertions.assertEquals(62,                     segment0.getMetadata().getLaunchNumber());
458         Assertions.assertEquals("A",                    segment0.getMetadata().getLaunchPiece());
459         Assertions.assertFalse(segment0.getMetadata().getHasCreatableBody());
460         Assertions.assertNull(segment0.getMetadata().getCenter().getBody());
461         Assertions.assertEquals(CelestialBodyFrame.EME2000, segment0.getMetadata().getEndpoints().getFrameA().asCelestialBodyFrame());
462         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY, segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
463         Assertions.assertEquals("1", segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
464         Assertions.assertTrue(segment0.getMetadata().getEndpoints().isA2b());
465         Assertions.assertEquals(new AbsoluteDate("2002-12-18T12:00:00.331", TimeScalesFactory.getUTC()),
466                             segment0.getMetadata().getStartTime());
467         Assertions.assertEquals(new AbsoluteDate("2002-12-18T12:00:00.331", TimeScalesFactory.getUTC()),
468                             segment0.getMetadata().getUseableStartTime());
469         Assertions.assertEquals(new AbsoluteDate("2002-12-18T12:02:00.331", TimeScalesFactory.getUTC()),
470                             segment0.getMetadata().getUseableStopTime());
471         Assertions.assertEquals(new AbsoluteDate("2002-12-18T12:02:00.331", TimeScalesFactory.getUTC()),
472                             segment0.getMetadata().getStopTime());
473         Assertions.assertEquals(AttitudeType.QUATERNION, segment0.getMetadata().getAttitudeType());
474         Assertions.assertFalse(segment0.getMetadata().isFirst());
475         Assertions.assertEquals("HERMITE", segment0.getMetadata().getInterpolationMethod());
476         Assertions.assertEquals(7, segment0.getMetadata().getInterpolationDegree());
477 
478         final AbsoluteDate refDate = new AbsoluteDate("2002-12-18T12:00:00.331", TimeScalesFactory.getUTC());
479 
480         Assertions.assertEquals(3, segment0.getData().getAngularCoordinates().size());
481         final TimeStampedAngularCoordinates ac0 = segment0.getData().getAngularCoordinates().get(0);
482         Assertions.assertEquals(0.0, ac0.getDate().durationFrom(refDate), 1.0e-5);
483         Assertions.assertEquals(0.0,
484                             Rotation.distance(new Rotation(0.68427, 0.56748, 0.03146, 0.45689, true),
485                                               ac0.getRotation()),
486                             1.0e-10);
487         final TimeStampedAngularCoordinates ac1 = segment0.getData().getAngularCoordinates().get(1);
488         Assertions.assertEquals(60.0, ac1.getDate().durationFrom(refDate), 1.0e-5);
489         Assertions.assertEquals(0.0,
490                             Rotation.distance(new Rotation(0.74533, 0.42319, -0.45697, 0.23784, true),
491                                               ac1.getRotation()),
492                             1.0e-10);
493         final TimeStampedAngularCoordinates ac2 = segment0.getData().getAngularCoordinates().get(2);
494         Assertions.assertEquals(120.0, ac2.getDate().durationFrom(refDate), 1.0e-5);
495         Assertions.assertEquals(0.0,
496                             Rotation.distance(new Rotation(0.45652, -0.84532, 0.26974, -0.06532, true),
497                                               ac2.getRotation()),
498                             1.0e-10);
499 
500     }
501 
502     @Test
503     public void testParseAEM11() {
504         final String ex = "/ccsds/adm/aem/AEMExample11.xml";
505         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
506         final AemParser parser  = new ParserBuilder().buildAemParser();
507         final Aem file = parser.parseMessage(source);
508         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
509         final List<String> headerComment = new ArrayList<>();
510         headerComment.add("This example shows an AEM with a rotation");
511         Assertions.assertEquals(headerComment, file.getHeader().getComments());
512         final List<String> metadataComment = new ArrayList<>();
513         metadataComment.add("The relative accuracy of these");
514         metadataComment.add("attitudes is 0.1 degrees per axis.");
515         Assertions.assertEquals(metadataComment, segment0.getMetadata().getComments());
516         Assertions.assertEquals(TimeSystem.UTC,  segment0.getMetadata().getTimeSystem());
517         Assertions.assertEquals("FICTITIOUS",    segment0.getMetadata().getObjectName());
518         Assertions.assertEquals("2020-224A",     segment0.getMetadata().getObjectID());
519         Assertions.assertEquals("EARTH",         segment0.getMetadata().getCenter().getName());
520         Assertions.assertEquals(CelestialBodyFrame.J2000, segment0.getMetadata().getEndpoints().getFrameA().asCelestialBodyFrame());
521         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY, segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
522         Assertions.assertEquals("1", segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
523         Assertions.assertTrue(segment0.getMetadata().getEndpoints().isA2b());
524         Assertions.assertEquals(new AbsoluteDate("2020-090T05:00:00.071", TimeScalesFactory.getUTC()),
525                             segment0.getMetadata().getStartTime());
526         Assertions.assertEquals(new AbsoluteDate("2020-090T05:00:00.946", TimeScalesFactory.getUTC()),
527                             segment0.getMetadata().getStopTime());
528         Assertions.assertEquals(AttitudeType.EULER_ANGLE_DERIVATIVE, segment0.getMetadata().getAttitudeType());
529 
530         final AbsoluteDate refDate = new AbsoluteDate("2020-090T05:00:00.071", TimeScalesFactory.getUTC());
531 
532         Assertions.assertEquals(2, segment0.getData().getAngularCoordinates().size());
533         final TimeStampedAngularCoordinates ac0 = segment0.getData().getAngularCoordinates().get(0);
534         Assertions.assertEquals(0.0, ac0.getDate().durationFrom(refDate), 1.0e-5);
535         Assertions.assertEquals(0.0,
536                             Rotation.distance(new Rotation(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM,
537                                                            FastMath.toRadians(45),
538                                                            FastMath.toRadians(0.9),
539                                                            FastMath.toRadians(15)),
540                                               ac0.getRotation()),
541                             1.0e-10);
542         final TimeStampedAngularCoordinates ac1 = segment0.getData().getAngularCoordinates().get(1);
543         Assertions.assertEquals(0.875, ac1.getDate().durationFrom(refDate), 1.0e-5);
544         Assertions.assertEquals(0.0,
545                             Rotation.distance(new Rotation(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM,
546                                                            FastMath.toRadians(50),
547                                                            FastMath.toRadians(1.9),
548                                                            FastMath.toRadians(1.5)),
549                                               ac1.getRotation()),
550                             1.0e-10);
551 
552     }
553 
554     @Test
555     public void testParseAEM13() {
556         final TimeScale tai = TimeScalesFactory.getTAI();
557         final String ex = "/ccsds/adm/aem/AEMExample13.xml";
558         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
559         final AemParser parser  = new ParserBuilder().buildAemParser();
560         final Aem file = parser.parseMessage(source);
561         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
562         Assertions.assertEquals(TimeSystem.TAI,          segment0.getMetadata().getTimeSystem());
563         Assertions.assertEquals("OREKIT SAT",            segment0.getMetadata().getObjectName());
564         Assertions.assertEquals("2020-012A",             segment0.getMetadata().getObjectID());
565         Assertions.assertEquals(OrbitRelativeFrame.LVLH, segment0.getMetadata().getEndpoints().getFrameA().asOrbitRelativeFrame());
566         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.IMU_FRAME, segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
567         Assertions.assertEquals("1", segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
568         Assertions.assertFalse(segment0.getMetadata().getEndpoints().isA2b());
569         Assertions.assertEquals(new AbsoluteDate("2021-04-15T13:31:20.000", tai), segment0.getMetadata().getStartTime());
570         Assertions.assertEquals(new AbsoluteDate("2021-04-15T13:31:23.000", tai), segment0.getMetadata().getStopTime());
571         Assertions.assertEquals(AttitudeType.QUATERNION_DERIVATIVE, segment0.getMetadata().getAttitudeType());
572         Assertions.assertTrue(segment0.getMetadata().isFirst());
573 
574         final AbsoluteDate refDate = new AbsoluteDate("2021-04-15T13:31:20.000", tai);
575 
576         Assertions.assertEquals(7, segment0.getData().getAngularCoordinates().size());
577         final TimeStampedAngularCoordinates ac0 = segment0.getData().getAngularCoordinates().get(0);
578         Assertions.assertEquals(0.0, ac0.getDate().durationFrom(refDate), 1.0e-5);
579         Assertions.assertEquals(0.0,
580                             Rotation.distance(new Rotation(-0.488615, -0.402157,  0.581628,  0.511111, true),
581                                               ac0.getRotation()),
582                             1.0e-10);
583         final TimeStampedAngularCoordinates ac1 = segment0.getData().getAngularCoordinates().get(1);
584         Assertions.assertEquals(0.5, ac1.getDate().durationFrom(refDate), 1.0e-5);
585         Assertions.assertEquals(0.0,
586                             Rotation.distance(new Rotation(-0.488765, -0.402027,  0.581486,  0.511231, true),
587                                               ac1.getRotation()),
588                             1.0e-10);
589         final TimeStampedAngularCoordinates ac2 = segment0.getData().getAngularCoordinates().get(2);
590         Assertions.assertEquals(1.0, ac2.getDate().durationFrom(refDate), 1.0e-5);
591         Assertions.assertEquals(0.0,
592                             Rotation.distance(new Rotation(-0.488916, -0.401898,  0.581344,  0.511350, true),
593                                               ac2.getRotation()),
594                             1.0e-10);
595 
596         final CircularOrbit o = new CircularOrbit(6992992, -5e-04, 1.2e-03,
597                                                   FastMath.toRadians(97.83), FastMath.toRadians(80.95),
598                                                   FastMath.toRadians(179.86), PositionAngleType.MEAN,
599                                                   FramesFactory.getEME2000(),
600                                                   new AbsoluteDate("2021-04-15T13:31:22.000", tai),
601                                                   Constants.EIGEN5C_EARTH_MU);
602         final FieldCircularOrbit<Binary64> fo =
603                         new FieldCircularOrbit<>(new Binary64(o.getA()),
604                                                  new Binary64(o.getCircularEx()), new Binary64(o.getCircularEy()),
605                                                  new Binary64(o.getI()), new Binary64(o.getRightAscensionOfAscendingNode()),
606                                                  new Binary64(o.getAlphaM()), PositionAngleType.MEAN,
607                                                  o.getFrame(), new FieldAbsoluteDate<>(Binary64Field.getInstance(), o.getDate()),
608                                                  new Binary64(o.getMu()));
609         final AemSatelliteEphemeris ephemeris = file.getSatellites().get("2020-012A");
610         final BoundedAttitudeProvider provider = ephemeris.getAttitudeProvider();
611         Attitude                  a = provider.getAttitude(o, o.getDate(), o.getFrame());
612         FieldAttitude<Binary64> fa = provider.getAttitude(fo, fo.getDate(), fo.getFrame());
613         Assertions.assertEquals(a.getRotation().getQ0(), fa.getRotation().getQ0().getReal(), 0.00001);
614         Assertions.assertEquals(a.getRotation().getQ1(), fa.getRotation().getQ1().getReal(), 0.00001);
615         Assertions.assertEquals(a.getRotation().getQ2(), fa.getRotation().getQ2().getReal(), 0.00001);
616         Assertions.assertEquals(a.getRotation().getQ3(), fa.getRotation().getQ3().getReal(), 0.00001);
617 
618     }
619 
620     @Test
621     public void testParseAEM14() {
622         final TimeScale tai = TimeScalesFactory.getTAI();
623         final String ex = "/ccsds/adm/aem/AEMExample14.txt";
624         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
625         final AemParser parser  = new ParserBuilder().buildAemParser();
626         final Aem file = parser.parseMessage(source);
627 
628         final Segment<AemMetadata, AemData> segment0 = file.getSegments().get(0);
629         Assertions.assertEquals(TimeSystem.TAI,          segment0.getMetadata().getTimeSystem());
630         Assertions.assertEquals("MMS",                   segment0.getMetadata().getObjectName());
631         Assertions.assertEquals("2015-011A",             segment0.getMetadata().getObjectID());
632         Assertions.assertEquals("EME2000",               segment0.getMetadata().getEndpoints().getFrameA().getName());
633         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY, segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
634         Assertions.assertEquals("1", segment0.getMetadata().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
635         Assertions.assertEquals(new AbsoluteDate("2023-01-01T00:00:00.000", tai), segment0.getMetadata().getStartTime());
636         Assertions.assertEquals(new AbsoluteDate("2023-01-01T00:04:30.000", tai), segment0.getMetadata().getStopTime());
637         Assertions.assertEquals(AttitudeType.EULER_ANGLE_DERIVATIVE, segment0.getMetadata().getAttitudeType());
638         Assertions.assertEquals(RotationOrder.ZXZ, segment0.getMetadata().getEulerRotSeq());
639         Assertions.assertEquals(10, segment0.getData().getAngularCoordinates().size());
640 
641         Assertions.assertEquals(AttitudeType.SPIN_NUTATION_MOMENTUM,
642                                 file.getSegments().get(1).getMetadata().getAttitudeType());
643         Assertions.assertEquals(AttitudeType.QUATERNION,
644                                 file.getSegments().get(2).getMetadata().getAttitudeType());
645         Assertions.assertEquals(AttitudeType.QUATERNION_ANGVEL,
646                                 file.getSegments().get(3).getMetadata().getAttitudeType());
647         Assertions.assertEquals(AttitudeType.EULER_ANGLE_ANGVEL,
648                                 file.getSegments().get(4).getMetadata().getAttitudeType());
649 
650     }
651 
652     @Test
653     public void testWrongNDMType() {
654         final String name = "/ccsds/odm/opm/OPMExample1.txt";
655         try {
656             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
657             new ParserBuilder().buildAemParser().parseMessage(source);
658             Assertions.fail("an exception should have been thrown");
659         } catch (OrekitException oe) {
660             Assertions.assertEquals(OrekitMessages.UNSUPPORTED_FILE_FORMAT, oe.getSpecifier());
661             Assertions.assertEquals(name, oe.getParts()[0]);
662         }
663     }
664 
665     @Test
666     public void testNonExistentFile() throws URISyntaxException {
667         final String realName = getClass().getResource("/ccsds/adm/aem/AEMExample01.txt").toURI().getPath();
668         final String wrongName = realName + "xxxxx";
669         final DataSource source =  new DataSource(wrongName, () -> getClass().getResourceAsStream(wrongName));
670         try {
671             new ParserBuilder().buildAemParser().parseMessage(source);
672             Assertions.fail("an exception should have been thrown");
673         } catch (OrekitException oe) {
674             Assertions.assertEquals(OrekitMessages.UNABLE_TO_FIND_FILE, oe.getSpecifier());
675             Assertions.assertEquals(wrongName, oe.getParts()[0]);
676         }
677     }
678 
679     @Test
680     public void testMissingAttitudeType() {
681         try {
682             final String name = "/ccsds/adm/aem/AEM-missing-attitude-type.txt";
683             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
684             new ParserBuilder().buildAemParser().parseMessage(source);
685             Assertions.fail("an exception should have been thrown");
686         } catch (OrekitException oe) {
687             Assertions.assertEquals(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, oe.getSpecifier());
688             Assertions.assertEquals(AemMetadataKey.ATTITUDE_TYPE.name(), oe.getParts()[0]);
689         }
690     }
691 
692     @Test
693     public void testInconsistentDirection() {
694         try {
695             final String name = "/ccsds/adm/aem/AEM-inconsistent-direction.txt";
696             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
697             new ParserBuilder().buildAemParser().parseMessage(source);
698             Assertions.fail("an exception should have been thrown");
699         } catch (OrekitException oe) {
700             Assertions.assertEquals(OrekitMessages.CCSDS_KEYWORD_NOT_ALLOWED_IN_VERSION, oe.getSpecifier());
701             Assertions.assertEquals(AemMetadataKey.ATTITUDE_DIR, oe.getParts()[0]);
702             Assertions.assertEquals(2.0, (Double) oe.getParts()[1], 1.0e-15);
703         }
704     }
705 
706     @Test
707     public void testInconsistentQuaternionType() {
708         try {
709             final String name = "/ccsds/adm/aem/AEM-inconsistent-quaternion-type.txt";
710             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
711             new ParserBuilder().buildAemParser().parseMessage(source);
712             Assertions.fail("an exception should have been thrown");
713         } catch (OrekitException oe) {
714             Assertions.assertEquals(OrekitMessages.CCSDS_KEYWORD_NOT_ALLOWED_IN_VERSION, oe.getSpecifier());
715             Assertions.assertEquals(AemMetadataKey.QUATERNION_TYPE, oe.getParts()[0]);
716             Assertions.assertEquals(2.0, (Double) oe.getParts()[1], 1.0e-15);
717         }
718     }
719 
720     @Test
721     public void testLowerCaseValue() {
722         //setup
723         String file = "/ccsds/adm/aem/aemLowerCaseValue.aem";
724         final DataSource source = new DataSource(file, () -> getClass().getResourceAsStream(file));
725 
726         //action
727         Aem actual = new ParserBuilder().buildAemParser().parseMessage(source);
728 
729         //verify
730         Assertions.assertEquals(
731                 CelestialBodyFactory.getEarth(),
732                 actual.getSegments().get(0).getMetadata().getCenter().getBody());
733     }
734 
735     @Test
736     public void testWrongFile() {
737         final String name = "/ccsds/odm/opm/OPMExample1.txt";
738         try {
739             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
740             new ParserBuilder().buildAemParser().parseMessage(source);
741             Assertions.fail("an exception should have been thrown");
742         } catch (OrekitException oe) {
743             Assertions.assertEquals(OrekitMessages.UNSUPPORTED_FILE_FORMAT, oe.getSpecifier());
744             Assertions.assertEquals(name, oe.getParts()[0]);
745         }
746     }
747 
748     @Test
749     public void testWrongKeyword() {
750         // simple test for AEM file, contains a wrong keyword in the metadata.
751         final String name = "/ccsds/adm/aem/AEM-wrong-keyword.txt";
752         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
753         try {
754             new ParserBuilder().buildAemParser().parseMessage(source);
755             Assertions.fail("an exception should have been thrown");
756         } catch (OrekitException oe) {
757             Assertions.assertEquals(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, oe.getSpecifier());
758             Assertions.assertEquals(24, ((Integer) oe.getParts()[0]).intValue());
759             Assertions.assertTrue(((String) oe.getParts()[2]).startsWith("WRONG_KEYWORD"));
760         }
761     }
762 
763     @Test
764     public void testEphemerisNumberFormatErrorType() {
765         final String name = "/ccsds/adm/aem/AEM-ephemeris-number-format-error.txt";
766         try {
767             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
768             new ParserBuilder().buildAemParser().parseMessage(source);
769             Assertions.fail("an exception should have been thrown");
770         } catch (OrekitException oe) {
771             Assertions.assertEquals(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, oe.getSpecifier());
772             Assertions.assertEquals(28, oe.getParts()[0]);
773             Assertions.assertEquals(name, oe.getParts()[1]);
774             Assertions.assertEquals("1996-11-28T22:08:03.5555 0.42319   this-is-not-a-number  0.23784   0.74533", oe.getParts()[2]);
775         }
776     }
777 
778     @Test
779     public void testKeywordWithinEphemeris() {
780         // simple test for AEM file, contains p/v entries and other mandatory data.
781         final String name = "/ccsds/adm/aem/AEM-keyword-within-ephemeris.txt";
782         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
783         try {
784             new ParserBuilder().buildAemParser().parseMessage(source);
785             Assertions.fail("an exception should have been thrown");
786         } catch (OrekitException oe) {
787             Assertions.assertEquals(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, oe.getSpecifier());
788             Assertions.assertEquals(29, ((Integer) oe.getParts()[0]).intValue());
789             Assertions.assertTrue(((String) oe.getParts()[2]).startsWith("USER_DEFINED_TEST_KEY"));
790         }
791     }
792 
793     @Test
794     public void testWrongRotationSequence() {
795         // simple test for AEM file, contains a wrong keyword in the metadata.
796         final String name = "/ccsds/adm/aem/AEM-inconsistent-rotation-sequence.txt";
797         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
798         try {
799             new ParserBuilder().buildAemParser().parseMessage(source);
800             Assertions.fail("an exception should have been thrown");
801         } catch (OrekitException oe) {
802             Assertions.assertEquals(OrekitMessages.CCSDS_INVALID_ROTATION_SEQUENCE, oe.getSpecifier());
803             Assertions.assertEquals("7051995", oe.getParts()[0]);
804             Assertions.assertEquals(22, ((Integer) oe.getParts()[1]).intValue());
805             Assertions.assertEquals(name, oe.getParts()[2]);
806         }
807     }
808 
809     @Test
810     public void testSpuriousMetaDataSection() {
811         final String name = "/ccsds/adm/aem/spurious-metadata.txt";
812         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
813         try {
814             new ParserBuilder().buildAemParser().parseMessage(source);
815             Assertions.fail("an exception should have been thrown");
816         } catch (OrekitException oe) {
817             Assertions.assertEquals(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, oe.getSpecifier());
818             Assertions.assertEquals(26, ((Integer) oe.getParts()[0]).intValue());
819             Assertions.assertEquals("META", oe.getParts()[2]);
820         }
821     }
822 
823     @Test
824     public void testMissingConvention() {
825         final String ex = "/ccsds/adm/aem/AEMExample01.txt";
826         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
827         final Aem file = new ParserBuilder().buildAemParser().parseMessage(source);
828         try {
829             file.getConventions();
830         } catch (OrekitException oe) {
831             Assertions.assertEquals(OrekitMessages.CCSDS_UNKNOWN_CONVENTIONS, oe.getSpecifier());
832         }
833     }
834 
835     private void verifyAngularCoordinates(final TimeStampedAngularCoordinates expected,
836                                           final TimeStampedAngularCoordinates actual,
837                                           final double threshold) {
838         // Verify date
839         Assertions.assertEquals(0.0, expected.getDate().durationFrom(actual.getDate()), threshold);
840 
841         // Verify Angular elements
842         Assertions.assertEquals(expected.getRotation().getQ0(), actual.getRotation().getQ0(), threshold);
843         Assertions.assertEquals(expected.getRotation().getQ1(), actual.getRotation().getQ1(), threshold);
844         Assertions.assertEquals(expected.getRotation().getQ2(), actual.getRotation().getQ2(), threshold);
845         Assertions.assertEquals(expected.getRotation().getQ3(), actual.getRotation().getQ3(), threshold);
846 
847         Assertions.assertEquals(0.0, expected.getRotationRate().distance(actual.getRotationRate()), threshold);
848     }
849 
850     /**
851      * Check if the parser enters the correct interpolation degree
852      * (the parsed one or the default if there is none)
853      */
854     @Test
855     public void testDefaultInterpolationDegree() {
856 
857         final String name = "/ccsds/adm/aem/AEMExample01.txt";
858         ParserBuilder builder = new ParserBuilder();
859 
860         final DataSource source1 = new DataSource(name, () -> getClass().getResourceAsStream(name));
861         final Aem file = builder.buildAemParser().parseMessage(source1);
862         Assertions.assertEquals(7, file.getSegments().get(0).getMetadata().getInterpolationDegree());
863         Assertions.assertEquals(1, file.getSegments().get(1).getMetadata().getInterpolationDegree());
864 
865         final DataSource source2 = new DataSource(name, () -> getClass().getResourceAsStream(name));
866         final Aem file2 = builder.withDefaultInterpolationDegree(5).buildAemParser().parseMessage(source2);
867         Assertions.assertEquals(7, file2.getSegments().get(0).getMetadata().getInterpolationDegree());
868         Assertions.assertEquals(5, file2.getSegments().get(1).getMetadata().getInterpolationDegree());
869     }
870 
871     @Test
872     public void testIssue739() {
873         final String ex = "/ccsds/adm/aem/AEMExample08.txt";
874         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
875         final Aem file = new ParserBuilder().buildAemParser().parseMessage(source);
876         final AemSegment segment0 = file.getSegments().get(0);
877         Assertions.assertEquals(CelestialBodyFrame.GTOD, segment0.getMetadata().getEndpoints().getFrameB().asCelestialBodyFrame());
878 
879         final BoundedAttitudeProvider provider = segment0.getAttitudeProvider();
880         Attitude attitude = provider.getAttitude(null, new AbsoluteDate("1996-11-28T22:08:03.555", TimeScalesFactory.getUTC()), null);
881         Rotation rotation = attitude.getRotation();
882         Assertions.assertEquals(0.42319,  rotation.getQ1(), 0.0001);
883         Assertions.assertEquals(-0.45697, rotation.getQ2(), 0.0001);
884         Assertions.assertEquals(0.23784,  rotation.getQ3(), 0.0001);
885         Assertions.assertEquals(0.74533,  rotation.getQ0(), 0.0001);
886         Assertions.assertEquals(0.0, provider.getMinDate().durationFrom(segment0.getMetadata().getStart()), 0.0001);
887         Assertions.assertEquals(0.0, provider.getMaxDate().durationFrom(segment0.getMetadata().getStop()), 0.0001);
888 
889     }
890 
891     @Test
892     public void testIssue739_2() {
893         final String ex = "/ccsds/adm/aem/AEMExample09.txt";
894         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
895         final Aem file = new ParserBuilder().buildAemParser().parseMessage(source);
896         final AemSegment segment0 = file.getSegments().get(0);
897         Assertions.assertEquals(FramesFactory.getITRF(ITRFVersion.ITRF_1993, IERSConventions.IERS_2010, true),
898                             segment0.getMetadata().getEndpoints().getFrameA().asFrame());
899 
900         final BoundedAttitudeProvider provider = segment0.getAttitudeProvider();
901         Attitude attitude = provider.getAttitude(null, new AbsoluteDate("1996-11-28T22:08:03.555", TimeScalesFactory.getUTC()), null);
902         Rotation rotation = attitude.getRotation();
903         Assertions.assertEquals(0.42319,  rotation.getQ1(), 0.0001);
904         Assertions.assertEquals(-0.45697, rotation.getQ2(), 0.0001);
905         Assertions.assertEquals(0.23784,  rotation.getQ3(), 0.0001);
906         Assertions.assertEquals(0.74533,  rotation.getQ0(), 0.0001);
907         Assertions.assertEquals(0.0, provider.getMinDate().durationFrom(segment0.getMetadata().getStart()), 0.0001);
908         Assertions.assertEquals(0.0, provider.getMaxDate().durationFrom(segment0.getMetadata().getStop()), 0.0001);
909 
910     }
911 
912     /** Unit tests for parsing an AEM with a custom frame mapper. */
913     @Test
914     public void testFrameMapper() {
915         // setup
916         TimeScale tai = TimeScalesFactory.getTAI();
917         AbsoluteDate expectedEpoch = new AbsoluteDate("2023-01-01T00:00:00.0000", tai);
918         Frame parent = FramesFactory.getEME2000();
919         Frame itrf93 = FramesFactory.getITRF(ITRFVersion.ITRF_1993, IERSConventions.IERS_2010, true);
920         Frame myJ2000 = new Frame(parent, Transform.IDENTITY, "MyJ2000");
921         Frame scBodyFrame = new Frame(parent, new Transform(expectedEpoch, new Rotation(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM,
922                 Math.PI / 4, Math.PI / 2, Math.PI /3)), "SC_BODY_1");
923         CcsdsFrameMapper mapper = new CcsdsFrameMapper() {
924             @Override
925             public Frame buildCcsdsFrame(FrameFacade orientation, AbsoluteDate epoch) {
926                 if ("SC_BODY_1".equals(orientation.getName()) && null == epoch) {
927                     return scBodyFrame;
928                 } else if (("J2000".equals(orientation.getName()) || "EME2000".equals(orientation.getName()))
929                         && null == epoch) {
930                     return myJ2000;
931                 } else if ("ITRF1993".equals(orientation.getName()) && null == epoch) {
932                     return itrf93;
933                 } else {
934                     throw new IllegalArgumentException(orientation.getName() + " " + epoch);
935                 }
936             }
937 
938             @Override
939             public Frame buildCcsdsFrame(BodyFacade center,
940                                          FrameFacade orientation,
941                                          AbsoluteDate epoch) {
942                 if ("EARTH".equals(center.getName()) &&
943                         "SC_BODY_1".equals(orientation.getName()) &&
944                         null == epoch) {
945                     return scBodyFrame;
946                 } else if ("EARTH".equals(center.getName()) && null == epoch &&
947                         ("EME2000".equals(orientation.getName()) || "J2000".equals(orientation.getName()))) {
948                         return myJ2000;
949                 } else if ("EARTH".equals(center.getName()) && "ITRF1993".equals(orientation.getName()) &&
950                         null == epoch) {
951                     return itrf93;
952                 } else {
953                     throw new IllegalArgumentException(
954                             center + " " + orientation + " " + epoch);
955                 }
956             }
957 
958         };
959 
960         // Test 3 types of attitude specifications: quaternion, Euler, and spin
961         final String quaternionExample = "/ccsds/adm/aem/AEMExample04.txt";
962         final String eulerExample = "/ccsds/adm/aem/AEMExample12.txt";
963         final String spinExample = "/ccsds/adm/aem/AEMExample06b.txt";
964         final DataSource quaternionSource = new DataSource(quaternionExample, () -> getClass().getResourceAsStream(quaternionExample));
965         final DataSource eulerSource = new DataSource(eulerExample, () -> getClass().getResourceAsStream(eulerExample));
966         final DataSource spinSource = new DataSource(spinExample, () -> getClass().getResourceAsStream(spinExample));
967 
968         // action
969         final AemParser parser = new ParserBuilder().withFrameMapper(mapper).buildAemParser();
970         final Aem qAem = parser.parseMessage(quaternionSource);
971         final Aem eAem = parser.parseMessage(eulerSource);
972         final Aem sAem = parser.parseMessage(spinSource);
973 
974         // verify
975         // only one segment per Aem
976         final AemSegment qAemSegment = qAem.getSegments().get(0);
977         final AemMetadata qAemMetadata = qAemSegment.getMetadata();
978         final AttitudeEndpoints qEndpoints = qAemMetadata.getEndpoints();
979         final BodyFacade qCenter = qAemMetadata.getCenter();
980         final AemSegment eAemSegment = eAem.getSegments().get(0);
981         final AemMetadata eAemMetadata = eAemSegment.getMetadata();
982         final AttitudeEndpoints eEndpoints = eAemMetadata.getEndpoints();
983         final BodyFacade eCenter = eAemMetadata.getCenter();
984         final AemSegment sAemSegment = sAem.getSegments().get(0);
985         final AemMetadata sAemMetadata = sAemSegment.getMetadata();
986         final AttitudeEndpoints sEndpoints = sAemMetadata.getEndpoints();
987         final BodyFacade sCenter = sAemMetadata.getCenter();
988 
989         // getReferenceFrame() returns a Frame, not FrameFacade from the endpoints object
990         MatcherAssert.assertThat(qAemSegment.getReferenceFrame(), Matchers.sameInstance(myJ2000));
991         MatcherAssert.assertThat(
992                 qAemSegment.getMetadata().getEndpoints().getExternal(),
993                 Matchers.sameInstance(myJ2000));
994         MatcherAssert.assertThat(
995                 qAemSegment.getMetadata().getFrameMapper(),
996                 Matchers.sameInstance(mapper));
997         // The frameA and frameB and therefore ExternalFrame and SpacecraftBodyFrame are all stored as FrameFacades
998         MatcherAssert.assertThat(
999                 mapper.buildCcsdsFrame(qCenter, qEndpoints.getFrameA(), null),
1000                 Matchers.sameInstance(myJ2000));
1001         MatcherAssert.assertThat(
1002                 mapper.buildCcsdsFrame(qCenter, qEndpoints.getFrameB(), null),
1003                 Matchers.sameInstance(scBodyFrame));
1004 
1005         MatcherAssert.assertThat(eAemSegment.getReferenceFrame(), Matchers.sameInstance(itrf93));
1006         MatcherAssert.assertThat(
1007                 eAemSegment.getMetadata().getEndpoints().getExternal(),
1008                 Matchers.sameInstance(itrf93));
1009         MatcherAssert.assertThat(
1010                 mapper.buildCcsdsFrame(eCenter, eEndpoints.getFrameA(), null),
1011                 Matchers.sameInstance(itrf93));
1012         MatcherAssert.assertThat(
1013                 mapper.buildCcsdsFrame(eCenter, eEndpoints.getFrameB(), null),
1014                 Matchers.sameInstance(scBodyFrame));
1015 
1016         MatcherAssert.assertThat(sAemSegment.getReferenceFrame(), Matchers.sameInstance(myJ2000));
1017         MatcherAssert.assertThat(
1018                 sAemSegment.getMetadata().getEndpoints().getExternal(),
1019                 Matchers.sameInstance(myJ2000));
1020         MatcherAssert.assertThat(
1021                 mapper.buildCcsdsFrame(sCenter, sEndpoints.getFrameA(), null),
1022                 Matchers.sameInstance(myJ2000));
1023         MatcherAssert.assertThat(
1024                 mapper.buildCcsdsFrame(sCenter, sEndpoints.getFrameB(), null),
1025                 Matchers.sameInstance(scBodyFrame));
1026     }
1027 
1028     /** Test deprecated constructor. Can be removed in 14.0. */
1029     @Test
1030     @Deprecated
1031     public void testDeprecatedConstructor() {
1032         // action
1033         AemParser actual = new AemParser(
1034                 null, true, null, null, 0, null, new Function[0]);
1035 
1036         // verify
1037         MatcherAssert.assertThat(actual.getFrameMapper(),
1038                 Matchers.is(new OrekitCcsdsFrameMapper()));
1039     }
1040 
1041 }