1   /* Copyright 2002-2022 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 static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.fail;
22  
23  import java.io.BufferedReader;
24  import java.io.BufferedWriter;
25  import java.io.ByteArrayInputStream;
26  import java.io.CharArrayWriter;
27  import java.io.IOException;
28  import java.io.InputStreamReader;
29  import java.nio.charset.StandardCharsets;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  
36  import org.hipparchus.geometry.euclidean.threed.Vector3D;
37  import org.junit.Before;
38  import org.junit.Test;
39  import org.orekit.Utils;
40  import org.orekit.bodies.CelestialBodyFactory;
41  import org.orekit.data.DataContext;
42  import org.orekit.data.DataSource;
43  import org.orekit.errors.OrekitIllegalArgumentException;
44  import org.orekit.errors.OrekitMessages;
45  import org.orekit.files.ccsds.definitions.BodyFacade;
46  import org.orekit.files.ccsds.definitions.FrameFacade;
47  import org.orekit.files.ccsds.definitions.TimeSystem;
48  import org.orekit.files.ccsds.ndm.ParserBuilder;
49  import org.orekit.files.ccsds.ndm.WriterBuilder;
50  import org.orekit.files.ccsds.ndm.odm.CartesianCovariance;
51  import org.orekit.files.ccsds.utils.FileFormat;
52  import org.orekit.files.ccsds.utils.generation.Generator;
53  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
54  import org.orekit.files.ccsds.utils.generation.XmlGenerator;
55  import org.orekit.files.general.EphemerisFile;
56  import org.orekit.frames.Frame;
57  import org.orekit.frames.FramesFactory;
58  import org.orekit.time.AbsoluteDate;
59  import org.orekit.utils.Constants;
60  import org.orekit.utils.IERSConventions;
61  import org.orekit.utils.TimeStampedPVCoordinates;
62  
63  public class EphemerisWriterTest {
64  
65      // As the default format for position is 3 digits after decimal point in km the max precision in m is 1
66      private static final double POSITION_PRECISION = 1; // in m
67      // As the default format for velocity is 5 digits after decimal point in km/s the max precision in m/s is 1e-2
68      private static final double VELOCITY_PRECISION = 1e-2; //in m/s
69  
70      @Before
71      public void setUp() throws Exception {
72          Utils.setDataRoot("regular-data");
73      }
74  
75      @Test
76      public void testOEMWriter() {
77          assertNotNull(new WriterBuilder().buildOemWriter());
78      }
79  
80      @Test
81      public void testWriteOEM1Kvn() throws IOException {
82          final CharArrayWriter caw = new CharArrayWriter();
83          final Generator generator = new KvnGenerator(caw, 0, "", 60);
84          doTestWriteOEM1(caw, generator);
85      }
86  
87      @Test
88      public void testWriteOEM1Xml() throws IOException {
89          final CharArrayWriter caw = new CharArrayWriter();
90          final Generator generator = new XmlGenerator(caw, 2, "", true);
91          doTestWriteOEM1(caw, generator);
92      }
93  
94      private void doTestWriteOEM1(final CharArrayWriter caw, Generator generator) throws IOException {
95          final String ex = "/ccsds/odm/oem/OEMExample1.txt";
96          final DataSource source =  new DataSource(ex, () -> getClass().getResourceAsStream(ex));
97          final OemParser parser  = new ParserBuilder().withMu(CelestialBodyFactory.getMars().getGM()).buildOemParser();
98          final Oem oem = parser.parseMessage(source);
99  
100         OemWriter writer = new WriterBuilder().
101                         withConventions(IERSConventions.IERS_2010).
102                         withDataContext(DataContext.getDefault()).
103                         buildOemWriter();
104         writer.writeMessage(generator, oem);
105         final byte[] bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
106 
107         final Oem generatedOem = new ParserBuilder().
108                                          withConventions(IERSConventions.IERS_2010).
109                                          withSimpleEOP(true).
110                                          withDataContext(DataContext.getDefault()).
111                                          withMu(CelestialBodyFactory.getMars().getGM()).
112                                          withDefaultInterpolationDegree(1).
113                                          buildOemParser().
114                                          parseMessage(new DataSource("", () -> new ByteArrayInputStream(bytes)));
115         compareOems(oem, generatedOem);
116     }
117 
118     @Test
119     public void testUnfoundSpaceId() throws IOException {
120         final String ex = "/ccsds/odm/oem/OEMExample1.txt";
121         final DataSource source =  new DataSource(ex, () -> getClass().getResourceAsStream(ex));
122         final OemParser parser  = new ParserBuilder().withMu(CelestialBodyFactory.getEarth().getGM()).buildOemParser();
123         final Oem oem = parser.parseMessage(source);
124 
125         EphemerisWriter writer = new EphemerisWriter(new WriterBuilder().buildOemWriter(),
126                                                      oem.getHeader(), dummyMetadata(), FileFormat.KVN, "", 0);
127         try {
128             writer.write(new CharArrayWriter(), oem);
129             fail("an exception should have been thrown");
130         } catch (OrekitIllegalArgumentException oiae) {
131             assertEquals(OrekitMessages.VALUE_NOT_FOUND, oiae.getSpecifier());
132             assertEquals(dummyMetadata().getObjectID(), oiae.getParts()[0]);
133         }
134 
135     }
136 
137     @Test
138     public void testNullFile() throws IOException {
139         final String ex = "/ccsds/odm/oem/OEMExample1.txt";
140         final DataSource source =  new DataSource(ex, () -> getClass().getResourceAsStream(ex));
141         final OemParser parser  = new ParserBuilder().withMu(CelestialBodyFactory.getEarth().getGM()).buildOemParser();
142         final Oem oem = parser.parseMessage(source);
143         EphemerisWriter writer = new EphemerisWriter(new WriterBuilder().buildOemWriter(),
144                                                      oem.getHeader(),
145                                                      oem.getSegments().get(0).getMetadata(),
146                                                      FileFormat.KVN, "dummy", 0);
147         try {
148             writer.write((BufferedWriter) null, oem);
149             fail("an exception should have been thrown");
150         } catch (OrekitIllegalArgumentException oiae) {
151             assertEquals(OrekitMessages.NULL_ARGUMENT, oiae.getSpecifier());
152             assertEquals("writer", oiae.getParts()[0]);
153         }
154     }
155 
156     @Test
157     public void testNullEphemeris() throws IOException {
158         EphemerisWriter writer = new EphemerisWriter(new WriterBuilder().buildOemWriter(),
159                                                      null, dummyMetadata(), FileFormat.KVN, "nullEphemeris", 60);
160         CharArrayWriter caw = new CharArrayWriter();
161         writer.write(caw, null);
162         assertEquals(0, caw.size());
163     }
164 
165     @Test
166     public void testUnisatelliteFileWithDefault() throws IOException {
167         final String ex = "/ccsds/odm/oem/OEMExample1.txt";
168         final DataSource source =  new DataSource(ex, () -> getClass().getResourceAsStream(ex));
169         final OemParser parser  = new ParserBuilder().withMu(CelestialBodyFactory.getEarth().getGM()).buildOemParser();
170         final Oem oem = parser.parseMessage(source);
171 
172         OemWriter writer = new WriterBuilder().buildOemWriter();
173         final CharArrayWriter caw = new CharArrayWriter();
174         writer.writeMessage(new KvnGenerator(caw, 0, "", 60), oem);
175         final byte[] bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
176 
177         final Oem generatedOem = new ParserBuilder().
178                                          withMu(CelestialBodyFactory.getEarth().getGM()).
179                                          buildOemParser().
180                                          parseMessage(new DataSource("", () -> new ByteArrayInputStream(bytes)));
181         assertEquals(oem.getSegments().get(0).getMetadata().getObjectID(),
182                 generatedOem.getSegments().get(0).getMetadata().getObjectID());
183     }
184 
185     @Test
186     public void testIssue723() throws IOException {
187         final String ex = "/ccsds/odm/oem/OEMExampleWithHeaderComment.txt";
188         final DataSource source =  new DataSource(ex, () -> getClass().getResourceAsStream(ex));
189         final OemParser parser  = new ParserBuilder().withMu(CelestialBodyFactory.getEarth().getGM()).buildOemParser();
190         final Oem oem = parser.parseMessage(source);
191 
192         EphemerisWriter writer = new EphemerisWriter(new WriterBuilder().buildOemWriter(),
193                                                      oem.getHeader(),
194                                                      oem.getSegments().get(0).getMetadata(), 
195                                                      FileFormat.KVN, "TestOEMIssue723.aem", 0);
196         final CharArrayWriter caw = new CharArrayWriter();
197         writer.write(caw, oem);
198         final byte[] bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
199 
200         final Oem generatedOem = new ParserBuilder().
201                                          withMu(CelestialBodyFactory.getEarth().getGM()).
202                                          buildOemParser().
203                                          parseMessage(new DataSource("", () -> new ByteArrayInputStream(bytes)));
204         assertEquals(oem.getHeader().getComments().get(0), generatedOem.getHeader().getComments().get(0));
205     }
206 
207     /**
208      * Check writing an OEM with format parameters for orbit.
209      *
210      * @throws IOException on error
211      */
212     @Test
213     public void testWriteOemFormat() throws IOException {
214         // setup
215         String exampleFile = "/ccsds/odm/oem/OEMExample4.txt";
216         final DataSource source =  new DataSource(exampleFile, () -> getClass().getResourceAsStream(exampleFile));
217         final OemParser parser  = new ParserBuilder().withMu(CelestialBodyFactory.getEarth().getGM()).buildOemParser();
218         Oem oem = parser.parseMessage(source);
219 
220         OemWriter writer = new WriterBuilder().buildOemWriter();
221         final CharArrayWriter caw = new CharArrayWriter();
222         writer.writeMessage(new KvnGenerator(caw, 0, "", 60), oem);
223 
224         String[] lines2 = caw.toString().split("\n");
225         assertEquals("2002-12-18T12:00:00.331 2789.619 -280.045 -1746.755 4.73372 -2.49586 -1.0419499999999997", lines2[21]);
226         assertEquals("2002-12-18T12:01:00.331 2783.419 -308.143 -1877.071 5.18604 -2.42124 -1.99608", lines2[22]);
227         assertEquals("2002-12-18T12:02:00.331 2776.033 -336.859 -2008.682 5.63678 -2.33951 -1.94687", lines2[23]);
228 
229     }
230 
231     @Test
232     public void testMultisatelliteFile() throws IOException {
233 
234         final DataContext context = DataContext.getDefault();
235         final String id1 = "1999-012A";
236         final String id2 = "1999-012B";
237         StandAloneEphemerisFile file = new StandAloneEphemerisFile();
238         file.generate(id1, id1 + "-name", context.getFrames().getEME2000(),
239                       new TimeStampedPVCoordinates(AbsoluteDate.GALILEO_EPOCH,
240                                                    new Vector3D(1.0e6, 2.0e6, 3.0e6),
241                                                    new Vector3D(-300, -200, -100)),
242                       900.0, 60.0);
243         file.generate(id2, id2 + "-name", context.getFrames().getEME2000(),
244                       new TimeStampedPVCoordinates(AbsoluteDate.GALILEO_EPOCH,
245                                                    new Vector3D(3.0e6, 2.0e6, -1.0e6),
246                                                    new Vector3D(-17, -20, 150)),
247                       600.0, 10.0);
248 
249 
250         OemMetadata metadata = dummyMetadata();
251         metadata.setObjectID(id2);
252         EphemerisWriter writer = new EphemerisWriter(new WriterBuilder().withDataContext(context).buildOemWriter(),
253                                                      null, metadata, FileFormat.KVN, "", -1);
254         final CharArrayWriter caw = new CharArrayWriter();
255         writer.write(caw, file);
256         final byte[] bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
257 
258         int count = 0;
259         try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
260              InputStreamReader    isr  = new InputStreamReader(bais, StandardCharsets.UTF_8);
261              BufferedReader       br   = new BufferedReader(isr)) {
262             for (String line = br.readLine(); line != null; line = br.readLine()) {
263                 ++count;
264             }
265         }
266         assertEquals(80, count);
267 
268     }
269 
270     private static void compareOemEphemerisBlocks(OemSegment block1, OemSegment block2) {
271         compareOemEphemerisBlocksMetadata(block1.getMetadata(), block2.getMetadata());
272         assertEquals(block1.getStart(), block2.getStart());
273         assertEquals(block1.getStop(), block2.getStop());
274         assertEquals(block1.getMetadata().getInterpolationDegree(), block2.getMetadata().getInterpolationDegree());
275         assertEquals(block1.getMetadata().getInterpolationMethod(), block2.getMetadata().getInterpolationMethod());
276         assertEquals(block1.getData().getEphemeridesDataLines().size(), block2.getData().getEphemeridesDataLines().size());
277         for (int i = 0; i < block1.getData().getEphemeridesDataLines().size(); i++) {
278             TimeStampedPVCoordinates c1 = block1.getData().getEphemeridesDataLines().get(i);
279             TimeStampedPVCoordinates c2 = block2.getData().getEphemeridesDataLines().get(i);
280             assertEquals(c1.getDate(), c2.getDate());
281             assertEquals(c1.getPosition() + " -> " + c2.getPosition(), 0.0,
282                     Vector3D.distance(c1.getPosition(), c2.getPosition()), POSITION_PRECISION);
283             assertEquals(c1.getVelocity() + " -> " + c2.getVelocity(), 0.0,
284                     Vector3D.distance(c1.getVelocity(), c2.getVelocity()), VELOCITY_PRECISION);
285         }
286         assertEquals(block1.getCovarianceMatrices().size(), block2.getCovarianceMatrices().size());
287         for (int j = 0; j < block1.getCovarianceMatrices().size(); j++) {
288         	CartesianCovariance covMat1 = block1.getCovarianceMatrices().get(j);
289         	CartesianCovariance covMat2 = block2.getCovarianceMatrices().get(j);
290         	assertEquals(covMat1.getEpoch(), covMat2.getEpoch());
291             assertEquals(covMat1.getReferenceFrame().asFrame(),               covMat2.getReferenceFrame().asFrame());
292             assertEquals(covMat1.getReferenceFrame().asCelestialBodyFrame(),  covMat2.getReferenceFrame().asCelestialBodyFrame());
293             assertEquals(covMat1.getReferenceFrame().asOrbitRelativeFrame(),  covMat2.getReferenceFrame().asOrbitRelativeFrame());
294             assertEquals(covMat1.getReferenceFrame().asSpacecraftBodyFrame(), covMat2.getReferenceFrame().asSpacecraftBodyFrame());
295         	assertEquals(covMat1.getCovarianceMatrix(),covMat2.getCovarianceMatrix());       	
296         }
297     }
298 
299     private static void compareOemEphemerisBlocksMetadata(OemMetadata meta1, OemMetadata meta2) {
300         assertEquals(meta1.getObjectID(),                               meta2.getObjectID());
301         assertEquals(meta1.getObjectName(),                             meta2.getObjectName());
302         assertEquals(meta1.getCenter().getName(),                       meta2.getCenter().getName());
303         assertEquals(meta1.getReferenceFrame().asFrame(),               meta2.getReferenceFrame().asFrame());
304         assertEquals(meta1.getReferenceFrame().asCelestialBodyFrame(),  meta2.getReferenceFrame().asCelestialBodyFrame());
305         assertEquals(meta1.getReferenceFrame().asOrbitRelativeFrame(),  meta2.getReferenceFrame().asOrbitRelativeFrame());
306         assertEquals(meta1.getReferenceFrame().asSpacecraftBodyFrame(), meta2.getReferenceFrame().asSpacecraftBodyFrame());
307         assertEquals(meta1.getTimeSystem().name(),    meta2.getTimeSystem().name());
308     }
309 
310     static void compareOems(Oem file1, Oem file2) {
311         assertEquals(file1.getHeader().getOriginator(), file2.getHeader().getOriginator());
312         assertEquals(file1.getSegments().size(), file2.getSegments().size());
313         for (int i = 0; i < file1.getSegments().size(); i++) {
314             compareOemEphemerisBlocks(file1.getSegments().get(i), file2.getSegments().get(i));
315         }
316     }
317 
318     private class StandAloneEphemerisFile
319         implements EphemerisFile<TimeStampedPVCoordinates, OemSegment> {
320         private final Map<String, OemSatelliteEphemeris> satEphem;
321 
322         /** Simple constructor.
323          */
324         public StandAloneEphemerisFile() {
325             this.satEphem    = new HashMap<String, OemSatelliteEphemeris>();
326         }
327 
328         private void generate(final String objectID, final String objectName,
329                               final Frame referenceFrame, final TimeStampedPVCoordinates pv0,
330                               final double duration, final double step) {
331 
332             OemMetadata metadata = dummyMetadata();
333             metadata.addComment("metadata for " + objectName);
334             metadata.setObjectID(objectID);
335             metadata.setObjectName(objectName);
336             metadata.setStartTime(pv0.getDate());
337             metadata.setStopTime(pv0.getDate().shiftedBy(duration));
338             metadata.setUseableStartTime(metadata.getStartTime().shiftedBy(step));
339             metadata.setUseableStartTime(metadata.getStopTime().shiftedBy(-step));
340 
341             OemData data = new OemData();
342             data.addComment("generated data for " + objectName);
343             data.addComment("duration was set to " + duration + " s");
344             data.addComment("step was set to " + step + " s");
345             for (double dt = 0; dt < duration; dt += step) {
346                 data.addData(pv0.shiftedBy(dt), false);
347             }
348 
349             if (!satEphem.containsKey(objectID)) {
350                 satEphem.put(objectID,
351                              new OemSatelliteEphemeris(objectID, Constants.EIGEN5C_EARTH_MU, Collections.emptyList()));
352             }
353 
354             List<OemSegment> segments =
355                             new ArrayList<>(satEphem.get(objectID).getSegments());
356             segments.add(new OemSegment(metadata, data, Constants.EIGEN5C_EARTH_MU));
357             satEphem.put(objectID, new OemSatelliteEphemeris(objectID, Constants.EIGEN5C_EARTH_MU, segments));
358 
359         }
360 
361         @Override
362         public Map<String, OemSatelliteEphemeris> getSatellites() {
363             return satEphem;
364         }
365 
366     }
367 
368     private OemMetadata dummyMetadata() {
369         OemMetadata metadata = new OemMetadata(4);
370         metadata.addComment("dummy comment");
371         metadata.setTimeSystem(TimeSystem.TT);
372         metadata.setObjectID("9999-999ZZZ");
373         metadata.setObjectName("transgalactic");
374         metadata.setCenter(new BodyFacade("EARTH", CelestialBodyFactory.getCelestialBodies().getEarth()));
375         metadata.setReferenceFrame(FrameFacade.map(FramesFactory.getEME2000()));
376         metadata.setStartTime(AbsoluteDate.J2000_EPOCH.shiftedBy(80 * Constants.JULIAN_CENTURY));
377         metadata.setStopTime(metadata.getStartTime().shiftedBy(Constants.JULIAN_YEAR));
378         return metadata;
379     }
380 
381 }