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.oem;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.StringReader;
21  import java.nio.charset.StandardCharsets;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.List;
25  
26  import org.hamcrest.CoreMatchers;
27  import org.hamcrest.MatcherAssert;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.junit.jupiter.api.Assertions;
30  import org.junit.jupiter.api.BeforeEach;
31  import org.junit.jupiter.api.Test;
32  import org.orekit.Utils;
33  import org.orekit.attitudes.FrameAlignedProvider;
34  import org.orekit.bodies.CelestialBody;
35  import org.orekit.bodies.CelestialBodyFactory;
36  import org.orekit.bodies.GeodeticPoint;
37  import org.orekit.bodies.OneAxisEllipsoid;
38  import org.orekit.data.DataContext;
39  import org.orekit.data.DataSource;
40  import org.orekit.files.ccsds.definitions.CelestialBodyFrame;
41  import org.orekit.files.ccsds.definitions.CenterName;
42  import org.orekit.files.ccsds.definitions.FrameFacade;
43  import org.orekit.files.ccsds.definitions.ModifiedFrame;
44  import org.orekit.files.ccsds.definitions.TimeSystem;
45  import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
46  import org.orekit.files.ccsds.ndm.ParserBuilder;
47  import org.orekit.files.ccsds.ndm.WriterBuilder;
48  import org.orekit.files.ccsds.ndm.odm.OdmHeader;
49  import org.orekit.files.ccsds.utils.generation.Generator;
50  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
51  import org.orekit.frames.Frame;
52  import org.orekit.frames.FramesFactory;
53  import org.orekit.frames.TopocentricFrame;
54  import org.orekit.propagation.BoundedPropagator;
55  import org.orekit.time.AbsoluteDate;
56  import org.orekit.utils.Constants;
57  import org.orekit.utils.IERSConventions;
58  import org.orekit.utils.TimeStampedPVCoordinates;
59  
60  /**
61   * Check {@link StreamingOemWriter}.
62   *
63   * @author Evan Ward
64   */
65  public class StreamingOemWriterTest {
66      // As the default format for position is 3 digits after decimal point in km the max precision in m is 1
67      private static final double POSITION_PRECISION = 1; // in m
68      // As the default format for velocity is 5 digits after decimal point in km/s the max precision in m/s is 1e-2
69      private static final double VELOCITY_PRECISION = 1e-2; //in m/s
70  
71      /** Set Orekit data. */
72      @BeforeEach
73      public void setUp() {
74          Utils.setDataRoot("regular-data");
75      }
76  
77      /**
78       * Check guessing the frame center for some frames.
79       */
80      @Test
81      public void testGuessCenter() {
82          // action + verify
83          // check all CCSDS common center names
84          List<CenterName> centerNames = new ArrayList<>(Arrays.asList(CenterName.values()));
85          centerNames.remove(CenterName.EARTH_MOON);
86          for (CenterName centerName : centerNames) {
87              CelestialBody body = centerName.getCelestialBody();
88              String name = centerName.name().replace('_', ' ');
89              MatcherAssert.assertThat(CenterName.guessCenter(body.getInertiallyOrientedFrame()),
90                                       CoreMatchers.is(name));
91              MatcherAssert.assertThat(CenterName.guessCenter(body.getBodyOrientedFrame()),
92                                       CoreMatchers.is(name));
93          }
94          // Earth-Moon Barycenter is special
95          CelestialBody emb = CenterName.EARTH_MOON.getCelestialBody();
96          MatcherAssert.assertThat(CenterName.guessCenter(emb.getInertiallyOrientedFrame()),
97                                   CoreMatchers.is("EARTH-MOON BARYCENTER"));
98          MatcherAssert.assertThat(CenterName.guessCenter(emb.getBodyOrientedFrame()),
99                                   CoreMatchers.is("EARTH-MOON BARYCENTER"));
100         // check some special CCSDS frames
101         ModifiedFrame frame = new ModifiedFrame(FramesFactory.getEME2000(),
102                                                           CelestialBodyFrame.EME2000,
103                                                           CelestialBodyFactory.getMars(), "MARS");
104         MatcherAssert.assertThat(CenterName.guessCenter(frame), CoreMatchers.is("MARS"));
105 
106         // check unknown frame
107         Frame topo = new TopocentricFrame(new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
108                                                                Constants.WGS84_EARTH_FLATTENING,
109                                                                FramesFactory.getITRF(IERSConventions.IERS_2010, true)),
110                                           new GeodeticPoint(1.2, 2.3, 45.6),
111                                           "dummy");
112         MatcherAssert.assertThat(CenterName.guessCenter(topo), CoreMatchers.is("UNKNOWN"));
113     }
114 
115 
116     /**
117      * Check reading and writing an OEM both with and without using the step handler
118      * methods.
119      *
120      * @throws Exception on error.
121      */
122     @Test
123     public void testWriteOemStepHandler() throws Exception {
124         // setup
125         List<String> files =
126                 Arrays.asList("/ccsds/odm/oem/OEMExample5.txt", "/ccsds/odm/oem/OEMExample4.txt");
127         for (final String ex : files) {
128             final DataSource source0 =  new DataSource(ex, () -> getClass().getResourceAsStream(ex));
129             final OemParser parser  = new ParserBuilder().withMu(CelestialBodyFactory.getEarth().getGM()).buildOemParser();
130             Oem oem = parser.parseMessage(source0);
131 
132             OemSatelliteEphemeris satellite = oem.getSatellites().values().iterator().next();
133             OemSegment ephemerisBlock = satellite.getSegments().get(0);
134             double step = ephemerisBlock.
135                           getMetadata().
136                           getStopTime().
137                           durationFrom(ephemerisBlock.getMetadata().getStartTime()) /
138                           (ephemerisBlock.getCoordinates().size() - 1);
139             String originator = oem.getHeader().getOriginator();
140             OemSegment block = oem.getSegments().get(0);
141             String objectName = block.getMetadata().getObjectName();
142             String objectID = block.getMetadata().getObjectID();
143             Frame frame = satellite.getSegments().get(0).getInertialFrame();
144 
145             OdmHeader header = new OdmHeader();
146             header.setOriginator(originator);
147             OemMetadata metadata = new OemMetadata(1);
148             metadata.setObjectName(objectName);
149             metadata.setObjectID(objectID);
150             metadata.setTimeSystem(TimeSystem.UTC);
151             metadata.setCenter(ephemerisBlock.getMetadata().getCenter());
152             metadata.setReferenceFrame(FrameFacade.map(FramesFactory.getEME2000())); // will be overwritten
153             metadata.setStartTime(AbsoluteDate.J2000_EPOCH.shiftedBy(80 * Constants.JULIAN_CENTURY));
154             metadata.setStopTime(metadata.getStartTime().shiftedBy(Constants.JULIAN_YEAR));
155 
156 
157             // check using the Propagator / StepHandler interface
158             final StringBuilder buffer1 = new StringBuilder();
159             StreamingOemWriter writer = new StreamingOemWriter(new KvnGenerator(buffer1, OemWriter.KVN_PADDING_WIDTH, "some-name",
160                                                                                 Constants.JULIAN_DAY, 60),
161                                                                new WriterBuilder().buildOemWriter(),
162                                                                header, metadata);
163             BoundedPropagator propagator = satellite.getPropagator(new FrameAlignedProvider(frame));
164             propagator.setStepHandler(step, writer.newSegment());
165             propagator.propagate(propagator.getMinDate(), propagator.getMaxDate());
166             writer.close();
167 
168             // verify
169             final DataSource source1 = new DataSource("buffer",
170                                                     () -> new ByteArrayInputStream(buffer1.toString().getBytes(StandardCharsets.UTF_8)));
171             Oem generatedOem = new ParserBuilder().
172                                        withConventions(IERSConventions.IERS_2010).
173                                        withSimpleEOP(true).
174                                        withDataContext(DataContext.getDefault()).
175                                        withMu(CelestialBodyFactory.getEarth().getGM()).
176                                        withDefaultInterpolationDegree(1).
177                                        buildOemParser().
178                                        parseMessage(source1);
179             compareOems(oem, generatedOem, POSITION_PRECISION, VELOCITY_PRECISION);
180 
181             // check calling the methods directly
182             final StringBuilder buffer2 = new StringBuilder();
183             OemWriter oemWriter = new WriterBuilder().buildOemWriter();
184             try (Generator generator = new KvnGenerator(buffer2, OemWriter.KVN_PADDING_WIDTH, "another-name",
185                                                         Constants.JULIAN_DAY, 60)) {
186                 oemWriter.writeHeader(generator, header);
187                 metadata.setObjectName(objectName);
188                 metadata.setStartTime(block.getStart());
189                 metadata.setStopTime(block.getStop());
190                 metadata.setReferenceFrame(FrameFacade.map(frame));
191                 oemWriter.writeMetadata(generator, metadata);
192                 for (TimeStampedPVCoordinates coordinate : block.getCoordinates()) {
193                     oemWriter.writeOrbitEphemerisLine(generator, metadata, coordinate, true);
194                 }
195             }
196 
197             // verify
198             final DataSource source2 = new DataSource("buffer",
199                                                     () -> new ByteArrayInputStream(buffer2.toString().getBytes(StandardCharsets.UTF_8)));
200             generatedOem = new ParserBuilder().
201                                withConventions(IERSConventions.IERS_2010).
202                                withSimpleEOP(true).
203                                withDataContext(DataContext.getDefault()).
204                                withMu(CelestialBodyFactory.getEarth().getGM()).
205                                withDefaultInterpolationDegree(1).
206                                withParsedUnitsBehavior(ParsedUnitsBehavior.STRICT_COMPLIANCE).
207                                buildOemParser().
208                                parseMessage(source2);
209             compareOems(oem, generatedOem, POSITION_PRECISION, VELOCITY_PRECISION);
210 
211         }
212 
213     }
214 
215     @Test
216     public void testWriteOemEcfNoInterpolation() {
217         // setup
218         String path = "/ccsds/odm/oem/OEMExample5.txt";
219         DataSource source = new DataSource(path, () -> getClass().getResourceAsStream(path));
220         final OemParser oemParser = new ParserBuilder().buildOemParser();
221         final Oem original = oemParser.parse(source);
222         final OemSatelliteEphemeris originalEphem =
223                 original.getSatellites().values().iterator().next();
224         final Frame frame = originalEphem.getSegments().get(0).getInertialFrame();
225         final BoundedPropagator propagator = originalEphem.getPropagator(new FrameAlignedProvider(frame));
226         StringBuilder buffer = new StringBuilder();
227         OdmHeader header = original.getHeader();
228         OemMetadata metadata = original.getSegments().get(0).getMetadata();
229         metadata.setTimeSystem(TimeSystem.UTC);
230         metadata.setReferenceFrame(FrameFacade.map(FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
231         metadata.setInterpolationMethod(null);
232         metadata.setInterpolationDegree(-1);
233 
234         // action
235         StreamingOemWriter writer = new StreamingOemWriter(
236                 new KvnGenerator(buffer, OemWriter.KVN_PADDING_WIDTH, "out",
237                                  Constants.JULIAN_DAY, 0),
238                 new WriterBuilder().buildOemWriter(),
239                 header,
240                 metadata,
241                 false,
242                 false);
243         propagator.setStepHandler(30 * 60, writer.newSegment());
244         propagator.propagate(propagator.getMinDate(), propagator.getMaxDate());
245 
246         // verify
247         String actualText = buffer.toString();
248         String expectedPath = "/ccsds/odm/oem/OEMExample5ITRF.txt";
249         Oem expected = oemParser.parse(
250                 new DataSource(expectedPath, () -> getClass().getResourceAsStream(expectedPath)));
251         Oem actual = oemParser.parse(
252                 new DataSource("mem", () -> new StringReader(actualText)));
253 
254         compareOems(expected, actual, 1.9e-5, 2.2e-8);
255         MatcherAssert.assertThat(
256                 actualText,
257                 CoreMatchers.not(CoreMatchers.containsString("INTERPOLATION_DEGREE")));
258         // check no acceleration
259         MatcherAssert.assertThat(
260                 actualText,
261                 CoreMatchers.containsString(
262                         "\n2017-04-11T22:31:43.121856 -2757.3016318855234 -4173.47960139054 4566.018498013474 6.625901653955907 -1.0118172088819106 3.0698336591485442\n"));
263     }
264 
265     private static void compareOemEphemerisBlocks(OemSegment block1,
266                                                   OemSegment block2,
267                                                   double p_tol,
268                                                   double v_tol) {
269         compareOemEphemerisBlocksMetadata(block1.getMetadata(), block2.getMetadata());
270         Assertions.assertEquals(block1.getStart(), block2.getStart());
271         Assertions.assertEquals(block1.getStop(), block2.getStop());
272         Assertions.assertEquals(block1.getData().getEphemeridesDataLines().size(), block2.getData().getEphemeridesDataLines().size());
273         for (int i = 0; i < block1.getData().getEphemeridesDataLines().size(); i++) {
274             TimeStampedPVCoordinates c1 = block1.getData().getEphemeridesDataLines().get(i);
275             TimeStampedPVCoordinates c2 = block2.getData().getEphemeridesDataLines().get(i);
276             Assertions.assertEquals(c1.getDate(), c2.getDate(),"" + i);
277             Assertions.assertEquals(0.0,
278                     Vector3D.distance(c1.getPosition(), c2.getPosition()), p_tol,c1.getPosition() + " -> " + c2.getPosition());
279             Assertions.assertEquals(0.0,
280                     Vector3D.distance(c1.getVelocity(), c2.getVelocity()), v_tol,c1.getVelocity() + " -> " + c2.getVelocity());
281         }
282 
283     }
284 
285     private static void compareOemEphemerisBlocksMetadata(OemMetadata meta1, OemMetadata meta2) {
286         Assertions.assertEquals(meta1.getObjectID(),                               meta2.getObjectID());
287         Assertions.assertEquals(meta1.getObjectName(),                             meta2.getObjectName());
288         Assertions.assertEquals(meta1.getCenter().getName(),                       meta2.getCenter().getName());
289         Assertions.assertEquals(meta1.getReferenceFrame().asFrame(),               meta2.getReferenceFrame().asFrame());
290         Assertions.assertEquals(meta1.getReferenceFrame().asCelestialBodyFrame(),  meta2.getReferenceFrame().asCelestialBodyFrame());
291         Assertions.assertEquals(meta1.getReferenceFrame().asOrbitRelativeFrame(),  meta2.getReferenceFrame().asOrbitRelativeFrame());
292         Assertions.assertEquals(meta1.getReferenceFrame().asSpacecraftBodyFrame(), meta2.getReferenceFrame().asSpacecraftBodyFrame());
293         Assertions.assertEquals(meta1.getTimeSystem().name(),    meta2.getTimeSystem().name());
294     }
295 
296     static void compareOems(Oem file1, Oem file2, double p_tol, double v_tol) {
297         Assertions.assertEquals(file1.getHeader().getOriginator(), file2.getHeader().getOriginator());
298         Assertions.assertEquals(file1.getSegments().size(), file2.getSegments().size());
299         for (int i = 0; i < file1.getSegments().size(); i++) {
300             compareOemEphemerisBlocks(file1.getSegments().get(i), file2.getSegments().get(i), p_tol, v_tol);
301         }
302     }
303 
304 }