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.ocm;
18  
19  import java.io.StringReader;
20  
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.junit.jupiter.api.AfterEach;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.BeforeEach;
25  import org.junit.jupiter.api.Test;
26  import org.orekit.Utils;
27  import org.orekit.attitudes.FrameAlignedProvider;
28  import org.orekit.bodies.OneAxisEllipsoid;
29  import org.orekit.data.DataContext;
30  import org.orekit.data.DataSource;
31  import org.orekit.files.ccsds.definitions.FrameFacade;
32  import org.orekit.files.ccsds.definitions.TimeSystem;
33  import org.orekit.files.ccsds.ndm.ParserBuilder;
34  import org.orekit.files.ccsds.ndm.WriterBuilder;
35  import org.orekit.files.ccsds.ndm.odm.OdmHeader;
36  import org.orekit.files.ccsds.ndm.odm.oem.InterpolationMethod;
37  import org.orekit.files.ccsds.ndm.odm.oem.Oem;
38  import org.orekit.files.ccsds.ndm.odm.oem.OemParser;
39  import org.orekit.files.ccsds.ndm.odm.oem.OemSatelliteEphemeris;
40  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
41  import org.orekit.frames.Frame;
42  import org.orekit.frames.FramesFactory;
43  import org.orekit.propagation.BoundedPropagator;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.IERSConventions;
46  import org.orekit.utils.TimeStampedPVCoordinates;
47  
48  /**
49   * Check {@link StreamingOcmWriter}.
50   *
51   * @author Evan Ward
52   */
53  public class StreamingOcmWriterTest {
54  
55      private OneAxisEllipsoid earth;
56  
57      /** Set Orekit data. */
58      @BeforeEach
59      public void setUp() {
60          Utils.setDataRoot("regular-data");
61          earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
62                                       Constants.WGS84_EARTH_FLATTENING,
63                                       FramesFactory.getITRF(IERSConventions.IERS_2010, true));
64      }
65  
66      @AfterEach
67      public void tearDown() {
68          earth = null;
69      }
70  
71      @Test
72      public void testWriteOcmEcf() {
73          // setup
74          // beware, we read on OEM in inertial frame and transform it into an OCM in ITRF frame
75          String path = "/ccsds/odm/oem/OEMExample5.txt";
76          DataSource source = new DataSource(path, () -> getClass().getResourceAsStream(path));
77          final OemParser oemParser = new ParserBuilder().
78                                      withMu(Constants.EIGEN5C_EARTH_MU).
79                                      buildOemParser();
80          final Oem original = oemParser.parse(source);
81          final OemSatelliteEphemeris originalEphem =
82                  original.getSatellites().values().iterator().next();
83          final Frame frame = originalEphem.getSegments().get(0).getInertialFrame();
84          final BoundedPropagator propagator = originalEphem.getPropagator(new FrameAlignedProvider(frame));
85          StringBuilder buffer = new StringBuilder();
86          OdmHeader header = original.getHeader();
87          OcmMetadata metadata = new OcmMetadata(DataContext.getDefault());
88          metadata.setTimeSystem(TimeSystem.UTC);
89          metadata.setEpochT0(original.getSegments().get(0).getStart());
90          TrajectoryStateHistoryMetadata trajectoryMetadata = new TrajectoryStateHistoryMetadata(metadata.getEpochT0(),
91                                                                                                 DataContext.getDefault());
92          trajectoryMetadata.setTrajReferenceFrame(FrameFacade.map(FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
93          trajectoryMetadata.setInterpolationMethod(InterpolationMethod.LAGRANGE);
94          trajectoryMetadata.setInterpolationDegree(2);
95  
96          // action
97          StreamingOcmWriter writer = new StreamingOcmWriter(
98                  new KvnGenerator(buffer, OcmWriter.KVN_PADDING_WIDTH, "out",
99                                   Constants.JULIAN_DAY, 0),
100                 new WriterBuilder().buildOcmWriter(),
101                 header,
102                 metadata,
103                 trajectoryMetadata);
104         propagator.setStepHandler(30 * 60, writer.newBlock());
105         propagator.propagate(propagator.getMinDate(), propagator.getMaxDate());
106 
107         // verify
108         String actualText = buffer.toString();
109         String expectedPath = "/ccsds/odm/ocm/OCMExample5ITRFStreamingOutput.txt";
110         final OcmParser ocmParser = new ParserBuilder().buildOcmParser();
111         Ocm expected = ocmParser.parse(new DataSource(expectedPath, () -> getClass().getResourceAsStream(expectedPath)));
112         Ocm actual = ocmParser.parse(new DataSource("mem", () -> new StringReader(actualText)));
113         partialCompareOcms(expected, actual);
114 
115     }
116 
117     @Test
118     public void testWriteOcmGeodetic() {
119         // setup
120         // beware, we read on OEM in inertial frame and transform it into an OCM in geodetic coordinates
121         String path = "/ccsds/odm/oem/OEMExample5.txt";
122         DataSource source = new DataSource(path, () -> getClass().getResourceAsStream(path));
123         final OemParser oemParser = new ParserBuilder().
124                                     withMu(Constants.EIGEN5C_EARTH_MU).
125                                     buildOemParser();
126         final Oem original = oemParser.parse(source);
127         final OemSatelliteEphemeris originalEphem =
128                 original.getSatellites().values().iterator().next();
129         final Frame frame = originalEphem.getSegments().get(0).getInertialFrame();
130         final BoundedPropagator propagator = originalEphem.getPropagator(new FrameAlignedProvider(frame));
131         StringBuilder buffer = new StringBuilder();
132         OdmHeader header = original.getHeader();
133         OcmMetadata metadata = new OcmMetadata(DataContext.getDefault());
134         metadata.setTimeSystem(TimeSystem.UTC);
135         metadata.setEpochT0(original.getSegments().get(0).getStart());
136         TrajectoryStateHistoryMetadata trajectoryMetadata = new TrajectoryStateHistoryMetadata(metadata.getEpochT0(),
137                                                                                                DataContext.getDefault());
138         trajectoryMetadata.setTrajReferenceFrame(FrameFacade.map(FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
139         trajectoryMetadata.setInterpolationMethod(InterpolationMethod.LAGRANGE);
140         trajectoryMetadata.setInterpolationDegree(2);
141         trajectoryMetadata.setTrajType(OrbitElementsType.GEODETIC);
142 
143         // action
144         StreamingOcmWriter writer = new StreamingOcmWriter(
145                 new KvnGenerator(buffer, OcmWriter.KVN_PADDING_WIDTH, "out",
146                                  Constants.JULIAN_DAY, 0),
147                 new WriterBuilder().
148                 withEquatorialRadius(earth.getEquatorialRadius()).
149                 withFlattening(earth.getFlattening()).
150                 buildOcmWriter(),
151                 header,
152                 metadata,
153                 trajectoryMetadata);
154         propagator.setStepHandler(30 * 60, writer.newBlock());
155         propagator.propagate(propagator.getMinDate(), propagator.getMaxDate());
156 
157         // verify
158         String actualText = buffer.toString();
159         String expectedPath = "/ccsds/odm/ocm/OCMExample5GeodeticStreamingOutput.txt";
160         final OcmParser ocmParser = new ParserBuilder().buildOcmParser();
161         Ocm expected = ocmParser.parse(new DataSource(expectedPath, () -> getClass().getResourceAsStream(expectedPath)));
162         Ocm actual = ocmParser.parse(new DataSource("mem", () -> new StringReader(actualText)));
163         partialCompareOcms(expected, actual);
164 
165     }
166 
167     private void partialCompareOcmEphemerisBlocks(TrajectoryStateHistory block1, TrajectoryStateHistory block2) {
168         partialCompareOcmEphemerisBlocksMetadata(block1.getMetadata(), block2.getMetadata());
169         Assertions.assertEquals(0.0, block1.getStart().durationFrom(block2.getStart()), 1.0e-12);
170         Assertions.assertEquals(0.0, block1.getStop().durationFrom(block2.getStop()), 1.0e-12);
171         Assertions.assertEquals(block1.getMetadata().getInterpolationDegree(), block2.getMetadata().getInterpolationDegree());
172         Assertions.assertEquals(block1.getMetadata().getInterpolationMethod(), block2.getMetadata().getInterpolationMethod());
173         Assertions.assertEquals(block1.getTrajectoryStates().size(), block2.getTrajectoryStates().size());
174         for (int i = 0; i < block1.getTrajectoryStates().size(); i++) {
175             TrajectoryState c1 = block1.getTrajectoryStates().get(i);
176             TrajectoryState c2 = block2.getTrajectoryStates().get(i);
177             Assertions.assertEquals(0.0, c1.getDate().durationFrom(c2.getDate()), 1.0e-12);
178             Assertions.assertEquals(c1.getType(), c2.getType());
179             Assertions.assertEquals(c1.getElements().length, c2.getElements().length);
180             TimeStampedPVCoordinates pv1 = c1.getType().toCartesian(c1.getDate(), c1.getElements(), earth, block1.getMu());
181             TimeStampedPVCoordinates pv2 = c2.getType().toCartesian(c2.getDate(), c2.getElements(), earth, block2.getMu());
182             pv2 = block2.getFrame().getTransformTo(block1.getFrame(), pv2.getDate()).transformPVCoordinates(pv2);
183             Assertions.assertEquals(0.0, Vector3D.distance(pv1.getPosition(), pv2.getPosition()), 5.0e-9);
184             if (c1.getElements().length > 3) {
185                 Assertions.assertEquals(0.0, Vector3D.distance(pv1.getVelocity(), pv2.getVelocity()), 1.0e-10);
186             }
187         }
188     }
189 
190     private void partialCompareOcmEphemerisBlocksMetadata(TrajectoryStateHistoryMetadata meta1, TrajectoryStateHistoryMetadata meta2) {
191         Assertions.assertEquals(meta1.getTrajID(),                                 meta2.getTrajID());
192         Assertions.assertEquals(meta1.getTrajPrevID(),                             meta2.getTrajPrevID());
193         Assertions.assertEquals(meta1.getTrajNextID(),                             meta2.getTrajNextID());
194         Assertions.assertEquals(meta1.getTrajBasis(),                              meta2.getTrajBasis());
195         Assertions.assertEquals(meta1.getTrajBasisID(),                            meta2.getTrajBasisID());
196         Assertions.assertEquals(meta1.getInterpolationMethod(),                    meta2.getInterpolationMethod());
197         Assertions.assertEquals(meta1.getInterpolationDegree(),                    meta2.getInterpolationDegree());
198         Assertions.assertEquals(meta1.getPropagator(),                             meta2.getPropagator());
199         Assertions.assertEquals(meta1.getCenter().getName(),                       meta2.getCenter().getName());
200         // we intentionally don't check trajectory reference frame
201         Assertions.assertEquals(meta1.getTrajFrameEpoch(),                         meta2.getTrajFrameEpoch());
202         // we intentionally don't check start and stop times
203         Assertions.assertEquals(meta1.getOrbRevNum(),                              meta2.getOrbRevNum());
204         Assertions.assertEquals(meta1.getOrbRevNumBasis(),                         meta2.getOrbRevNumBasis());
205         Assertions.assertEquals(meta1.getOrbAveraging(),                           meta2.getOrbAveraging());
206         Assertions.assertEquals(meta1.getTrajType(),                               meta2.getTrajType());
207         if (meta1.getTrajUnits() == null) {
208             Assertions.assertNull(meta2.getTrajUnits());
209         } else {
210             Assertions.assertEquals(meta1.getTrajUnits().size(), meta2.getTrajUnits().size());
211             for (int i = 0; i < meta1.getTrajUnits().size(); ++i) {
212                 Assertions.assertEquals(meta1.getTrajUnits().get(i), meta2.getTrajUnits().get(i));
213             }
214         }
215     }
216 
217     void partialCompareOcms(Ocm file1, Ocm file2) {
218         Assertions.assertEquals(file1.getHeader().getOriginator(), file2.getHeader().getOriginator());
219         Assertions.assertEquals(file1.getSegments().get(0).getData().getTrajectoryBlocks().size(),
220                                 file2.getSegments().get(0).getData().getTrajectoryBlocks().size());
221         for (int i = 0; i < file1.getSegments().get(0).getData().getTrajectoryBlocks().size(); i++) {
222             partialCompareOcmEphemerisBlocks(file1.getSegments().get(0).getData().getTrajectoryBlocks().get(i),
223                                              file2.getSegments().get(0).getData().getTrajectoryBlocks().get(i));
224         }
225     }
226 
227 
228 }