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.odm.omm;
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.Arrays;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.function.Function;
28  
29  import org.hamcrest.MatcherAssert;
30  import org.hamcrest.Matchers;
31  import org.hipparchus.linear.Array2DRowRealMatrix;
32  import org.hipparchus.util.FastMath;
33  import org.hipparchus.util.MathUtils;
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.bodies.CelestialBodyFactory;
39  import org.orekit.data.DataContext;
40  import org.orekit.data.DataSource;
41  import org.orekit.errors.OrekitException;
42  import org.orekit.errors.OrekitMessages;
43  import org.orekit.files.ccsds.definitions.BodyFacade;
44  import org.orekit.files.ccsds.definitions.CcsdsFrameMapper;
45  import org.orekit.files.ccsds.definitions.FrameFacade;
46  import org.orekit.files.ccsds.definitions.OrekitCcsdsFrameMapper;
47  import org.orekit.files.ccsds.ndm.ParserBuilder;
48  import org.orekit.files.ccsds.ndm.WriterBuilder;
49  import org.orekit.files.ccsds.ndm.odm.CartesianCovariance;
50  import org.orekit.files.ccsds.ndm.odm.KeplerianElements;
51  import org.orekit.files.ccsds.ndm.odm.SpacecraftParameters;
52  import org.orekit.files.ccsds.ndm.odm.UserDefined;
53  import org.orekit.files.ccsds.utils.generation.Generator;
54  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
55  import org.orekit.files.ccsds.utils.lexical.ParseToken;
56  import org.orekit.files.ccsds.utils.lexical.TokenType;
57  import org.orekit.frames.Frame;
58  import org.orekit.frames.FramesFactory;
59  import org.orekit.frames.LOFType;
60  import org.orekit.frames.Transform;
61  import org.orekit.propagation.analytical.tle.TLE;
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  
69  public class OmmParserTest {
70  
71      @BeforeEach
72      public void setUp()
73          throws Exception {
74          Utils.setDataRoot("regular-data");
75      }
76  
77      @Test
78      public void testParseOMM1() {
79          // simple test for OMM file, contains p/v entries and other mandatory
80          // data.
81          final String ex = "/ccsds/odm/omm/OMMExample1.txt";
82          final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
83  
84          // initialize parser
85          final OmmParser parser = new ParserBuilder().withMu(398600e9).withDefaultMass(1000.0).buildOmmParser();
86          final Omm   file   = parser.parseMessage(source);
87  
88          // Check Header Block;
89          Assertions.assertEquals(3.0, file.getHeader().getFormatVersion(), 1.0e-10);
90          Assertions.assertEquals(new AbsoluteDate(2007, 3, 6, 16, 0, 0,
91                                               TimeScalesFactory.getUTC()),
92                                               file.getHeader().getCreationDate());
93          Assertions.assertEquals("NOAA/USA", file.getHeader().getOriginator());
94          Assertions.assertNull(file.getHeader().getMessageId());
95  
96          // Check Metadata Block;
97  
98          Assertions.assertEquals("GOES 9", file.getMetadata().getObjectName());
99          Assertions.assertEquals("1995-025A", file.getMetadata().getObjectID());
100         Assertions.assertEquals("EARTH", file.getMetadata().getCenter().getName());
101         Assertions.assertNotNull(file.getMetadata().getCenter().getBody());
102         Assertions.assertEquals(CelestialBodyFactory.getEarth(), file.getMetadata().getCenter().getBody());
103         Assertions.assertEquals(FramesFactory.getTEME(), file.getMetadata().getFrame());
104         Assertions.assertEquals("UTC",      file.getMetadata().getTimeSystem().name());
105         Assertions.assertEquals("SGP/SGP4", file.getMetadata().getMeanElementTheory());
106         Assertions.assertEquals("TEME", file.getMetadata().getFrame().toString());
107         Assertions.assertTrue(file.getData().getTLEBlock().getComments().isEmpty());
108 
109         // Check Mean Keplerian elements data block;
110         KeplerianElements kep = file.getData().getKeplerianElementsBlock();
111         Assertions.assertEquals(new AbsoluteDate(2007, 3, 5, 10, 34, new TimeOffset(41L, 426400000000000000L),
112                                              TimeScalesFactory.getUTC()),
113                             file.getDate());
114         Assertions.assertEquals(1.00273272 * FastMath.PI / 43200.0, kep.getMeanMotion(), 1e-10);
115         Assertions.assertEquals(0.0005013, kep.getE(), 1e-10);
116         Assertions.assertEquals(FastMath.toRadians(3.0539), kep.getI(), 1e-10);
117         Assertions.assertEquals(FastMath.toRadians(81.7939), kep.getRaan(), 1e-10);
118         Assertions.assertEquals(FastMath.toRadians(249.2363), kep.getPa(), 1e-10);
119         Assertions.assertEquals(FastMath.toRadians(150.1602), kep.getAnomaly(), 1e-10);
120         Assertions.assertEquals(398600.8 * 1e9, kep.getMu(), 1e-10);
121 
122 
123         // Check TLE Related Parameters data block;
124         OmmTle tle = file.getData().getTLEBlock();
125         Assertions.assertEquals(0, tle.getEphemerisType());
126         Assertions.assertEquals('U', tle.getClassificationType());
127         int[] noradIDExpected = new int[23581];
128         int[] noradIDActual = new int[tle.getNoradID()];
129         Assertions.assertEquals(noradIDExpected[0], noradIDActual[0]);
130         Assertions.assertEquals(925, tle.getElementSetNumber());
131         int[] revAtEpochExpected = new int[4316];
132         int[] revAtEpochActual = new int[tle.getRevAtEpoch()];
133         Assertions.assertEquals(revAtEpochExpected[0], revAtEpochActual[0]);
134         Assertions.assertEquals(0.0001, tle.getBStar(), 1e-10);
135         Assertions.assertEquals(-0.00000113 * FastMath.PI / 1.86624e9, tle.getMeanMotionDot(), 1e-12);
136         Assertions.assertEquals(0.0 * FastMath.PI / 5.3747712e13, tle.getMeanMotionDotDot(), 1e-10);
137         Assertions.assertEquals(1995, file.getMetadata().getLaunchYear());
138         Assertions.assertEquals(25, file.getMetadata().getLaunchNumber());
139         Assertions.assertEquals("A", file.getMetadata().getLaunchPiece());
140         file.generateKeplerianOrbit();
141         try {
142             file.generateSpacecraftState();
143         } catch (OrekitException orekitException) {
144             Assertions.assertEquals(OrekitMessages.CCSDS_UNKNOWN_SPACECRAFT_MASS, orekitException.getSpecifier());
145         }
146         TLE generated = file.generateTLE();
147         Assertions.assertEquals("1 23581U 95025A   07064.44075725 -.00000056  00000-0  10000-3 0  9256", generated.getLine1());
148         Assertions.assertEquals("2 23581   3.0539  81.7939 0005013 249.2363 150.1602  1.00273272 43169", generated.getLine2());
149     }
150 
151     @Test
152     public void testParseOMM2KVN() throws URISyntaxException {
153         String name = "/ccsds/odm/omm/OMMExample2.txt";
154         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
155         final OmmParser parser = new ParserBuilder().withMu(Constants.EIGEN5C_EARTH_MU).buildOmmParser();
156 
157         validateOMM2(parser.parseMessage(source));
158     }
159 
160     @Test
161     public void testParseOMM2XML() throws URISyntaxException {
162         String name = "/ccsds/odm/omm/OMMExample2.xml";
163         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
164         final OmmParser parser = new ParserBuilder().withMu(Constants.EIGEN5C_EARTH_MU).buildOmmParser();
165 
166         validateOMM2(parser.parseMessage(source));
167     }
168 
169     @Test
170     public void testIssue906() throws URISyntaxException {
171         String name = "/ccsds/odm/omm/OMM-with-units.xml";
172         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
173         final OmmParser parser = new ParserBuilder().withMu(Constants.EIGEN5C_EARTH_MU).buildOmmParser();
174 
175         validateOMM2(parser.parseMessage(source));
176     }
177 
178     @Test
179     public void testWriteOMM3() throws URISyntaxException, IOException {
180         final String name = "/ccsds/odm/omm/OMMExample2.xml";
181         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
182         OmmParser parser = new ParserBuilder().withMu(Constants.EIGEN5C_EARTH_MU).buildOmmParser();
183         final Omm original = parser.parseMessage(source);
184 
185         // write the parsed file back to a characters array
186         final CharArrayWriter caw = new CharArrayWriter();
187         final Generator generator = new KvnGenerator(caw, OmmWriter.KVN_PADDING_WIDTH, "dummy",
188                                                      Constants.JULIAN_DAY, 60);
189         new WriterBuilder().buildOmmWriter().writeMessage(generator, original);
190 
191         // reparse the written file
192         final byte[]     bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
193         final DataSource source2 = new DataSource(name, () -> new ByteArrayInputStream(bytes));
194         final Omm    rebuilt = new ParserBuilder().buildOmmParser().parseMessage(source2);
195         validateOMM2(rebuilt);
196 
197     }
198 
199     private void validateOMM2(final Omm file) {
200         Assertions.assertEquals(3.0, file.getHeader().getFormatVersion(), 1.0e-10);
201         Assertions.assertEquals(OmmMetadata.SGP_SGP4_THEORY, file.getMetadata().getMeanElementTheory());
202         final KeplerianElements kep = file.getData().getKeplerianElementsBlock();
203         Assertions.assertEquals(1.00273272, Constants.JULIAN_DAY * kep.getMeanMotion() / MathUtils.TWO_PI, 1e-10);
204         Assertions.assertTrue(Double.isNaN(file.getData().getMass()));
205         CartesianCovariance covariance = file.getData().getCovarianceBlock();
206         Assertions.assertEquals(FramesFactory.getTEME(), covariance.getReferenceFrame().asFrame());
207         Assertions.assertEquals(6, covariance.getCovarianceMatrix().getRowDimension());
208         Assertions.assertEquals(6, covariance.getCovarianceMatrix().getColumnDimension());
209         Assertions.assertEquals(1995, file.getMetadata().getLaunchYear());
210         Assertions.assertEquals(25, file.getMetadata().getLaunchNumber());
211         Assertions.assertEquals("A", file.getMetadata().getLaunchPiece());
212         Assertions.assertEquals(0.0001, file.getData().getTLEBlock().getBStar(), 1.0e-15);
213         Assertions.assertTrue(Double.isNaN(file.getData().getTLEBlock().getBTerm()));
214         file.generateKeplerianOrbit();
215 
216         Array2DRowRealMatrix covMatrix = new Array2DRowRealMatrix(6, 6);
217         double[] column1 = {
218             333.1349476038534, 461.8927349220216,
219             -307.0007847730449, -0.3349365033922630,
220             -0.2211832501084875, -0.3041346050686871
221         };
222         double[] column2 = {
223             461.8927349220216, 678.2421679971363,
224             -422.1234189514228, -0.4686084221046758,
225             -0.2864186892102733, -0.4989496988610662
226         };
227         double[] column3 = {
228             -307.0007847730449, -422.1234189514228,
229             323.1931992380369, 0.2484949578400095,
230             0.1798098699846038, 0.3540310904497689
231         };
232         double[] column4 = {
233             -0.3349365033922630, -0.4686084221046758,
234             0.2484949578400095, 0.0004296022805587290,
235             0.0002608899201686016, 0.0001869263192954590
236         };
237         double[] column5 = {
238             -0.2211832501084875, -0.2864186892102733,
239             0.1798098699846038, 0.0002608899201686016,
240             0.0001767514756338532, 0.0001008862586240695
241         };
242         double[] column6 = {
243             -0.3041346050686871, -0.4989496988610662,
244             0.3540310904497689, 0.0001869263192954590,
245             0.0001008862586240695, 0.0006224444338635500
246         };
247         covMatrix.setColumn(0, column1);
248         covMatrix.setColumn(1, column2);
249         covMatrix.setColumn(2, column3);
250         covMatrix.setColumn(3, column4);
251         covMatrix.setColumn(4, column5);
252         covMatrix.setColumn(5, column6);
253         for (int i = 0; i < 6; i++) {
254             for (int j = 0; j < 6; j++) {
255                 Assertions.assertEquals(covMatrix.getEntry(i, j),
256                                     covariance.getCovarianceMatrix().getEntry(i, j),
257                                     1e-15);
258             }
259         }
260 
261     }
262 
263     @Test
264     public void testParseOMM3() {
265         // simple test for OMM file, contains p/v entries and other mandatory
266         // data.
267         final String name = "/ccsds/odm/omm/OMMExample3.txt";
268         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
269         final AbsoluteDate missionReferenceDate = new AbsoluteDate(2000, 1, 1, DataContext.getDefault().getTimeScales().getUTC());
270         final OmmParser parser = new ParserBuilder().
271                                  withMu(Constants.EIGEN5C_EARTH_MU).
272                                  withMissionReferenceDate(missionReferenceDate).
273                                  withDefaultMass(1000.0).
274                                  buildOmmParser();
275 
276         final Omm file = parser.parseMessage(source);
277         final KeplerianElements kep = file.getData().getKeplerianElementsBlock();
278         Assertions.assertEquals(2.0, file.getHeader().getFormatVersion(), 1.0e-10);
279         Assertions.assertEquals(missionReferenceDate.shiftedBy(210840), file.getMetadata().getFrameEpoch());
280         Assertions.assertEquals(6800e3, kep.getA(), 1e-10);
281 
282         final SpacecraftParameters sp = file.getData().getSpacecraftParametersBlock();
283         Assertions.assertEquals(300, sp.getMass(), 1e-10);
284         Assertions.assertEquals(5, sp.getSolarRadArea(), 1e-10);
285         Assertions.assertEquals(0.001, sp.getSolarRadCoeff(), 1e-10);
286 
287         CartesianCovariance covariance = file.getData().getCovarianceBlock();
288         Assertions.assertNull(covariance.getReferenceFrame().asFrame());
289         Assertions.assertNull(covariance.getReferenceFrame().asCelestialBodyFrame());
290         Assertions.assertEquals(LOFType.TNW_INERTIAL, covariance.getReferenceFrame().asOrbitRelativeFrame().getLofType());
291 
292         UserDefined ud = file.getData().getUserDefinedBlock();
293         HashMap<String, String> userDefinedParameters = new HashMap<>();
294         userDefinedParameters.put("EARTH_MODEL", "WGS-84");
295         Assertions.assertEquals(userDefinedParameters, ud.getParameters());
296         Assertions.assertEquals(Arrays.asList("this is a comment", "here is another one"),
297                             file.getHeader().getComments());
298         Assertions.assertEquals(Collections.singletonList("this comment doesn't say much"),
299                             file.getMetadata().getComments());
300         Assertions.assertEquals(Collections.singletonList("the following data is what we're looking for"),
301                             file.getData().getKeplerianElementsBlock().getComments());
302         Assertions.assertEquals(Collections.singletonList("spacecraft data"),
303                             file.getData().getSpacecraftParametersBlock().getComments());
304         Assertions.assertEquals(Collections.singletonList("Covariance matrix"),
305                             file.getData().getCovarianceBlock().getComments());
306         Assertions.assertEquals(1995, file.getMetadata().getLaunchYear());
307         Assertions.assertEquals(25, file.getMetadata().getLaunchNumber());
308         Assertions.assertEquals("A", file.getMetadata().getLaunchPiece());
309         file.generateSpacecraftState();
310         file.generateKeplerianOrbit();
311 
312     }
313 
314     @Test
315     public void testParseOMM5() {
316         // simple test for OMM file, contains SGP4-XP elements with BTERM
317         final String name = "/ccsds/odm/omm/OMMExample5.txt";
318         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
319         final AbsoluteDate missionReferenceDate = new AbsoluteDate(2000, 1, 1, DataContext.getDefault().getTimeScales().getUTC());
320         final OmmParser parser = new ParserBuilder().
321                                  withMu(Constants.EIGEN5C_EARTH_MU).
322                                  withMissionReferenceDate(missionReferenceDate).
323                                  buildOmmParser();
324 
325         final Omm file = parser.parseMessage(source);
326         Assertions.assertEquals(3.0, file.getHeader().getFormatVersion(), 1.0e-10);
327         Assertions.assertEquals(OmmMetadata.SGP4_XP_THEORY, file.getMetadata().getMeanElementTheory());
328         final KeplerianElements kep = file.getData().getKeplerianElementsBlock();
329         Assertions.assertEquals(1.00273272, Constants.JULIAN_DAY * kep.getMeanMotion() / MathUtils.TWO_PI, 1e-10);
330         Assertions.assertTrue(Double.isNaN(file.getData().getMass()));
331         Assertions.assertTrue(Double.isNaN(file.getData().getTLEBlock().getBStar()));
332         Assertions.assertEquals(0.0015, file.getData().getTLEBlock().getBTerm(), 1.0e-15);
333     }
334 
335     @Test
336     public void testWrongKeyword() {
337         // simple test for OMM file, contains p/v entries and other mandatory
338         // data.
339         final String name = "/ccsds/odm/omm/OMM-wrong-keyword.txt";
340         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
341         final OmmParser parser = new ParserBuilder().
342                                  withMu(Constants.EIGEN5C_EARTH_MU).
343                                  withMissionReferenceDate(new AbsoluteDate()).
344                                  withDefaultMass(1000.0).
345                                  buildOmmParser();
346         try {
347             parser.parseMessage(source);
348             Assertions.fail("an exception should have been thrown");
349         } catch (OrekitException oe) {
350             Assertions.assertEquals(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, oe.getSpecifier());
351             Assertions.assertEquals(9, ((Integer) oe.getParts()[0]).intValue());
352             Assertions.assertTrue(((String) oe.getParts()[2]).startsWith("WRONG_KEYWORD"));
353         }
354     }
355 
356     @Test
357     public void testEmptyObjectID() {
358         // test with an OMM file that does not fulfills CCSDS standard and uses an empty OBJECT_ID
359         final String name = "/ccsds/odm/omm/OMM-empty-object-id.txt";
360         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
361         final OmmParser parser = new ParserBuilder().
362                                  withMu(Constants.EIGEN5C_EARTH_MU).
363                                  withMissionReferenceDate(new AbsoluteDate()).
364                                  withDefaultMass(1000.0).
365                                  buildOmmParser();
366         try {
367             parser.parseMessage(source);
368             Assertions.fail("an exception should have been thrown");
369         } catch (OrekitException oe) {
370             Assertions.assertEquals(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, oe.getSpecifier());
371             Assertions.assertEquals("OBJECT_ID", oe.getParts()[0]);
372         }
373 
374         final String replacement = "replacement-object-id";
375         final Omm omm = new ParserBuilder().
376                         withMu(Constants.EIGEN5C_EARTH_MU).
377                         withMissionReferenceDate(new AbsoluteDate()).
378                         withDefaultMass(1000.0).
379                         withFilter(token -> {
380                             if ("OBJECT_ID".equals(token.getName()) &&
381                                             (token.getRawContent() == null || token.getRawContent().isEmpty())) {
382                                 // replace null/empty entries with specified value
383                                 return Collections.singletonList(new ParseToken(token.getType(), token.getName(),
384                                                                                 replacement, token.getUnits(),
385                                                                                 token.getLineNumber(), token.getFileName()));
386                             } else {
387                                 return Collections.singletonList(token);
388                             }
389                         }).
390                         buildOmmParser().
391                         parseMessage(source);
392         // note that object id is always converted to uppercase during parsing
393         Assertions.assertEquals(replacement.toUpperCase(), omm.getMetadata().getObjectID());
394 
395     }
396 
397     @Test
398     public void testEmptyObjectIDXml() {
399         // test with an OMM file that does not fulfills CCSDS standard and uses an empty OBJECT_ID
400         String name = "/ccsds/odm/omm/OMM-empty-object-id.xml";
401         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
402         final OmmParser parser = new ParserBuilder().
403                         withMu(Constants.EIGEN5C_EARTH_MU).
404                         withMissionReferenceDate(new AbsoluteDate()).
405                         withDefaultMass(1000.0).
406                         buildOmmParser();
407         try {
408             parser.parseMessage(source);
409             Assertions.fail("an exception should have been thrown");
410         } catch (OrekitException oe) {
411             Assertions.assertEquals(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, oe.getSpecifier());
412             Assertions.assertEquals("OBJECT_ID", oe.getParts()[0]);
413         }
414 
415         final String replacement = "replacement-object-id";
416         final Omm omm = new ParserBuilder().
417                         withMu(Constants.EIGEN5C_EARTH_MU).
418                         withMissionReferenceDate(new AbsoluteDate()).
419                         withDefaultMass(1000.0).
420                         withFilter(token -> {
421                             if ("OBJECT_ID".equals(token.getName()) &&
422                                 (token.getRawContent() == null || token.getRawContent().isEmpty())) {
423                                 // replace null/empty entries with specified value
424                                 return Collections.singletonList(new ParseToken(token.getType(), token.getName(),
425                                                                                 replacement, token.getUnits(),
426                                                                                 token.getLineNumber(), token.getFileName()));
427                             } else {
428                                 return Collections.singletonList(token);
429                             }
430                         }).
431                         buildOmmParser().
432                         parseMessage(source);
433         // note that object id is always converted to uppercase during parsing
434         Assertions.assertEquals(replacement.toUpperCase(), omm.getMetadata().getObjectID());
435     }
436 
437     @Test
438     public void testRemoveUserData() {
439         final String name = "/ccsds/odm/omm/OMMExample3.txt";
440         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
441         final AbsoluteDate missionReferenceDate = new AbsoluteDate(2000, 1, 1, DataContext.getDefault().getTimeScales().getUTC());
442         final Omm omm = new ParserBuilder().
443                         withMu(Constants.EIGEN5C_EARTH_MU).
444                         withMissionReferenceDate(missionReferenceDate).
445                         withDefaultMass(1000.0).
446                         withFilter(token -> {
447                             if (token.getName().startsWith("USER_DEFINED")) {
448                                 return Collections.emptyList();
449                             } else {
450                                 return Collections.singletonList(token);
451                             }
452                         }).
453                         buildOmmParser().
454                         parseMessage(source);
455         Assertions.assertNull(omm.getData().getUserDefinedBlock());
456     }
457 
458     @Test
459     public void testChangeVersionAndAddMessageId() {
460         final String name = "/ccsds/odm/omm/OMMExample3.txt";
461         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
462         final AbsoluteDate missionReferenceDate = new AbsoluteDate(2000, 1, 1, DataContext.getDefault().getTimeScales().getUTC());
463         final String myMessageId = "custom-message-id";
464         final Omm omm = new ParserBuilder().
465                         withMu(Constants.EIGEN5C_EARTH_MU).
466                         withMissionReferenceDate(missionReferenceDate).
467                         withDefaultMass(1000.0).
468                         withFilter(token -> {
469                             if ("CCSDS_OMM_VERS".equals(token.getName())) {
470                                 // enforce ODM V3
471                                 return Collections.singletonList(new ParseToken(token.getType(), token.getName(),
472                                                                                 "3.0", token.getUnits(),
473                                                                                 token.getLineNumber(), token.getFileName()));
474                             } else {
475                                 return Collections.singletonList(token);
476                             }
477                         }).
478                         withFilter(token -> {
479                             if ("ORIGINATOR".equals(token.getName())) {
480                                 // add generated message ID after ORIGINATOR entry
481                                 return Arrays.asList(token,
482                                                      new ParseToken(TokenType.ENTRY, "MESSAGE_ID",
483                                                                     myMessageId, null,
484                                                                     -1, token.getFileName()));
485                             } else {
486                                 return Collections.singletonList(token);
487                             }
488                         }).
489                         buildOmmParser().
490                         parseMessage(source);
491         Assertions.assertEquals(3.0, omm.getHeader().getFormatVersion(), 1.0e-10);
492         Assertions.assertEquals("NOAA/USA", omm.getHeader().getOriginator());
493         Assertions.assertEquals(myMessageId, omm.getHeader().getMessageId());
494     }
495 
496     @Test
497     public void testOrbitFileInterface() {
498         // simple test for OMM file, contains p/v entries and other mandatory data.
499         final String name = "/ccsds/odm/omm/OMMExample1.txt";
500         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
501 
502         // initialize parser
503         final OmmParser parser = new ParserBuilder().
504                         withMu(398600e9).
505                         withMissionReferenceDate(new AbsoluteDate()).
506                         withDefaultMass(1000.0).
507                         buildOmmParser();
508 
509         final Omm file = parser.parseMessage(source);
510 
511         final String satId = "1995-025A";
512         Assertions.assertEquals(satId, file.getMetadata().getObjectID());
513 
514     }
515 
516     @Test
517     public void testWrongODMType() {
518         final String name = "/ccsds/odm/oem/OEMExample1.txt";
519         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
520         try {
521             new ParserBuilder().
522             withConventions(IERSConventions.IERS_1996).
523             withMu(Constants.EIGEN5C_EARTH_MU).
524             withMissionReferenceDate(new AbsoluteDate()).
525             withDefaultMass(1000.0).
526             buildOmmParser().
527             parseMessage(source);
528         } catch (OrekitException oe) {
529             Assertions.assertEquals(OrekitMessages.UNSUPPORTED_FILE_FORMAT, oe.getSpecifier());
530             Assertions.assertEquals(name, oe.getParts()[0]);
531         }
532     }
533 
534     @Test
535     public void testSpuriousMetaDataSection() {
536         final String name = "/ccsds/odm/omm/spurious-metadata.xml";
537         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
538         try {
539             new ParserBuilder().buildOmmParser().parseMessage(source);
540             Assertions.fail("an exception should have been thrown");
541         } catch (OrekitException oe) {
542             Assertions.assertEquals(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, oe.getSpecifier());
543             Assertions.assertEquals(17, ((Integer) oe.getParts()[0]).intValue());
544             Assertions.assertEquals("metadata", oe.getParts()[2]);
545         }
546     }
547 
548     @Test
549     public void testNumberFormatErrorType() {
550         final String name = "/ccsds/odm/omm/OMM-number-format-error.txt";
551         final DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
552         try {
553             new ParserBuilder().
554             withConventions(IERSConventions.IERS_1996).
555             withMu(Constants.EIGEN5C_EARTH_MU).
556             withMissionReferenceDate(new AbsoluteDate()).
557             withDefaultMass(1000.0).
558             buildOmmParser().
559             parseMessage(source);
560         } catch (OrekitException oe) {
561             Assertions.assertEquals(OrekitMessages.UNABLE_TO_PARSE_ELEMENT_IN_FILE, oe.getSpecifier());
562             Assertions.assertEquals("ARG_OF_PERICENTER", oe.getParts()[0]);
563             Assertions.assertEquals(15, oe.getParts()[1]);
564             Assertions.assertEquals(name, oe.getParts()[2]);
565         }
566     }
567 
568     @Test
569     public void testNonExistentFile() {
570         final String realName = "/ccsds/odm/omm/OMMExample1.txt";
571         final String wrongName = realName + "xxxxx";
572         final DataSource source = new DataSource(wrongName, () -> getClass().getResourceAsStream(wrongName));
573         try {
574             new ParserBuilder().
575             withConventions(IERSConventions.IERS_1996).
576             withMu(Constants.EIGEN5C_EARTH_MU).
577             withMissionReferenceDate(new AbsoluteDate()).
578             withDefaultMass(1000.0).
579             buildOmmParser().
580             parseMessage(source);
581             Assertions.fail("an exception should have been thrown");
582         } catch (OrekitException oe) {
583             Assertions.assertEquals(OrekitMessages.UNABLE_TO_FIND_FILE, oe.getSpecifier());
584             Assertions.assertEquals(wrongName, oe.getParts()[0]);
585         }
586     }
587 
588     /** Check parsing an OMM with a custom frame mapper. */
589     @Test
590     public void testFrameMapping() {
591         // setup
592         String name = "/ccsds/odm/omm/OMMExample3_utc.txt";
593         DataSource source = new DataSource(name, () -> getClass().getResourceAsStream(name));
594         DataContext context = DataContext.getDefault();
595         TimeScale utc = context.getTimeScales().getUTC();
596         Frame tod = context.getFrames().getTOD(false);
597         Frame myTod = new Frame(tod, Transform.IDENTITY, "myTod", true);
598         AbsoluteDate expectedEpoch = new AbsoluteDate("2000-003T10:34:00", utc);
599         Frame myTnw = new Frame(tod, Transform.IDENTITY, "myTnw", false);
600 
601         // action
602         final OmmParser ommParser = new ParserBuilder(context)
603                 .withFrameMapper(new CcsdsFrameMapper() {
604                     @Override
605                     public Frame buildCcsdsFrame(FrameFacade orientation,
606                                                  AbsoluteDate frameEpoch) {
607                         if ("TNW".equals(orientation.getName())) {
608                             return myTnw;
609                         }
610                         throw new UnsupportedOperationException();
611                     }
612 
613                     @Override
614                     public Frame buildCcsdsFrame(BodyFacade center,
615                                                  FrameFacade orientation,
616                                                  AbsoluteDate frameEpoch) {
617                         if ("EARTH".equals(center.getName()) &&
618                                 "TOD".equals(orientation.getName()) &&
619                                 expectedEpoch.equals(frameEpoch)) {
620                             return myTod;
621                         }
622                         throw new UnsupportedOperationException();
623                     }
624                 })
625                 .buildOmmParser();
626         final Omm omm = ommParser.parseMessage(source);
627 
628         // verify
629         MatcherAssert.assertThat(omm.getMetadata().getFrame(),
630                 Matchers.sameInstance(myTod));
631         MatcherAssert.assertThat(omm.generateKeplerianOrbit().getFrame(),
632                 Matchers.sameInstance(myTod));
633         MatcherAssert.assertThat(omm.generateSpacecraftState().getFrame(),
634                 Matchers.sameInstance(myTod));
635         MatcherAssert.assertThat(omm.getData().getCovarianceBlock().getFrame(),
636                 Matchers.sameInstance(myTnw));
637     }
638 
639     /** Test deprecated constructor. Can be removed in 14.0. */
640     @Test
641     @Deprecated
642     public void testDeprecatedConstructor() {
643         // action
644         OmmParser actual = new OmmParser(
645                 null, true, null, null, 0, 0, null, new Function[0]);
646 
647         // verify
648         MatcherAssert.assertThat(actual.getFrameMapper(),
649                 Matchers.is(new OrekitCcsdsFrameMapper()));
650     }
651 
652 }