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.apm;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.CharArrayWriter;
21  import java.io.IOException;
22  import java.net.URISyntaxException;
23  import java.nio.charset.StandardCharsets;
24  import java.util.ArrayList;
25  import java.util.function.Function;
26  
27  import org.hamcrest.MatcherAssert;
28  import org.hamcrest.Matchers;
29  import org.hipparchus.geometry.euclidean.threed.Rotation;
30  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
31  import org.hipparchus.geometry.euclidean.threed.RotationOrder;
32  import org.hipparchus.geometry.euclidean.threed.Vector3D;
33  import org.hipparchus.util.FastMath;
34  import org.junit.jupiter.api.Assertions;
35  import org.junit.jupiter.api.BeforeEach;
36  import org.junit.jupiter.api.Test;
37  import org.orekit.Utils;
38  import org.orekit.attitudes.Attitude;
39  import org.orekit.bodies.CelestialBodyFactory;
40  import org.orekit.data.DataContext;
41  import org.orekit.data.DataSource;
42  import org.orekit.errors.OrekitException;
43  import org.orekit.errors.OrekitMessages;
44  import org.orekit.files.ccsds.definitions.BodyFacade;
45  import org.orekit.files.ccsds.definitions.CcsdsFrameMapper;
46  import org.orekit.files.ccsds.definitions.CelestialBodyFrame;
47  import org.orekit.files.ccsds.definitions.FrameFacade;
48  import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
49  import org.orekit.files.ccsds.definitions.OrekitCcsdsFrameMapper;
50  import org.orekit.files.ccsds.definitions.SpacecraftBodyFrame;
51  import org.orekit.files.ccsds.definitions.SpacecraftBodyFrame.BaseEquipment;
52  import org.orekit.files.ccsds.ndm.ParserBuilder;
53  import org.orekit.files.ccsds.ndm.WriterBuilder;
54  import org.orekit.files.ccsds.ndm.adm.AdmMetadata;
55  import org.orekit.files.ccsds.ndm.adm.AttitudeEndpoints;
56  import org.orekit.files.ccsds.section.Segment;
57  import org.orekit.files.ccsds.utils.generation.Generator;
58  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
59  import org.orekit.frames.Frame;
60  import org.orekit.frames.FramesFactory;
61  import org.orekit.frames.Transform;
62  import org.orekit.time.AbsoluteDate;
63  import org.orekit.time.TimeOffset;
64  import org.orekit.time.TimeScale;
65  import org.orekit.time.TimeScalesFactory;
66  import org.orekit.utils.Constants;
67  import org.orekit.utils.IERSConventions;
68  import org.orekit.utils.PVCoordinates;
69  import org.orekit.utils.PVCoordinatesProvider;
70  import org.orekit.utils.TimeStampedPVCoordinates;
71  
72  public class APMParserTest {
73  
74      private static final double QUATERNION_PRECISION = 1e-5;
75      private static final double ANGLE_PRECISION = 1e-4;
76      private static final double SPACECRAFT_PRECISION = 0.1;
77      private static final double MANEUVER_PRECISION = 1.0e-2;
78  
79      @BeforeEach
80      public void setUp() {
81          Utils.setDataRoot("regular-data");
82      }
83  
84      @Test
85      public void testParseAPM1() {
86  
87          // File
88          final String ex = "/ccsds/adm/apm/APMExample01.txt";
89  
90          // Initialize the parser
91          final ApmParser parser = new ParserBuilder().
92                                   withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
93                                                                             TimeScalesFactory.getUTC())).
94                                   buildApmParser();
95  
96          final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
97  
98          // Generated APM file
99          final Apm file = parser.parseMessage(source);
100 
101         // Verify general data
102         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
103         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
104 
105         // Check Header Block
106         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
107         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 19, 23, 57,
108                                              TimeScalesFactory.getUTC()),
109                             file.getHeader().getCreationDate());
110         Assertions.assertEquals("GSFC", file.getHeader().getOriginator());
111 
112         // Check Metadata Block
113         Assertions.assertEquals("TRMM",       file.getMetadata().getObjectName());
114         Assertions.assertEquals("1997-009A",  file.getMetadata().getObjectID());
115         Assertions.assertEquals(1997,         file.getMetadata().getLaunchYear());
116         Assertions.assertEquals(9,            file.getMetadata().getLaunchNumber());
117         Assertions.assertEquals("A",          file.getMetadata().getLaunchPiece());
118         Assertions.assertEquals("EARTH",      file.getMetadata().getCenter().getName());
119         Assertions.assertTrue(file.getMetadata().getHasCreatableBody());
120         Assertions.assertEquals(CelestialBodyFactory.getEarth(), file.getMetadata().getCenter().getBody());
121         Assertions.assertEquals("UTC",        file.getMetadata().getTimeSystem().name());
122 
123         // Check data block
124         Assertions.assertFalse(file.getData().hasManeuvers());
125         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
126                             file.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
127         Assertions.assertEquals("1", file.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
128         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, file.getData().getQuaternionBlock().getEndpoints().getFrameB().asCelestialBodyFrame());
129         Assertions.assertTrue(file.getData().getQuaternionBlock().getEndpoints().isA2b());
130         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND),
131                                              TimeScalesFactory.getUTC()),
132                             file.getData().getEpoch());
133         Assertions.assertEquals(0.25678, file.getData().getQuaternionBlock().getQuaternion().getQ0(),    QUATERNION_PRECISION);
134         Assertions.assertEquals(0.00005, file.getData().getQuaternionBlock().getQuaternion().getQ1(),    QUATERNION_PRECISION);
135         Assertions.assertEquals(0.87543, file.getData().getQuaternionBlock().getQuaternion().getQ2(),    QUATERNION_PRECISION);
136         Assertions.assertEquals(0.40949, file.getData().getQuaternionBlock().getQuaternion().getQ3(),    QUATERNION_PRECISION);
137         Assertions.assertFalse(file.getData().getQuaternionBlock().hasRates());
138         Assertions.assertTrue(Double.isNaN(file.getData().getQuaternionBlock().getQuaternionDot().getQ1()));
139         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND),
140                                              TimeScalesFactory.getUTC()),
141                             file.getAttitude(null, null).getDate());
142         Assertions.assertEquals(0.0, file.getAttitude(null, null).getSpin().getNorm(), 1.0e-15);
143 
144         Attitude attitude = file.getAttitude(null, null);
145         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND), TimeScalesFactory.getUTC()),
146                             attitude.getDate());
147         Assertions.assertEquals("ITRF-1997/CIO/2010-based ITRF simple EOP", attitude.getReferenceFrame().getName());
148         Assertions.assertEquals(2 * FastMath.atan(FastMath.sqrt(0.00005 * 0.00005 + 0.87543 * 0.87543 + 0.40949 * 0.40949) / 0.25678),
149                             attitude.getRotation().getAngle(), 1.0e-15);
150         Assertions.assertEquals(0, attitude.getSpin().getNorm(), 1.0e-15);
151 
152     }
153 
154     @Test
155     public void testParseAPM2KVN() {
156         final ApmParser parser = new ParserBuilder().
157                                  withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
158                                                                            TimeScalesFactory.getUTC())).
159                                  buildApmParser();
160         final String name = "/ccsds/adm/apm/APMExample02.txt";
161         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
162         validateAPM2(parser.parseMessage(source));
163     }
164 
165     @Test
166     public void testParseAPM2XML() {
167         final ApmParser parser = new ParserBuilder().
168                         withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
169                                                                   TimeScalesFactory.getUTC())).
170                         buildApmParser();
171         final String name = "/ccsds/adm/apm/APMExample02.xml";
172         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
173         validateAPM2(parser.parseMessage(source));
174     }
175 
176     @Test
177     public void testWriteApm2() throws IOException {
178         final ApmParser parser = new ParserBuilder().
179                         withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
180                                                                   TimeScalesFactory.getUTC())).
181                         buildApmParser();
182         final String name = "/ccsds/adm/apm/APMExample02.xml";
183         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
184         final Apm original = parser.parseMessage(source);
185 
186         // write the parsed file back to a characters array
187         final CharArrayWriter caw = new CharArrayWriter();
188         final Generator generator = new KvnGenerator(caw, ApmWriter.KVN_PADDING_WIDTH, "dummy",
189                                                      Constants.JULIAN_DAY, 60);
190         new WriterBuilder().buildApmWriter().writeMessage(generator, original);
191 
192         // reparse the written file
193         final byte[]     bytes   = caw.toString().getBytes(StandardCharsets.UTF_8);
194         final DataSource source2 = new DataSource(name, () -> new ByteArrayInputStream(bytes));
195         final Apm    rebuilt = new ParserBuilder().buildApmParser().parseMessage(source2);
196         validateAPM2(rebuilt);
197     }
198 
199     private void validateAPM2(final Apm file) {
200 
201         // Verify general data
202         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
203         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
204 
205         // Check Header Block
206         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
207         Assertions.assertEquals(new AbsoluteDate(2004, 2, 14, 19, 23, 57,
208                                              TimeScalesFactory.getUTC()),
209                             file.getHeader().getCreationDate());
210         Assertions.assertEquals("JPL", file.getHeader().getOriginator());
211 
212         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
213 
214         // Check Metadata Block
215         Assertions.assertEquals("MARS SPIRIT", segment.getMetadata().getObjectName());
216         Assertions.assertEquals("2004-003A",   segment.getMetadata().getObjectID());
217         Assertions.assertEquals(2004,          segment.getMetadata().getLaunchYear());
218         Assertions.assertEquals(3,             segment.getMetadata().getLaunchNumber());
219         Assertions.assertEquals("A",           segment.getMetadata().getLaunchPiece());
220         Assertions.assertEquals("EARTH",       segment.getMetadata().getCenter().getName());
221         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
222         Assertions.assertTrue(segment.getMetadata().getComments().isEmpty());
223         Assertions.assertEquals(CelestialBodyFactory.getEarth(), segment.getMetadata().getCenter().getBody());
224         Assertions.assertEquals("UTC",         segment.getMetadata().getTimeSystem().name());
225 
226         // Check general comments
227         ArrayList<String> generalComment = new ArrayList<>();
228         generalComment.add("GEOCENTRIC, CARTESIAN, EARTH FIXED");
229         generalComment.add("OBJECT ID: 2004-003");
230         generalComment.add("$ITIM = 2004 JAN 14 22:26:18.400000, original launch time 14:36");
231         generalComment.add("Generated by JPL");
232         generalComment.add("Current attitude for orbit 20 and attitude maneuver");
233         generalComment.add("planning data.");
234         generalComment.add("Attitude state quaternion");
235         Assertions.assertEquals(generalComment, segment.getData().getComments());
236 
237         // Check data block: QUATERNION
238         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.INSTRUMENT,
239                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
240         Assertions.assertEquals("A", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
241         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asCelestialBodyFrame());
242         Assertions.assertEquals(new AbsoluteDate(2004, 2, 14, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND),
243                                              TimeScalesFactory.getUTC()),
244                             segment.getData().getEpoch());
245 
246         Assertions.assertEquals(0.47832, segment.getData().getQuaternionBlock().getQuaternion().getQ0(),    QUATERNION_PRECISION);
247         Assertions.assertEquals(0.03123, segment.getData().getQuaternionBlock().getQuaternion().getQ1(),    QUATERNION_PRECISION);
248         Assertions.assertEquals(0.78543, segment.getData().getQuaternionBlock().getQuaternion().getQ2(),    QUATERNION_PRECISION);
249         Assertions.assertEquals(0.39158, segment.getData().getQuaternionBlock().getQuaternion().getQ3(),    QUATERNION_PRECISION);
250         Assertions.assertFalse(segment.getData().getQuaternionBlock().hasRates());
251         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ1()));
252 
253         // Check data block: EULER
254         ArrayList<String> eulerComment = new ArrayList<>();
255         eulerComment.add("Attitude specified as Euler elements");
256         Assertions.assertEquals(eulerComment,    segment.getData().getEulerBlock().getComments());
257         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, segment.getData().getEulerBlock().getEndpoints().getFrameA().asCelestialBodyFrame());
258         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.INSTRUMENT,  segment.getData().getEulerBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
259         Assertions.assertEquals("A",  segment.getData().getEulerBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
260         Assertions.assertTrue(segment.getData().getEulerBlock().getEndpoints().isA2b());
261         Assertions.assertTrue(segment.getData().getEulerBlock().rateFrameIsA());
262         Assertions.assertEquals(RotationOrder.ZXY, segment.getData().getEulerBlock().getEulerRotSeq());
263 
264         Assertions.assertEquals(-53.3688,  FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[0]), ANGLE_PRECISION);
265         Assertions.assertEquals(139.7527,  FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[1]), ANGLE_PRECISION);
266         Assertions.assertEquals( 25.0658,  FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[2]), ANGLE_PRECISION);
267         Assertions.assertEquals(  0.02156, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationRates()[0]),  ANGLE_PRECISION);
268         Assertions.assertEquals(  0.1045,  FastMath.toDegrees(segment.getData().getEulerBlock().getRotationRates()[1]),  ANGLE_PRECISION);
269         Assertions.assertEquals(  0.03214, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationRates()[2]),  ANGLE_PRECISION);
270 
271         // Check data block: SPACECRAFT PARAMETERS
272         ArrayList<String> spacecraftComment = new ArrayList<>();
273         spacecraftComment.add("Spacecraft Parameters");
274         Assertions.assertEquals(spacecraftComment, segment.getData().getInertiaBlock().getComments());
275         Assertions.assertEquals(6080.0,            segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(0, 0), SPACECRAFT_PRECISION);
276         Assertions.assertEquals(5245.5,            segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(1, 1), SPACECRAFT_PRECISION);
277         Assertions.assertEquals(8067.3,            segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(2, 2), SPACECRAFT_PRECISION);
278         Assertions.assertEquals(-135.9,            segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(0, 1), SPACECRAFT_PRECISION);
279         Assertions.assertEquals(89.3,              segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(0, 2), SPACECRAFT_PRECISION);
280         Assertions.assertEquals(-90.7,             segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(1, 2), SPACECRAFT_PRECISION);
281 
282         // Check data block: MANEUVER
283         ArrayList<String> maneuverComment = new ArrayList<>();
284         maneuverComment.add("Data follows for 1 planned maneuver.");
285         maneuverComment.add("First attitude maneuver for: MARS SPIRIT");
286         maneuverComment.add("Impulsive, torque direction fixed in body frame");
287         Assertions.assertEquals(maneuverComment, segment.getData().getManeuver(0).getComments());
288         Assertions.assertTrue(segment.getData().hasManeuvers());
289         Assertions.assertEquals(1, segment.getData().getNbManeuvers());
290         Assertions.assertEquals(1, segment.getData().getManeuvers().size());
291         Assertions.assertEquals(BaseEquipment.INSTRUMENT, segment.getData().getManeuver(0).getFrame().asSpacecraftBodyFrame().getBaseEquipment());
292         Assertions.assertEquals("A", segment.getData().getManeuver(0).getFrame().asSpacecraftBodyFrame().getLabel());
293         Assertions.assertEquals(new AbsoluteDate(2004, 2, 14, 14, 29, 0.5098,
294                                              TimeScalesFactory.getUTC()),
295                             segment.getData().getManeuver(0).getEpochStart());
296         Assertions.assertEquals(3,     segment.getData().getManeuver(0).getDuration(),      MANEUVER_PRECISION);
297         Assertions.assertEquals(-1.25, segment.getData().getManeuver(0).getTorque().getX(), MANEUVER_PRECISION);
298         Assertions.assertEquals(-0.5,  segment.getData().getManeuver(0).getTorque().getY(), MANEUVER_PRECISION);
299         Assertions.assertEquals(0.5,   segment.getData().getManeuver(0).getTorque().getZ(), MANEUVER_PRECISION);
300 
301     }
302 
303     @Test
304     public void testParseAPM3() {
305 
306         // File
307         final String ex = "/ccsds/adm/apm/APMExample03.txt";
308 
309         // Initialize the parser
310         final ApmParser parser = new ParserBuilder().
311                                  withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
312                                                                            TimeScalesFactory.getUTC())).
313                                  buildApmParser();
314 
315         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
316 
317         // Generated APM file
318         final Apm file = parser.parseMessage(source);
319 
320         // Verify general data
321         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
322         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
323 
324         // Check Header Block
325         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
326         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 19, 23, 57,
327                                              TimeScalesFactory.getUTC()),
328                             file.getHeader().getCreationDate());
329         Assertions.assertEquals("GSFC", file.getHeader().getOriginator());
330 
331         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
332 
333         // Check Metadata Block
334         Assertions.assertEquals("TRMM",       segment.getMetadata().getObjectName());
335         Assertions.assertEquals("1997-009A",  segment.getMetadata().getObjectID());
336         Assertions.assertEquals(1997,         segment.getMetadata().getLaunchYear());
337         Assertions.assertEquals(9,            segment.getMetadata().getLaunchNumber());
338         Assertions.assertEquals("A",          segment.getMetadata().getLaunchPiece());
339         Assertions.assertEquals("EARTH",      segment.getMetadata().getCenter().getName());
340         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
341         Assertions.assertEquals(CelestialBodyFactory.getEarth(), segment.getMetadata().getCenter().getBody());
342         Assertions.assertEquals("UTC",        segment.getMetadata().getTimeSystem().name());
343 
344         // Check data block: QUATERNION
345         Assertions.assertFalse(segment.getData().hasManeuvers());
346         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
347                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
348         Assertions.assertEquals("1", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
349         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asCelestialBodyFrame());
350         Assertions.assertFalse(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
351         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND),
352                                              TimeScalesFactory.getUTC()),
353                             segment.getData().getEpoch());
354         Assertions.assertEquals(0.25678, segment.getData().getQuaternionBlock().getQuaternion().getQ0(),    QUATERNION_PRECISION);
355         Assertions.assertEquals(0.00005, segment.getData().getQuaternionBlock().getQuaternion().getQ1(),    QUATERNION_PRECISION);
356         Assertions.assertEquals(0.87543, segment.getData().getQuaternionBlock().getQuaternion().getQ2(),    QUATERNION_PRECISION);
357         Assertions.assertEquals(0.40949, segment.getData().getQuaternionBlock().getQuaternion().getQ3(),    QUATERNION_PRECISION);
358         Assertions.assertFalse(segment.getData().getQuaternionBlock().hasRates());
359         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ1()));
360 
361         // Check data block: SPIN
362         ArrayList<String> spinComment = new ArrayList<>();
363         spinComment.add("SPIN Parameters");
364         Assertions.assertEquals(spinComment, segment.getData().getSpinStabilizedBlock().getComments());
365         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
366                             segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
367         Assertions.assertEquals("1", segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
368         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameA().asCelestialBodyFrame());
369         Assertions.assertFalse(segment.getData().getSpinStabilizedBlock().getEndpoints().isA2b());
370         Assertions.assertEquals(FastMath.toRadians(24.8),   segment.getData().getSpinStabilizedBlock().getSpinAlpha(),      ANGLE_PRECISION);
371         Assertions.assertEquals(FastMath.toRadians(33.7),   segment.getData().getSpinStabilizedBlock().getSpinDelta(),      ANGLE_PRECISION);
372         Assertions.assertEquals(FastMath.toRadians(42.5),   segment.getData().getSpinStabilizedBlock().getSpinAngle(),      ANGLE_PRECISION);
373         Assertions.assertEquals(FastMath.toRadians(-135.9), segment.getData().getSpinStabilizedBlock().getSpinAngleVel(),   ANGLE_PRECISION);
374         Assertions.assertEquals(FastMath.toRadians(89.3),   segment.getData().getSpinStabilizedBlock().getNutation(),       ANGLE_PRECISION);
375         Assertions.assertEquals(FastMath.toRadians(-90.7),  segment.getData().getSpinStabilizedBlock().getNutationPhase(),  ANGLE_PRECISION);
376         Assertions.assertEquals(64.0,                       segment.getData().getSpinStabilizedBlock().getNutationPeriod(), ANGLE_PRECISION);
377 
378     }
379 
380     @Test
381     public void testParseAPM4() {
382 
383         // File
384         final String ex = "/ccsds/adm/apm/APMExample04.txt";
385 
386         // Initialize the parser
387         final ApmParser parser = new ParserBuilder().
388                                  withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
389                                                                            TimeScalesFactory.getUTC())).
390                                  buildApmParser();
391 
392         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
393 
394         // Generated APM file
395         final Apm file = parser.parseMessage(source);
396 
397         // Verify general data
398         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
399         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
400 
401         // Check Header Block
402         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
403         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 19, 23, 57,
404                                              TimeScalesFactory.getUTC()),
405                             file.getHeader().getCreationDate());
406         Assertions.assertEquals("GSFC", file.getHeader().getOriginator());
407 
408         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
409 
410         // Check Metadata Block
411         Assertions.assertEquals("TRMM",       segment.getMetadata().getObjectName());
412         Assertions.assertEquals("1997-009A",  segment.getMetadata().getObjectID());
413         Assertions.assertEquals(1997,         segment.getMetadata().getLaunchYear());
414         Assertions.assertEquals(9,            segment.getMetadata().getLaunchNumber());
415         Assertions.assertEquals("A",          segment.getMetadata().getLaunchPiece());
416         Assertions.assertEquals("EARTH",      segment.getMetadata().getCenter().getName());
417         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
418         Assertions.assertEquals(CelestialBodyFactory.getEarth(), segment.getMetadata().getCenter().getBody());
419         Assertions.assertEquals("UTC",        segment.getMetadata().getTimeSystem().name());
420 
421         // Check data block
422         Assertions.assertFalse(segment.getData().hasManeuvers());
423         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
424                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
425         Assertions.assertEquals("1", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
426         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asCelestialBodyFrame());
427         Assertions.assertTrue(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
428         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND),
429                                              TimeScalesFactory.getUTC()),
430                             segment.getData().getEpoch());
431         Assertions.assertEquals(0.25678, segment.getData().getQuaternionBlock().getQuaternion().getQ0(),    QUATERNION_PRECISION);
432         Assertions.assertEquals(0.00005, segment.getData().getQuaternionBlock().getQuaternion().getQ1(),    QUATERNION_PRECISION);
433         Assertions.assertEquals(0.87543, segment.getData().getQuaternionBlock().getQuaternion().getQ2(),    QUATERNION_PRECISION);
434         Assertions.assertEquals(0.40949, segment.getData().getQuaternionBlock().getQuaternion().getQ3(),    QUATERNION_PRECISION);
435         Assertions.assertEquals(0.05678, segment.getData().getQuaternionBlock().getQuaternionDot().getQ0(), QUATERNION_PRECISION);
436         Assertions.assertEquals(0.00001, segment.getData().getQuaternionBlock().getQuaternionDot().getQ1(), QUATERNION_PRECISION);
437         Assertions.assertEquals(0.07543, segment.getData().getQuaternionBlock().getQuaternionDot().getQ2(), QUATERNION_PRECISION);
438         Assertions.assertEquals(0.00949, segment.getData().getQuaternionBlock().getQuaternionDot().getQ3(), QUATERNION_PRECISION);
439         Assertions.assertEquals(new AbsoluteDate(2003, 9, 30, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND), TimeScalesFactory.getUTC()),
440                                 segment.getData().getAttitude(null, null).getDate());
441         Assertions.assertEquals(8.63363e-2, segment.getData().getAttitude(null, null).getSpin().getNorm(), 1.0e-7);
442 
443         Attitude attitude = file.getAttitude(null, null);
444         Assertions.assertEquals(segment.getData().getEpoch(), attitude.getDate());
445         Assertions.assertEquals(8.63363e-2, attitude.getSpin().getNorm(), 1.0e-7);
446 
447     }
448 
449     @Test
450     public void testParseAPM5() {
451 
452         // File
453         final String ex = "/ccsds/adm/apm/APMExample05.txt";
454 
455         // Initialize the parser
456         final ApmParser parser = new ParserBuilder().buildApmParser();
457 
458         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
459 
460         // Generated APM file
461         final Apm file = parser.parseMessage(source);
462 
463         // Verify general data
464         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
465         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
466 
467         // Check Header Block
468         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
469         Assertions.assertEquals(new AbsoluteDate(2004, 2, 14, 19, 23, 57,
470                                              TimeScalesFactory.getUTC()),
471                             file.getHeader().getCreationDate());
472         Assertions.assertEquals("GSFC", file.getHeader().getOriginator());
473 
474         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
475 
476         // Check Metadata Block
477         Assertions.assertEquals("TRMM",       segment.getMetadata().getObjectName());
478         Assertions.assertEquals("1997-009A",  segment.getMetadata().getObjectID());
479         Assertions.assertEquals(1997,         segment.getMetadata().getLaunchYear());
480         Assertions.assertEquals(9,            segment.getMetadata().getLaunchNumber());
481         Assertions.assertEquals("A",          segment.getMetadata().getLaunchPiece());
482         Assertions.assertEquals("EARTH",      segment.getMetadata().getCenter().getName());
483         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
484         Assertions.assertEquals(CelestialBodyFactory.getEarth(), segment.getMetadata().getCenter().getBody());
485         Assertions.assertEquals("UTC",        segment.getMetadata().getTimeSystem().name());
486 
487         // Check data block
488         Assertions.assertFalse(segment.getData().hasManeuvers());
489         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
490                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
491         Assertions.assertEquals("1", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
492         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asCelestialBodyFrame());
493         Assertions.assertTrue(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
494         Assertions.assertEquals(new AbsoluteDate(2004, 2, 14, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND),
495                                              TimeScalesFactory.getUTC()),
496                             segment.getData().getEpoch());
497         Assertions.assertEquals(0.47832, segment.getData().getQuaternionBlock().getQuaternion().getQ0(), QUATERNION_PRECISION);
498         Assertions.assertEquals(0.03123, segment.getData().getQuaternionBlock().getQuaternion().getQ1(), QUATERNION_PRECISION);
499         Assertions.assertEquals(0.78543, segment.getData().getQuaternionBlock().getQuaternion().getQ2(), QUATERNION_PRECISION);
500         Assertions.assertEquals(0.39158, segment.getData().getQuaternionBlock().getQuaternion().getQ3(), QUATERNION_PRECISION);
501         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ0()));
502         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ1()));
503         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ2()));
504         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ3()));
505 
506         Assertions.assertEquals(RotationOrder.ZXY, segment.getData().getEulerBlock().getEulerRotSeq());
507         Assertions.assertFalse(segment.getData().getEulerBlock().hasAngles());
508         Assertions.assertTrue(Double.isNaN(segment.getData().getEulerBlock().getRotationAngles()[0]));
509         Assertions.assertTrue(Double.isNaN(segment.getData().getEulerBlock().getRotationAngles()[1]));
510         Assertions.assertTrue(Double.isNaN(segment.getData().getEulerBlock().getRotationAngles()[2]));
511         Assertions.assertEquals(0.02156, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationRates()[0]), ANGLE_PRECISION);
512         Assertions.assertEquals(0.1045,  FastMath.toDegrees(segment.getData().getEulerBlock().getRotationRates()[1]), ANGLE_PRECISION);
513         Assertions.assertEquals(0.03214, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationRates()[2]), ANGLE_PRECISION);
514 
515         Attitude attitude = file.getAttitude(null, null);
516         Assertions.assertEquals(segment.getData().getEpoch(), attitude.getDate());
517         Assertions.assertEquals(2.0137e-3, attitude.getSpin().getNorm(), 1.0e-7);
518 
519     }
520 
521     @Test
522     public void testParseAPM6() {
523 
524         // File
525         final String ex = "/ccsds/adm/apm/APMExample06.txt";
526 
527         // Initialize the parser
528         final ApmParser parser = new ParserBuilder().buildApmParser();
529 
530         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
531 
532         // Generated APM file
533         final Apm file = parser.parseMessage(source);
534 
535         // Verify general data
536         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
537         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
538 
539         // Check Header Block
540         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
541         Assertions.assertEquals(new AbsoluteDate(2004, 2, 14, 19, 23, 57,
542                                              TimeScalesFactory.getUTC()),
543                             file.getHeader().getCreationDate());
544         Assertions.assertEquals("GSFC", file.getHeader().getOriginator());
545 
546         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
547 
548         // Check Metadata Block
549         Assertions.assertEquals("TRMM",       segment.getMetadata().getObjectName());
550         Assertions.assertEquals("1997-009A",  segment.getMetadata().getObjectID());
551         Assertions.assertEquals(1997,         segment.getMetadata().getLaunchYear());
552         Assertions.assertEquals(9,            segment.getMetadata().getLaunchNumber());
553         Assertions.assertEquals("A",          segment.getMetadata().getLaunchPiece());
554         Assertions.assertEquals("EARTH",      segment.getMetadata().getCenter().getName());
555         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
556         Assertions.assertEquals(CelestialBodyFactory.getEarth(), segment.getMetadata().getCenter().getBody());
557         Assertions.assertEquals("UTC",        segment.getMetadata().getTimeSystem().name());
558 
559         // Check data block
560         Assertions.assertFalse(segment.getData().hasManeuvers());
561         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
562                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
563         Assertions.assertEquals("1", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
564         Assertions.assertEquals(CelestialBodyFrame.ITRF1997, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asCelestialBodyFrame());
565         Assertions.assertTrue(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
566         Assertions.assertEquals(new AbsoluteDate(2004, 2, 14, 14, 28, new TimeOffset(15, TimeOffset.SECOND, 117200, TimeOffset.MICROSECOND),
567                                              TimeScalesFactory.getUTC()),
568                             segment.getData().getEpoch());
569         Assertions.assertEquals(0.47832, segment.getData().getQuaternionBlock().getQuaternion().getQ0(), QUATERNION_PRECISION);
570         Assertions.assertEquals(0.03123, segment.getData().getQuaternionBlock().getQuaternion().getQ1(), QUATERNION_PRECISION);
571         Assertions.assertEquals(0.78543, segment.getData().getQuaternionBlock().getQuaternion().getQ2(), QUATERNION_PRECISION);
572         Assertions.assertEquals(0.39158, segment.getData().getQuaternionBlock().getQuaternion().getQ3(), QUATERNION_PRECISION);
573         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ0()));
574         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ1()));
575         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ2()));
576         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ3()));
577 
578         Assertions.assertEquals(RotationOrder.ZXY, segment.getData().getEulerBlock().getEulerRotSeq());
579         Assertions.assertEquals(0.02156, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[0]), ANGLE_PRECISION);
580         Assertions.assertEquals(0.1045,  FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[1]), ANGLE_PRECISION);
581         Assertions.assertEquals(0.03214, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[2]), ANGLE_PRECISION);
582         Assertions.assertTrue(Double.isNaN(segment.getData().getEulerBlock().getRotationRates()[0]));
583         Assertions.assertTrue(Double.isNaN(segment.getData().getEulerBlock().getRotationRates()[1]));
584         Assertions.assertTrue(Double.isNaN(segment.getData().getEulerBlock().getRotationRates()[2]));
585 
586         Attitude attitude = file.getAttitude(null, null);
587         Assertions.assertEquals(segment.getData().getEpoch(),
588                             attitude.getDate());
589         Assertions.assertEquals(0.0, attitude.getSpin().getNorm(), 1.0e-15);
590 
591     }
592 
593     @Test
594     public void testParseAPM7() {
595 
596         // File
597         final String ex = "/ccsds/adm/apm/APMExample07.txt";
598 
599         // Initialize the parser
600         final ApmParser parser = new ParserBuilder().buildApmParser();
601 
602         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
603 
604         // Generated APM file
605         final Apm file = parser.parseMessage(source);
606  
607         // Verify general data
608         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
609         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
610 
611         // Check Header Block
612         Assertions.assertEquals(2.0, file.getHeader().getFormatVersion(), 1.0e-10);
613         Assertions.assertEquals(new AbsoluteDate(2023, 1, 21, 11, 55, 0, TimeScalesFactory.getUTC()),
614                                 file.getHeader().getCreationDate());
615         Assertions.assertEquals("GSFC", file.getHeader().getOriginator());
616         Assertions.assertEquals("A000001", file.getHeader().getMessageId());
617 
618         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
619 
620         // Check Metadata Block
621         Assertions.assertEquals("SPINNING",   segment.getMetadata().getComments().get(0));
622         Assertions.assertEquals("MMS1",       segment.getMetadata().getObjectName());
623         Assertions.assertEquals("2015-011A",  segment.getMetadata().getObjectID());
624         Assertions.assertEquals(2015,         segment.getMetadata().getLaunchYear());
625         Assertions.assertEquals(11,           segment.getMetadata().getLaunchNumber());
626         Assertions.assertEquals("A",          segment.getMetadata().getLaunchPiece());
627         Assertions.assertEquals("EARTH",      segment.getMetadata().getCenter().getName());
628         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
629         Assertions.assertEquals(CelestialBodyFactory.getEarth(), segment.getMetadata().getCenter().getBody());
630         Assertions.assertEquals("TAI",        segment.getMetadata().getTimeSystem().name());
631 
632         // Check data block
633         Assertions.assertEquals(new AbsoluteDate(2023, 1, 1, 0, 0, 0.0,
634                                                  TimeScalesFactory.getTAI()),
635                                 segment.getData().getEpoch());
636         Assertions.assertFalse(segment.getData().hasManeuvers());
637         Assertions.assertEquals(CelestialBodyFrame.J2000, segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameA().asCelestialBodyFrame());
638         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
639                                 segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
640         Assertions.assertEquals("1", segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
641         Assertions.assertEquals(10.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getSpinAlpha()),     ANGLE_PRECISION);
642         Assertions.assertEquals(30.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getSpinDelta()),     ANGLE_PRECISION);
643         Assertions.assertEquals( 0.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getSpinAngle()),     ANGLE_PRECISION);
644         Assertions.assertEquals(80.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getMomentumAlpha()), ANGLE_PRECISION);
645         Assertions.assertEquals(10.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getMomentumDelta()), ANGLE_PRECISION);
646         Assertions.assertEquals( 0.5, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getNutationVel()),   ANGLE_PRECISION);
647 
648         Attitude attitude = file.getAttitude(null, null);
649         Assertions.assertEquals(segment.getData().getEpoch(), attitude.getDate());
650         Assertions.assertEquals(2.22728e-2, attitude.getSpin().getNorm(), 1.0e-7);
651 
652     }
653 
654     @Test
655     public void testParseAPM8() {
656 
657         // File
658         final String ex = "/ccsds/adm/apm/APMExample08.txt";
659 
660         // Initialize the parser
661         final ApmParser parser = new ParserBuilder().buildApmParser();
662 
663         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
664 
665         // Generated APM file
666         final Apm file = parser.parseMessage(source);
667 
668         // Verify general data
669         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
670         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
671 
672         // Check Header Block
673         Assertions.assertEquals(2.0, file.getHeader().getFormatVersion(), 1.0e-10);
674         Assertions.assertEquals(new AbsoluteDate(2023, 2, 3, 12, 0, 0, TimeScalesFactory.getUTC()),
675                             file.getHeader().getCreationDate());
676         Assertions.assertEquals("GSFC",    file.getHeader().getOriginator());
677         Assertions.assertEquals("A000002", file.getHeader().getMessageId());
678 
679         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
680 
681         // Check Metadata Block
682         Assertions.assertEquals("Rotation From Nadir", segment.getMetadata().getComments().get(0));
683         Assertions.assertEquals("LRO",        segment.getMetadata().getObjectName());
684         Assertions.assertEquals("2009-031A",  segment.getMetadata().getObjectID());
685         Assertions.assertEquals(2009,         segment.getMetadata().getLaunchYear());
686         Assertions.assertEquals(31,           segment.getMetadata().getLaunchNumber());
687         Assertions.assertEquals("A",          segment.getMetadata().getLaunchPiece());
688         Assertions.assertEquals("MOON",       segment.getMetadata().getCenter().getName());
689         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
690         Assertions.assertEquals(CelestialBodyFactory.getMoon(), segment.getMetadata().getCenter().getBody());
691         Assertions.assertEquals("TAI",        segment.getMetadata().getTimeSystem().name());
692 
693         // Check data block
694         Assertions.assertEquals(new AbsoluteDate(2023, 1, 1, 0, 0, 0.0, TimeScalesFactory.getTAI()),
695                                 segment.getData().getEpoch());
696         Assertions.assertFalse(segment.getData().hasManeuvers());
697         Assertions.assertEquals(CelestialBodyFrame.EME2000, segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asCelestialBodyFrame());
698         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
699                                 segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
700         Assertions.assertEquals("", segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
701         Assertions.assertEquals( 0.925417, segment.getData().getQuaternionBlock().getQuaternion().getQ0(), QUATERNION_PRECISION);
702         Assertions.assertEquals( 0.171010, segment.getData().getQuaternionBlock().getQuaternion().getQ1(), QUATERNION_PRECISION);
703         Assertions.assertEquals(-0.030154, segment.getData().getQuaternionBlock().getQuaternion().getQ2(), QUATERNION_PRECISION);
704         Assertions.assertEquals( 0.336824, segment.getData().getQuaternionBlock().getQuaternion().getQ3(), QUATERNION_PRECISION);
705         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ0()));
706         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ1()));
707         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ2()));
708         Assertions.assertTrue(Double.isNaN(segment.getData().getQuaternionBlock().getQuaternionDot().getQ3()));
709 
710         Assertions.assertEquals(CelestialBodyFrame.EME2000, segment.getData().getAngularVelocityBlock().getEndpoints().getFrameA().asCelestialBodyFrame());
711         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
712                                 segment.getData().getAngularVelocityBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
713         Assertions.assertEquals("", segment.getData().getAngularVelocityBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
714         Assertions.assertEquals( 0.0001,  FastMath.toDegrees(segment.getData().getAngularVelocityBlock().getAngVelX()), ANGLE_PRECISION);
715         Assertions.assertEquals( 0.05,    FastMath.toDegrees(segment.getData().getAngularVelocityBlock().getAngVelY()), ANGLE_PRECISION);
716         Assertions.assertEquals( 0.00003, FastMath.toDegrees(segment.getData().getAngularVelocityBlock().getAngVelZ()), ANGLE_PRECISION);
717 
718     }
719 
720     @Test
721     public void testParseAPM9() {
722 
723         // File
724         final String ex = "/ccsds/adm/apm/APMExample09.txt";
725 
726         // Initialize the parser
727         final ApmParser parser = new ParserBuilder().buildApmParser();
728 
729         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
730 
731         // Generated APM file
732         final Apm file = parser.parseMessage(source);
733 
734         // Verify general data
735         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
736         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
737 
738         // Check Header Block
739         Assertions.assertEquals(2.0, file.getHeader().getFormatVersion(), 1.0e-10);
740         Assertions.assertEquals(new AbsoluteDate(2023, 2, 5, 12, 0, 0, TimeScalesFactory.getUTC()),
741                                 file.getHeader().getCreationDate());
742         Assertions.assertEquals("GSFC", file.getHeader().getOriginator());
743         Assertions.assertEquals("A000003", file.getHeader().getMessageId());
744 
745         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
746 
747         Assertions.assertEquals("SPINNING",   segment.getMetadata().getComments().get(0));
748         Assertions.assertEquals("MMS1",       segment.getMetadata().getObjectName());
749         Assertions.assertEquals("2015-011A",  segment.getMetadata().getObjectID());
750         Assertions.assertEquals(2015,         segment.getMetadata().getLaunchYear());
751         Assertions.assertEquals(11,           segment.getMetadata().getLaunchNumber());
752         Assertions.assertEquals("A",          segment.getMetadata().getLaunchPiece());
753         Assertions.assertEquals("EARTH",      segment.getMetadata().getCenter().getName());
754         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
755         Assertions.assertEquals(CelestialBodyFactory.getEarth(), segment.getMetadata().getCenter().getBody());
756         Assertions.assertEquals("TAI",        segment.getMetadata().getTimeSystem().name());
757 
758         // Check data block
759         Assertions.assertEquals(new AbsoluteDate(2023, 1, 1, 0, 0, 0.0,
760                                                  TimeScalesFactory.getTAI()),
761                                 segment.getData().getEpoch());
762         Assertions.assertNull(segment.getData().getQuaternionBlock());
763 
764         Assertions.assertEquals(CelestialBodyFrame.EME2000, segment.getData().getEulerBlock().getEndpoints().getFrameA().asCelestialBodyFrame());
765         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
766                                 segment.getData().getEulerBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
767         Assertions.assertEquals("", segment.getData().getEulerBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
768         Assertions.assertEquals(RotationOrder.ZXZ, segment.getData().getEulerBlock().getEulerRotSeq());
769         Assertions.assertEquals(10.0, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[0]), ANGLE_PRECISION);
770         Assertions.assertEquals(20.0, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[1]), ANGLE_PRECISION);
771         Assertions.assertEquals( 0.0, FastMath.toDegrees(segment.getData().getEulerBlock().getRotationAngles()[2]), ANGLE_PRECISION);
772 
773         Assertions.assertEquals(CelestialBodyFrame.EME2000, segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameA().asCelestialBodyFrame());
774         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
775                                 segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getBaseEquipment());
776         Assertions.assertEquals("", segment.getData().getSpinStabilizedBlock().getEndpoints().getFrameB().asSpacecraftBodyFrame().getLabel());
777         Assertions.assertEquals(-80.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getSpinAlpha()),      ANGLE_PRECISION);
778         Assertions.assertEquals( 70.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getSpinDelta()),      ANGLE_PRECISION);
779         Assertions.assertEquals(  0.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getSpinAngle()),      ANGLE_PRECISION);
780         Assertions.assertEquals(  1.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getSpinAngleVel()),   ANGLE_PRECISION);
781         Assertions.assertEquals(  0.1, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getNutation()),       ANGLE_PRECISION);
782         Assertions.assertEquals(720.0, segment.getData().getSpinStabilizedBlock().getNutationPeriod(), 1.0e-10);
783         Assertions.assertEquals(-85.0, FastMath.toDegrees(segment.getData().getSpinStabilizedBlock().getNutationPhase()),  ANGLE_PRECISION);
784 
785         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
786                                 segment.getData().getInertiaBlock().getFrame().asSpacecraftBodyFrame().getBaseEquipment());
787         Assertions.assertEquals("", segment.getData().getInertiaBlock().getFrame().asSpacecraftBodyFrame().getLabel());
788         Assertions.assertEquals(1443.10, segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(0, 0), 1.0e-10);
789         Assertions.assertEquals(1445.20, segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(1, 1), 1.0e-10);
790         Assertions.assertEquals(1760.20, segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(2, 2), 1.0e-10);
791         Assertions.assertEquals( -86.40, segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(0, 1), 1.0e-10);
792         Assertions.assertEquals(   0.00, segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(0, 2), 1.0e-10);
793         Assertions.assertEquals(  -0.09, segment.getData().getInertiaBlock().getInertiaMatrix().getEntry(1, 2), 1.0e-10);
794 
795         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
796                                 segment.getData().getManeuver(0).getFrame().asSpacecraftBodyFrame().getBaseEquipment());
797         Assertions.assertEquals("",      segment.getData().getManeuver(0).getFrame().asSpacecraftBodyFrame().getLabel());
798         Assertions.assertEquals(1.0,     segment.getData().getManeuver(0).getDuration(),      1.0e-10);
799         Assertions.assertEquals(1.0,     segment.getData().getManeuver(0).getTorque().getX(), 1.0e-10);
800         Assertions.assertEquals(0.0,     segment.getData().getManeuver(0).getTorque().getY(), 1.0e-10);
801         Assertions.assertEquals(0.0,     segment.getData().getManeuver(0).getTorque().getZ(), 1.0e-10);
802         Assertions.assertEquals(-0.001,  segment.getData().getManeuver(0).getDeltaMass(),     1.0e-10);
803 
804     }
805 
806     @Test
807     public void testOrbitRelativeFrameInertial() {
808 
809         // File
810         final String ex = "/ccsds/adm/apm/APM-orbit-relative-frame-inertial.txt";
811 
812         // Initialize the parser
813         final ApmParser parser = new ParserBuilder().
814                                  withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
815                                                                            TimeScalesFactory.getUTC())).
816                                  buildApmParser();
817 
818         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
819 
820         // Generated APM file
821         final Apm file = parser.parseMessage(source);
822 
823         // Verify general data
824         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
825         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
826 
827         // Check Header Block
828         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
829         Assertions.assertEquals(new AbsoluteDate(2021, 2, 24, 18, 59, 43,
830                                              TimeScalesFactory.getUTC()),
831                             file.getHeader().getCreationDate());
832         Assertions.assertEquals("CS GROUP", file.getHeader().getOriginator());
833 
834         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
835 
836         // Check Metadata Block
837         Assertions.assertEquals("DUMMY",      segment.getMetadata().getObjectName());
838         Assertions.assertEquals("9999-111Z",  segment.getMetadata().getObjectID());
839         Assertions.assertEquals(9999,         segment.getMetadata().getLaunchYear());
840         Assertions.assertEquals(111,          segment.getMetadata().getLaunchNumber());
841         Assertions.assertEquals("Z",          segment.getMetadata().getLaunchPiece());
842         Assertions.assertEquals("JUPITER",    segment.getMetadata().getCenter().getName());
843         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
844         Assertions.assertEquals(CelestialBodyFactory.getJupiter(), segment.getMetadata().getCenter().getBody());
845         Assertions.assertEquals("TDB",        segment.getMetadata().getTimeSystem().name());
846 
847         // Check data block
848         Assertions.assertFalse(segment.getData().hasManeuvers());
849         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
850                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
851         Assertions.assertEquals("1", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
852         Assertions.assertEquals(OrbitRelativeFrame.VNC_INERTIAL, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asOrbitRelativeFrame());
853         Assertions.assertFalse(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
854         Assertions.assertEquals(new AbsoluteDate(2021, 1, 1, 0, 0, 0.0, TimeScalesFactory.getTDB()),
855                             segment.getData().getEpoch());
856 
857         final PVCoordinates pv = new PVCoordinates(new Vector3D( 1.234e7, -0.567e7, 9.876e6),
858                                                    new Vector3D(-0.772e4,  5.002e4, 4.892e2));
859         Attitude attitude = file.getAttitude(FramesFactory.getEME2000(),
860                                              (date, frame) -> new TimeStampedPVCoordinates(date, pv));
861         Vector3D xSat = attitude.getRotation().applyInverseTo(Vector3D.PLUS_I);
862         Vector3D ySat = attitude.getRotation().applyInverseTo(Vector3D.PLUS_J);
863         Assertions.assertEquals(FastMath.PI, Vector3D.angle(xSat, pv.getVelocity()), 1.0e-10);
864         Assertions.assertEquals(0.0,         Vector3D.angle(ySat, pv.getMomentum()), 1.0e-10);
865         Assertions.assertEquals(0.0,         attitude.getSpin().getX(), 1.0e-10);
866         Assertions.assertEquals(0.0,         attitude.getSpin().getY(), 1.0e-10);
867         Assertions.assertEquals(0.0,         attitude.getSpin().getZ(), 1.0e-10);
868 
869     }
870 
871     @Test
872     public void testOrbitRelativeFrameRotating() {
873 
874         // File
875         final String ex = "/ccsds/adm/apm/APM-orbit-relative-frame-rotating.txt";
876 
877         // Initialize the parser
878         final ApmParser parser = new ParserBuilder().
879                                  withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
880                                                                            TimeScalesFactory.getUTC())).
881                                  buildApmParser();
882 
883         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
884 
885         // Generated APM file
886         final Apm file = parser.parseMessage(source);
887 
888         // Verify general data
889         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
890         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
891 
892         // Check Header Block
893         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
894         Assertions.assertEquals(new AbsoluteDate(2021, 2, 24, 18, 59, 43,
895                                              TimeScalesFactory.getUTC()),
896                             file.getHeader().getCreationDate());
897         Assertions.assertEquals("CS GROUP", file.getHeader().getOriginator());
898 
899         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
900 
901         // Check Metadata Block
902         Assertions.assertEquals("DUMMY",      segment.getMetadata().getObjectName());
903         Assertions.assertEquals("9999-111Z",  segment.getMetadata().getObjectID());
904         Assertions.assertEquals(9999,         segment.getMetadata().getLaunchYear());
905         Assertions.assertEquals(111,          segment.getMetadata().getLaunchNumber());
906         Assertions.assertEquals("Z",          segment.getMetadata().getLaunchPiece());
907         Assertions.assertEquals("JUPITER",    segment.getMetadata().getCenter().getName());
908         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
909         Assertions.assertEquals(CelestialBodyFactory.getJupiter(), segment.getMetadata().getCenter().getBody());
910         Assertions.assertEquals("TDB",        segment.getMetadata().getTimeSystem().name());
911 
912         // Check data block
913         Assertions.assertFalse(segment.getData().hasManeuvers());
914         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
915                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
916         Assertions.assertEquals("1", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
917         Assertions.assertEquals(OrbitRelativeFrame.VNC_ROTATING, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asOrbitRelativeFrame());
918         Assertions.assertFalse(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
919         Assertions.assertEquals(new AbsoluteDate(2021, 1, 1, 0, 0, 0.0, TimeScalesFactory.getTDB()),
920                             segment.getData().getEpoch());
921 
922         PVCoordinates pv = new PVCoordinates(new Vector3D( 1.234e7, -0.567e7, 9.876e6),
923                                              new Vector3D(-0.772e4,  5.002e4, 4.892e2));
924         Attitude attitude = file.getAttitude(FramesFactory.getEME2000(),
925                                              (date, frame) -> new TimeStampedPVCoordinates(date, pv));
926         Vector3D xSat = attitude.getRotation().applyInverseTo(Vector3D.PLUS_I);
927         Vector3D ySat = attitude.getRotation().applyInverseTo(Vector3D.PLUS_J);
928         Assertions.assertEquals(FastMath.PI, Vector3D.angle(xSat, pv.getVelocity()), 1.0e-10);
929         Assertions.assertEquals(0.0,         Vector3D.angle(ySat, pv.getMomentum()), 1.0e-10);
930         Assertions.assertEquals(0.0,                               attitude.getSpin().getX(), 1.0e-10);
931         Assertions.assertEquals(pv.getAngularVelocity().getNorm(), attitude.getSpin().getY(), 1.0e-10);
932         Assertions.assertEquals(0.0,                               attitude.getSpin().getZ(), 1.0e-10);
933 
934     }
935 
936     @Test
937     public void testUnsupportedOrbitRelativeFrame() {
938 
939         // File
940         final String ex = "/ccsds/adm/apm/APM-unsupported-orbit-relative-frame.txt";
941 
942         // Initialize the parser
943         final ApmParser parser = new ParserBuilder().
944                                  withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
945                                                                            TimeScalesFactory.getUTC())).
946                                  buildApmParser();
947 
948         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
949 
950         // Generated APM file
951         final Apm file = parser.parseMessage(source);
952 
953         // Verify general data
954         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
955         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
956 
957         // Check Header Block
958         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
959         Assertions.assertEquals(new AbsoluteDate(2021, 2, 24, 18, 59, 43,
960                                              TimeScalesFactory.getUTC()),
961                             file.getHeader().getCreationDate());
962         Assertions.assertEquals("CS GROUP", file.getHeader().getOriginator());
963 
964         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
965 
966         // Check Metadata Block
967         Assertions.assertEquals("DUMMY",      segment.getMetadata().getObjectName());
968         Assertions.assertEquals("9999-111Z",  segment.getMetadata().getObjectID());
969         Assertions.assertEquals(9999,         segment.getMetadata().getLaunchYear());
970         Assertions.assertEquals(111,          segment.getMetadata().getLaunchNumber());
971         Assertions.assertEquals("Z",          segment.getMetadata().getLaunchPiece());
972         Assertions.assertEquals("JUPITER",    segment.getMetadata().getCenter().getName());
973         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
974         Assertions.assertEquals(CelestialBodyFactory.getJupiter(), segment.getMetadata().getCenter().getBody());
975         Assertions.assertEquals("TDB",        segment.getMetadata().getTimeSystem().name());
976 
977         // Check data block
978         Assertions.assertFalse(segment.getData().hasManeuvers());
979         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY,
980                             segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getBaseEquipment());
981         Assertions.assertEquals("1", segment.getData().getQuaternionBlock().getEndpoints().getFrameA().asSpacecraftBodyFrame().getLabel());
982         Assertions.assertEquals(OrbitRelativeFrame.SEZ_INERTIAL, segment.getData().getQuaternionBlock().getEndpoints().getFrameB().asOrbitRelativeFrame());
983         Assertions.assertTrue(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
984         Assertions.assertEquals(new AbsoluteDate(2021, 1, 1, 0, 0, 0.0, TimeScalesFactory.getTDB()),
985                             segment.getData().getEpoch());
986 
987         final PVCoordinatesProvider prov = (date, frame) -> new TimeStampedPVCoordinates(date,
988                                                                                          new PVCoordinates(new Vector3D( 1.234e7, -0.567e7, 9.876e6),
989                                                                                                            new Vector3D(-0.772e4,  5.002e4, 4.892e2)));
990         try {
991             file.getAttitude(FramesFactory.getEME2000(), prov);
992             Assertions.fail("an exception should have been thrown");
993         } catch (OrekitException oe) {
994             Assertions.assertEquals(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, oe.getSpecifier());
995             Assertions.assertEquals(OrbitRelativeFrame.SEZ_INERTIAL.name(), oe.getParts()[0]);
996         }
997 
998     }
999 
1000     @Test
1001     public void testUnknownFrame() {
1002 
1003         // File
1004         final String ex = "/ccsds/adm/apm/APM-unknown-frame.txt";
1005 
1006         // Initialize the parser
1007         final ApmParser parser = new ParserBuilder().
1008                                  withMissionReferenceDate(new AbsoluteDate("2002-09-30T14:28:15.117",
1009                                                                            TimeScalesFactory.getUTC())).
1010                                  buildApmParser();
1011 
1012         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
1013 
1014         // Generated APM file
1015         final Apm file = parser.parseMessage(source);
1016 
1017         // Verify general data
1018         Assertions.assertEquals(IERSConventions.IERS_2010, file.getConventions());
1019         Assertions.assertEquals(DataContext.getDefault(),  file.getDataContext());
1020 
1021         // Check Header Block
1022         Assertions.assertEquals(1.0, file.getHeader().getFormatVersion(), 1.0e-10);
1023         Assertions.assertEquals(new AbsoluteDate(2021, 2, 24, 18, 59, 43,
1024                                              TimeScalesFactory.getUTC()),
1025                             file.getHeader().getCreationDate());
1026         Assertions.assertEquals("CS GROUP", file.getHeader().getOriginator());
1027 
1028         Segment<AdmMetadata, ApmData> segment = file.getSegments().get(0);
1029 
1030         // Check Metadata Block
1031         Assertions.assertEquals("DUMMY",      segment.getMetadata().getObjectName());
1032         Assertions.assertEquals("9999-111Z",  segment.getMetadata().getObjectID());
1033         Assertions.assertEquals(9999,         segment.getMetadata().getLaunchYear());
1034         Assertions.assertEquals(111,          segment.getMetadata().getLaunchNumber());
1035         Assertions.assertEquals("Z",          segment.getMetadata().getLaunchPiece());
1036         Assertions.assertEquals("JUPITER",    segment.getMetadata().getCenter().getName());
1037         Assertions.assertTrue(segment.getMetadata().getHasCreatableBody());
1038         Assertions.assertEquals(CelestialBodyFactory.getJupiter(), segment.getMetadata().getCenter().getBody());
1039         Assertions.assertEquals("TDB",        segment.getMetadata().getTimeSystem().name());
1040 
1041         // Check data block
1042         Assertions.assertFalse(segment.getData().hasManeuvers());
1043         FrameFacade sb = segment.getData().getQuaternionBlock().getEndpoints().getSpacecraftBodyFrame();
1044         Assertions.assertEquals(SpacecraftBodyFrame.BaseEquipment.SC_BODY, sb.asSpacecraftBodyFrame().getBaseEquipment());
1045         Assertions.assertEquals("1", sb.asSpacecraftBodyFrame().getLabel());
1046         FrameFacade ext = segment.getData().getQuaternionBlock().getEndpoints().getExternalFrame();
1047         Assertions.assertNull(ext.asFrame());
1048         Assertions.assertNull(ext.asCelestialBodyFrame());
1049         Assertions.assertNull(ext.asOrbitRelativeFrame());
1050         Assertions.assertNull(ext.asSpacecraftBodyFrame());
1051         Assertions.assertEquals("UNKNOWN", ext.getName());
1052         Assertions.assertFalse(segment.getData().getQuaternionBlock().getEndpoints().isA2b());
1053         Assertions.assertEquals(new AbsoluteDate(2021, 1, 1, 0, 0, 0.0, TimeScalesFactory.getTDB()),
1054                             segment.getData().getEpoch());
1055 
1056         try {
1057             file.getAttitude(FramesFactory.getEME2000(),
1058                              (date, frame) -> new TimeStampedPVCoordinates(date,
1059                                                                            new PVCoordinates(new Vector3D( 1.234e7, -0.567e7, 9.876e6),
1060                                                                                              new Vector3D(-0.772e4,  5.002e4, 4.892e2))));
1061             Assertions.fail("an exception should have been thrown");
1062         } catch (OrekitException oe) {
1063             Assertions.assertEquals(OrekitMessages.CCSDS_INVALID_FRAME, oe.getSpecifier());
1064             Assertions.assertEquals(ext.getName(), oe.getParts()[0]);
1065         }
1066 
1067     }
1068 
1069     @Test
1070     public void testAttitudeQuaternion() {
1071         doTestAttitude("/ccsds/adm/apm/APM-quaternion.xml");
1072     }
1073 
1074     @Test
1075     public void testAtitudeQuaternionRates() {
1076         doTestAttitude("/ccsds/adm/apm/APM-quaternion-rates.xml");
1077     }
1078 
1079     @Test
1080     public void testAtitudeQuaternionAngvel() {
1081         doTestAttitude("/ccsds/adm/apm/APM-quaternion-angvel.xml");
1082     }
1083 
1084     @Test
1085     public void testAtitudeQuaternionEulerAnglesRates() {
1086         doTestAttitude("/ccsds/adm/apm/APM-quaternion-euler-angles-rates.xml");
1087     }
1088 
1089     @Test
1090     public void testAtitudeQuaternionEulerRates() {
1091         doTestAttitude("/ccsds/adm/apm/APM-quaternion-euler-rates.xml");
1092     }
1093 
1094     @Test
1095     public void testAtitudeEulerAnglesRates() {
1096         doTestAttitude("/ccsds/adm/apm/APM-euler-angles-rates.xml");
1097     }
1098 
1099     @Test
1100     public void testAtitudeEulerAnglesAngvel() {
1101         doTestAttitude("/ccsds/adm/apm/APM-euler-angles-angvel.xml");
1102     }
1103 
1104     @Test
1105     public void testAtitudeEulerAngles() {
1106         doTestAttitude("/ccsds/adm/apm/APM-euler-angles.xml");
1107     }
1108 
1109     @Test
1110     public void testAtitudeSpin() {
1111         doTestAttitude("/ccsds/adm/apm/APM-spin.xml");
1112     }
1113 
1114     @Test
1115     public void testAtitudeSpinNutation() {
1116         doTestAttitude("/ccsds/adm/apm/APM-spin-nutation.xml");
1117     }
1118 
1119     @Test
1120     public void testAtitudeSpinNutationMomentum() {
1121         doTestAttitude("/ccsds/adm/apm/APM-spin-nutation-momentum.xml");
1122     }
1123 
1124     private void doTestAttitude(final String name) {
1125         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1126         final Apm apm = new ParserBuilder().
1127                         withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1128                         buildApmParser().
1129                         parseMessage(source);
1130         final Attitude attitude = apm.getAttitude(FramesFactory.getEME2000(), null);
1131         Assertions.assertEquals(0.0,
1132                                 attitude.getDate().durationFrom(new AbsoluteDate(2008, 4, 7, TimeScalesFactory.getUTC())),
1133                                 1.0e-15);
1134         final double sign = FastMath.copySign(1.0, attitude.getRotation().getQ0());
1135         Assertions.assertEquals( 0.6184633325084984,  sign * attitude.getRotation().getQ0(), 1.0e-15);
1136         Assertions.assertEquals(-0.6447327809733585,  sign * attitude.getRotation().getQ1(), 1.0e-15);
1137         Assertions.assertEquals(-0.3463409538535587,  sign * attitude.getRotation().getQ2(), 1.0e-15);
1138         Assertions.assertEquals(-0.28613054916357517, sign * attitude.getRotation().getQ3(), 1.0e-15);
1139 
1140     }
1141 
1142     @Test
1143     public void testNoLogicalBlocks() {
1144         try {
1145             final String name = "/ccsds/adm/apm/APM-no-logical-blocks.txt";
1146             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1147             new ParserBuilder().
1148             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1149             buildApmParser().
1150             parseMessage(source);
1151             Assertions.fail("an exception should have been thrown");
1152         } catch (OrekitException oe) {
1153             Assertions.assertEquals(OrekitMessages.CCSDS_INCOMPLETE_DATA, oe.getSpecifier());
1154         }
1155     }
1156 
1157     @Test
1158     public void testNoAttitude() {
1159         final String name = "/ccsds/adm/apm/APM-no-attitude.txt";
1160         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1161         final Apm apm = new ParserBuilder().
1162                         withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1163                         buildApmParser().
1164                         parseMessage(source);
1165         try {
1166             apm.getAttitude(FramesFactory.getGCRF(), null);
1167             Assertions.fail("an exception should have been thrown");
1168         } catch (OrekitException oe) {
1169             Assertions.assertEquals(OrekitMessages.CCSDS_INCOMPLETE_DATA, oe.getSpecifier());
1170         }
1171     }
1172 
1173     @Test
1174     public void testNotImplementedTimeSystems() {
1175         try {
1176             final String name = "/ccsds/adm/apm/APM-inconsistent-time-systems.txt";
1177             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1178             new ParserBuilder().
1179             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1180             buildApmParser().
1181             parseMessage(source);
1182             Assertions.fail("an exception should have been thrown");
1183         } catch (OrekitException oe) {
1184             Assertions.assertEquals(OrekitMessages.CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED, oe.getSpecifier());
1185             Assertions.assertEquals("BCE", oe.getParts()[0]);
1186         }
1187     }
1188 
1189     @Test
1190     public void testWrongADMType() {
1191         final String name = "/ccsds/adm/aem/AEMExample01.txt";
1192         try {
1193             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1194             new ParserBuilder().
1195             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1196             buildApmParser().
1197             parseMessage(source);
1198             Assertions.fail("an exception should have been thrown");
1199         } catch (OrekitException oe) {
1200             Assertions.assertEquals(OrekitMessages.UNSUPPORTED_FILE_FORMAT, oe.getSpecifier());
1201             Assertions.assertEquals(name, oe.getParts()[0]);
1202         }
1203     }
1204 
1205     @Test
1206     public void testNumberFormatErrorType() {
1207         final String name = "/ccsds/adm/apm/APM-number-format-error.txt";
1208         try {
1209             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1210             new ParserBuilder().
1211             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1212             buildApmParser().
1213             parseMessage(source);
1214             Assertions.fail("an exception should have been thrown");
1215         } catch (OrekitException oe) {
1216             Assertions.assertEquals(OrekitMessages.UNABLE_TO_PARSE_ELEMENT_IN_FILE, oe.getSpecifier());
1217             Assertions.assertEquals("Q1", oe.getParts()[0]);
1218             Assertions.assertEquals(22, oe.getParts()[1]);
1219             Assertions.assertEquals(name, oe.getParts()[2]);
1220         }
1221     }
1222 
1223 
1224     @Test
1225     public void testNonExistentFile() throws URISyntaxException {
1226         final String realName = getClass().getResource("/ccsds/adm/apm/APMExample01.txt").toURI().getPath();
1227         final String wrongName = realName + "xxxxx";
1228         try {
1229             final DataSource source = new DataSource(wrongName, () -> getClass().getResourceAsStream(wrongName));
1230             new ParserBuilder().
1231             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1232             buildApmParser().
1233             parseMessage(source);
1234             Assertions.fail("an exception should have been thrown");
1235         } catch (OrekitException oe) {
1236             Assertions.assertEquals(OrekitMessages.UNABLE_TO_FIND_FILE, oe.getSpecifier());
1237             Assertions.assertEquals(wrongName, oe.getParts()[0]);
1238         }
1239     }
1240 
1241     @Test
1242     public void testMissingTwoSpacecraftFrames() {
1243         final String name = "/ccsds/adm/apm/APM-two-spacecraft-frames.txt";
1244         try {
1245             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1246             new ParserBuilder().
1247             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1248             buildApmParser().
1249             parseMessage(source);
1250             Assertions.fail("an exception should have been thrown");
1251         } catch (OrekitException oe) {
1252             Assertions.assertEquals(OrekitMessages.CCSDS_INVALID_FRAME, oe.getSpecifier());
1253             Assertions.assertEquals("INSTRUMENT_2", oe.getParts()[0]);
1254         }
1255     }
1256 
1257     @Test
1258     public void testNoSpacecraftFrames() {
1259         final String name = "/ccsds/adm/apm/APM-no-spacecraft-frames.txt";
1260         try {
1261             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1262             new ParserBuilder().
1263             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1264             buildApmParser().
1265             parseMessage(source);
1266             Assertions.fail("an exception should have been thrown");
1267         } catch (OrekitException oe) {
1268             Assertions.assertEquals(OrekitMessages.CCSDS_INVALID_FRAME, oe.getSpecifier());
1269             Assertions.assertEquals("EME2000", oe.getParts()[0]);
1270         }
1271     }
1272 
1273     @Test
1274     public void testMissingFrame() {
1275         final String name = "/ccsds/adm/apm/APM-missing-frame.txt";
1276         try {
1277             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1278             new ParserBuilder().
1279             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1280             buildApmParser().
1281             parseMessage(source);
1282             Assertions.fail("an exception should have been thrown");
1283         } catch (OrekitException oe) {
1284             Assertions.assertEquals(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, oe.getSpecifier());
1285             Assertions.assertEquals("Q_FRAME_A", oe.getParts()[0]);
1286         }
1287     }
1288 
1289     @Test
1290     public void testMissingQuaternionComponent() {
1291         final String name = "/ccsds/adm/apm/APM-missing-quaternion-component.txt";
1292         try {
1293             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1294             new ParserBuilder().
1295             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1296             buildApmParser().
1297             parseMessage(source);
1298             Assertions.fail("an exception should have been thrown");
1299         } catch (OrekitException oe) {
1300             Assertions.assertEquals(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, oe.getSpecifier());
1301             Assertions.assertEquals("Q{C|1|2|3}", oe.getParts()[0]);
1302         }
1303     }
1304 
1305     @Test
1306     public void testWrongKeyword() {
1307         final String name = "/ccsds/adm/apm/APM-wrong-keyword.txt";
1308         try {
1309             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1310             new ParserBuilder().
1311             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1312             buildApmParser().
1313             parseMessage(source);
1314             Assertions.fail("an exception should have been thrown");
1315         } catch (OrekitException oe) {
1316             Assertions.assertEquals(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, oe.getSpecifier());
1317             Assertions.assertEquals(12, ((Integer) oe.getParts()[0]).intValue());
1318             Assertions.assertTrue(((String) oe.getParts()[2]).startsWith("WRONG_KEYWORD"));
1319         }
1320     }
1321 
1322     @Test
1323     public void testWrongEulerSequence() {
1324         final String name = "/ccsds/adm/apm/APM-wrong-Euler-sequence.txt";
1325         try {
1326             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1327             new ParserBuilder().
1328             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1329             buildApmParser().
1330             parseMessage(source);
1331             Assertions.fail("an exception should have been thrown");
1332         } catch (OrekitException oe) {
1333             Assertions.assertEquals(OrekitMessages.CCSDS_INVALID_ROTATION_SEQUENCE, oe.getSpecifier());
1334             Assertions.assertEquals("331", oe.getParts()[0]);
1335             Assertions.assertEquals(33, ((Integer) oe.getParts()[1]).intValue());
1336             Assertions.assertEquals(name, oe.getParts()[2]);
1337         }
1338     }
1339 
1340     @Test
1341     public void testMissingEulerSequence() {
1342         final String name = "/ccsds/adm/apm/APM-missing-Euler-sequence.txt";
1343         try {
1344             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1345             new ParserBuilder().
1346             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1347             buildApmParser().
1348             parseMessage(source);
1349             Assertions.fail("an exception should have been thrown");
1350         } catch (OrekitException oe) {
1351             Assertions.assertEquals(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, oe.getSpecifier());
1352             Assertions.assertEquals("EULER_ROT_SEQ", oe.getParts()[0]);
1353         }
1354     }
1355 
1356     @Test
1357     public void testRepeatedEulerAngle() {
1358         final String name = "/ccsds/adm/apm/APM-repeated-Euler-angle.txt";
1359         try {
1360             final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1361             new ParserBuilder().
1362             withMissionReferenceDate(AbsoluteDate.J2000_EPOCH).
1363             buildApmParser().
1364             parseMessage(source);
1365             Assertions.fail("an exception should have been thrown");
1366         } catch (OrekitException oe) {
1367             Assertions.assertEquals(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, oe.getSpecifier());
1368             Assertions.assertEquals("{X|Y|Z}_ANGLE", oe.getParts()[0]);
1369         }
1370     }
1371 
1372     @Test
1373     public void testSpuriousMetaDataSection() {
1374         final String name = "/ccsds/adm/apm/spurious-metadata.xml";
1375         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1376         try {
1377             new ParserBuilder().buildApmParser().parseMessage(source);
1378             Assertions.fail("an exception should have been thrown");
1379         } catch (OrekitException oe) {
1380             Assertions.assertEquals(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, oe.getSpecifier());
1381             Assertions.assertEquals(15, ((Integer) oe.getParts()[0]).intValue());
1382             Assertions.assertEquals("metadata", oe.getParts()[2]);
1383         }
1384     }
1385 
1386     /** Unit tests for parsing an APM with a custom frame mapper. */
1387     @Test
1388     public void testFrameMapper() {
1389         // setup
1390         TimeScale tai = TimeScalesFactory.getTAI();
1391         AbsoluteDate expectedEpoch = new AbsoluteDate("2023-01-01T00:00:00.0000", tai);
1392         Frame parent = FramesFactory.getEME2000();
1393         Frame myJ2000 = new Frame(parent, Transform.IDENTITY, "MyJ2000");
1394         final Rotation rotation = new Rotation(RotationOrder.XYZ,
1395                 RotationConvention.FRAME_TRANSFORM,
1396                 Math.PI / 4, Math.PI / 2, Math.PI / 3);
1397         final Transform transform = new Transform(expectedEpoch, rotation);
1398         Frame scBodyFrame = new Frame(parent, transform, "SC_BODY_1");
1399         CcsdsFrameMapper mapper = new CcsdsFrameMapper() {
1400             @Override
1401             public Frame buildCcsdsFrame(FrameFacade orientation, AbsoluteDate epoch) {
1402                 if (epoch != null) {
1403                     throw new IllegalArgumentException("" + epoch);
1404                 }
1405                 if ("SC_BODY_1".equals(orientation.getName())) {
1406                     return scBodyFrame;
1407                 } else if ("EME2000".equals(orientation.getName())) {
1408                     return myJ2000;
1409                 } else {
1410                     throw new IllegalArgumentException(orientation + " " + epoch);
1411                 }
1412             }
1413 
1414             @Override
1415             public Frame buildCcsdsFrame(BodyFacade center,
1416                                          FrameFacade orientation,
1417                                          AbsoluteDate epoch) {
1418                 if (epoch != null) {
1419                     throw new IllegalArgumentException("" + epoch);
1420                 }
1421                 if ("ZZ".equals(center.getName()) &&
1422                         "SC_BODY_1".equals(orientation.getName())) {
1423                     return scBodyFrame;
1424                 } else if ("ZZ".equals(center.getName())
1425                         && "EME2000".equals(orientation.getName())) {
1426                     return myJ2000;
1427                 } else {
1428                 throw new IllegalArgumentException(
1429                         center + " " + orientation + " " + epoch);
1430                 }
1431             }
1432 
1433         };
1434         final String name = "/ccsds/adm/apm/APM-frame-mapper.txt";
1435         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
1436 
1437         // action
1438         final ApmParser parser = new ParserBuilder().withFrameMapper(mapper).buildApmParser();
1439         final Apm apm = parser.parseMessage(source);
1440         final BodyFacade center = apm.getMetadata().getCenter();
1441 
1442         // verify
1443         // Check data, metadata does not include frame
1444         final ApmData data = apm.getData();
1445         // quaternion block
1446         final AttitudeEndpoints qEndpoints = data.getQuaternionBlock().getEndpoints();
1447         MatcherAssert.assertThat(
1448                 mapper.buildCcsdsFrame(center, qEndpoints.getFrameA(), null),
1449                 Matchers.sameInstance(myJ2000));
1450         MatcherAssert.assertThat(
1451                 mapper.buildCcsdsFrame(center, qEndpoints.getFrameB(), null),
1452                 Matchers.sameInstance(scBodyFrame));
1453         MatcherAssert.assertThat(
1454                 mapper.buildCcsdsFrame(center, qEndpoints.getExternalFrame(), null),
1455                 Matchers.sameInstance(myJ2000));
1456         MatcherAssert.assertThat(
1457                 qEndpoints.getExternal(),
1458                 Matchers.sameInstance(myJ2000));
1459 
1460         // Euler block
1461         final AttitudeEndpoints eEndpoints = data.getEulerBlock().getEndpoints();
1462         MatcherAssert.assertThat(
1463                 mapper.buildCcsdsFrame(eEndpoints.getFrameA(), null),
1464                 Matchers.sameInstance(myJ2000));
1465         MatcherAssert.assertThat(
1466                 mapper.buildCcsdsFrame(eEndpoints.getFrameB(), null),
1467                 Matchers.sameInstance(scBodyFrame));
1468         MatcherAssert.assertThat(
1469                 mapper.buildCcsdsFrame(eEndpoints.getExternalFrame(), null),
1470                 Matchers.sameInstance(myJ2000));
1471         MatcherAssert.assertThat(
1472                 eEndpoints.getExternal(),
1473                 Matchers.sameInstance(myJ2000));
1474 
1475         // spin stabilized block
1476         final AttitudeEndpoints sEndpoints = data.getSpinStabilizedBlock().getEndpoints();
1477         MatcherAssert.assertThat(
1478                 mapper.buildCcsdsFrame(sEndpoints.getFrameA(), null),
1479                 Matchers.sameInstance(myJ2000));
1480         MatcherAssert.assertThat(
1481                 mapper.buildCcsdsFrame(sEndpoints.getFrameB(), null),
1482                 Matchers.sameInstance(scBodyFrame));
1483         MatcherAssert.assertThat(
1484                 mapper.buildCcsdsFrame(sEndpoints.getExternalFrame(), null),
1485                 Matchers.sameInstance(myJ2000));
1486         MatcherAssert.assertThat(
1487                 sEndpoints.getExternal(),
1488                 Matchers.sameInstance(myJ2000));
1489 
1490         // angular velocity block
1491         final AttitudeEndpoints aEndpoints = data.getAngularVelocityBlock().getEndpoints();
1492         MatcherAssert.assertThat(
1493                 mapper.buildCcsdsFrame(aEndpoints.getFrameA(), null),
1494                 Matchers.sameInstance(myJ2000));
1495         MatcherAssert.assertThat(
1496                 mapper.buildCcsdsFrame(aEndpoints.getFrameB(), null),
1497                 Matchers.sameInstance(scBodyFrame));
1498         MatcherAssert.assertThat(
1499                 mapper.buildCcsdsFrame(aEndpoints.getExternalFrame(), null),
1500                 Matchers.sameInstance(myJ2000));
1501         MatcherAssert.assertThat(
1502                 aEndpoints.getExternal(),
1503                 Matchers.sameInstance(myJ2000));
1504 
1505         // inertia block
1506         final FrameFacade inertiaFrame = data.getInertiaBlock().getFrame();
1507         MatcherAssert.assertThat(
1508                 mapper.buildCcsdsFrame(inertiaFrame, null), Matchers.sameInstance(scBodyFrame));
1509 
1510         // maneuver block
1511         final FrameFacade maneuverFrame = data.getManeuver(0).getFrame();
1512         MatcherAssert.assertThat(
1513                 mapper.buildCcsdsFrame(maneuverFrame, null), Matchers.sameInstance(scBodyFrame));
1514     }
1515 
1516     /** Test deprecated constructor. Can be removed in 14.0. */
1517     @Test
1518     @Deprecated
1519     public void testDeprecatedConstructor() {
1520         // action
1521         ApmParser actual = new ApmParser(
1522                 null, true, null, null, null, new Function[0]);
1523 
1524         // verify
1525         MatcherAssert.assertThat(actual.getFrameMapper(),
1526                 Matchers.is(new OrekitCcsdsFrameMapper()));
1527     }
1528 
1529 }