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.adm.aem;
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.File;
28  import java.io.IOException;
29  import java.io.InputStreamReader;
30  import java.nio.charset.StandardCharsets;
31  import java.util.ArrayList;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  
37  import org.hipparchus.geometry.euclidean.threed.Rotation;
38  import org.hipparchus.geometry.euclidean.threed.Vector3D;
39  import org.junit.Before;
40  import org.junit.Rule;
41  import org.junit.Test;
42  import org.junit.rules.TemporaryFolder;
43  import org.orekit.Utils;
44  import org.orekit.data.DataContext;
45  import org.orekit.data.DataSource;
46  import org.orekit.errors.OrekitIllegalArgumentException;
47  import org.orekit.errors.OrekitMessages;
48  import org.orekit.files.ccsds.definitions.CelestialBodyFrame;
49  import org.orekit.files.ccsds.definitions.FrameFacade;
50  import org.orekit.files.ccsds.definitions.SpacecraftBodyFrame;
51  import org.orekit.files.ccsds.definitions.TimeSystem;
52  import org.orekit.files.ccsds.ndm.ParserBuilder;
53  import org.orekit.files.ccsds.ndm.WriterBuilder;
54  import org.orekit.files.ccsds.ndm.adm.AttitudeType;
55  import org.orekit.files.ccsds.section.Header;
56  import org.orekit.files.ccsds.utils.FileFormat;
57  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
58  import org.orekit.files.general.AttitudeEphemerisFile;
59  import org.orekit.frames.Frame;
60  import org.orekit.frames.FramesFactory;
61  import org.orekit.time.AbsoluteDate;
62  import org.orekit.utils.Constants;
63  import org.orekit.utils.IERSConventions;
64  import org.orekit.utils.TimeStampedAngularCoordinates;
65  
66  public class AttitudeWriterTest {
67  
68      // The default format writes 5O digits after the decimal point hence the quaternion precision
69      private static final double QUATERNION_PRECISION = 1e-5;
70      private static final double DATE_PRECISION = 1e-3;
71  
72      @Rule
73      public TemporaryFolder tempFolder = new TemporaryFolder();
74  
75      @Before
76      public void setUp() throws Exception {
77          Utils.setDataRoot("regular-data");
78      }
79  
80      @Test
81      public void testAEMWriter() {
82          assertNotNull(new WriterBuilder().buildAemWriter());
83      }
84  
85      @Test
86      public void testWriteAEM1() throws IOException {
87          final String ex = "/ccsds/adm/aem/AEMExample01.txt";
88          final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
89          final Aem aem = new ParserBuilder().buildAemParser().parseMessage(source);
90  
91          Header header = new Header(2.0);
92          header.setFormatVersion(aem.getHeader().getFormatVersion());
93          header.setCreationDate(aem.getHeader().getCreationDate());
94          header.setOriginator(aem.getHeader().getOriginator());
95  
96          final AemSegment s0 = aem.getSegments().get(0);
97          AemMetadata metadata = new AemMetadata(s0.getInterpolationSamples() - 1);
98          metadata.setObjectName(s0.getMetadata().getObjectName());
99          metadata.setObjectID(s0.getMetadata().getObjectID());
100         metadata.getEndpoints().setFrameA(s0.getMetadata().getEndpoints().getFrameA());
101         metadata.getEndpoints().setFrameB(s0.getMetadata().getEndpoints().getFrameB());
102         metadata.getEndpoints().setA2b(s0.getMetadata().getEndpoints().isA2b());
103         metadata.setTimeSystem(s0.getMetadata().getTimeSystem());
104         metadata.setStartTime(s0.getMetadata().getStart());
105         metadata.setStopTime(s0.getMetadata().getStop());
106         metadata.setAttitudeType(s0.getMetadata().getAttitudeType());
107         metadata.setIsFirst(s0.getMetadata().isFirst());
108         metadata.setCenter(s0.getMetadata().getCenter());
109         metadata.setInterpolationMethod(s0.getMetadata().getInterpolationMethod());
110         AemWriter writer = new WriterBuilder().
111                            withConventions(IERSConventions.IERS_2010).
112                            withDataContext(DataContext.getDefault()).
113                            buildAemWriter();
114         final CharArrayWriter caw = new CharArrayWriter();
115         writer.writeMessage(new KvnGenerator(caw, 0, "", 60), aem);
116         final byte[] bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
117 
118         final Aem generatedOem = new ParserBuilder().buildAemParser().
119                         parseMessage(new DataSource("", () -> new ByteArrayInputStream(bytes)));
120         compareAems(aem, generatedOem);
121     }
122 
123     @Test
124     public void testUnfoundSpaceId() throws IOException {
125         final String ex = "/ccsds/adm/aem/AEMExample01.txt";
126         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
127         final Aem aem = new ParserBuilder().buildAemParser().parseMessage(source);
128 
129         AemMetadata metadata = dummyMetadata();
130         metadata.setObjectID("12345");
131         AttitudeWriter writer = new AttitudeWriter(new WriterBuilder().buildAemWriter(), null, metadata,
132                                                    FileFormat.KVN, "", 60);
133         try {
134             writer.write(new CharArrayWriter(), aem);
135             fail("an exception should have been thrown");
136         } catch (OrekitIllegalArgumentException oiae) {
137             assertEquals(OrekitMessages.VALUE_NOT_FOUND, oiae.getSpecifier());
138             assertEquals(metadata.getObjectID(), oiae.getParts()[0]);
139         }
140     }
141 
142     @Test
143     public void testNullFile() throws IOException {
144         final String ex = "/ccsds/adm/aem/AEMExample01.txt";
145         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
146         final Aem aem = new ParserBuilder().buildAemParser().parseMessage(source);
147         AttitudeWriter writer = new AttitudeWriter(new WriterBuilder().
148                                                    withConventions(aem.getConventions()).
149                                                    withDataContext(aem.getDataContext()).
150                                                    buildAemWriter(),
151                                                    aem.getHeader(),
152                                                    aem.getSegments().get(0).getMetadata(),
153                                                    FileFormat.KVN,
154                                                    "dummy", 0);
155         try {
156             writer.write((BufferedWriter) null, aem);
157             fail("an exception should have been thrown");
158         } catch (OrekitIllegalArgumentException oiae) {
159             assertEquals(OrekitMessages.NULL_ARGUMENT, oiae.getSpecifier());
160             assertEquals("writer", oiae.getParts()[0]);
161         }
162     }
163 
164     @Test
165     public void testNullEphemeris() throws IOException {
166         Header header = new Header(2.0);
167         header.setOriginator("NASA/JPL");
168         AemMetadata metadata = dummyMetadata();
169         metadata.setObjectID("1996-062A");
170         metadata.setObjectName("MARS GLOBAL SURVEYOR");
171         AttitudeWriter writer = new AttitudeWriter(new WriterBuilder().buildAemWriter(),
172                                                    header, metadata, FileFormat.KVN, "TestNullEphemeris.aem", 0);
173         CharArrayWriter caw = new CharArrayWriter();
174         writer.write(caw, null);
175         assertEquals(0, caw.size());
176     }
177 
178     @Test
179     public void testUnisatelliteFileWithDefault() throws IOException {
180         final String ex = "/ccsds/adm/aem/AEMExample01.txt";
181         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
182         final Aem aem = new ParserBuilder().buildAemParser().parseMessage(source);
183 
184         final File temp = tempFolder.newFile("writeAEMExample01.xml");
185         AttitudeWriter writer = new AttitudeWriter(new WriterBuilder().buildAemWriter(),
186                                                    aem.getHeader(), aem.getSegments().get(0).getMetadata(),
187                                                    FileFormat.XML, temp.getName(), 1);
188         writer.write(temp.getAbsolutePath(), aem);
189         final Aem generatedAem = new ParserBuilder().buildAemParser().parseMessage(new DataSource(temp));
190         assertEquals(aem.getSegments().get(0).getMetadata().getObjectID(),
191                      generatedAem.getSegments().get(0).getMetadata().getObjectID());
192     }
193 
194     @Test
195     public void testMultisatelliteFile() throws IOException {
196 
197         final DataContext context = DataContext.getDefault();
198         final String id1 = "1999-012A";
199         final String id2 = "1999-012B";
200         StandAloneEphemerisFile file = new StandAloneEphemerisFile();
201         file.generate(id1, id1 + "-name", AttitudeType.QUATERNION_RATE,
202                       context.getFrames().getEME2000(),
203                       new TimeStampedAngularCoordinates(AbsoluteDate.GALILEO_EPOCH,
204                                                         Rotation.IDENTITY,
205                                                         new Vector3D(0.000, 0.010, 0.000),
206                                                         new Vector3D(0.000, 0.000, 0.001)),
207                       900.0, 60.0);
208         file.generate(id2, id2 + "-name", AttitudeType.QUATERNION_RATE,
209                       context.getFrames().getEME2000(),
210                       new TimeStampedAngularCoordinates(AbsoluteDate.GALILEO_EPOCH,
211                                                         Rotation.IDENTITY,
212                                                         new Vector3D(0.000, -0.010, 0.000),
213                                                         new Vector3D(0.000, 0.000, 0.003)),
214                       600.0, 10.0);
215 
216         AemMetadata metadata = dummyMetadata();
217         metadata.setObjectID(id2);
218         AttitudeWriter writer = new AttitudeWriter(new WriterBuilder().buildAemWriter(),
219                                                    null, metadata, FileFormat.KVN, "", 60);
220         final CharArrayWriter caw = new CharArrayWriter();
221         writer.write(caw, file);
222         final byte[] bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
223 
224         int count = 0;
225         try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
226              InputStreamReader    isr  = new InputStreamReader(bais, StandardCharsets.UTF_8);
227              BufferedReader       br   = new BufferedReader(isr)) {
228             for (String line = br.readLine(); line != null; line = br.readLine()) {
229                 ++count;
230             }
231         }
232         assertEquals(82, count);
233 
234     }
235 
236     @Test
237     public void testIssue723() throws IOException {
238         final String ex = "/ccsds/adm/aem/AEMExample02.txt";
239         final DataSource source = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
240         final Aem aem = new ParserBuilder().buildAemParser().parseMessage(source);
241 
242         AttitudeWriter writer = new AttitudeWriter(new WriterBuilder().buildAemWriter(),
243                                                    aem.getHeader(), aem.getSegments().get(0).getMetadata(),
244                                                    FileFormat.KVN, "TestAEMIssue723.aem", 0);
245         final CharArrayWriter caw = new CharArrayWriter();
246         writer.write(caw, aem);
247         final byte[] bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
248 
249         final Aem generatedAem = new ParserBuilder().buildAemParser().
250                         parseMessage(new DataSource("", () -> new ByteArrayInputStream(bytes)));
251         assertEquals(aem.getHeader().getComments().get(0), generatedAem.getHeader().getComments().get(0));
252     }
253 
254     @Test
255     public void testWriteAemFormat() throws IOException {
256         // setup
257         String exampleFile = "/ccsds/adm/aem/AEMExample07.txt";
258         final DataSource source = new DataSource(exampleFile, () -> getClass().getResourceAsStream(exampleFile));
259         final Aem aem = new ParserBuilder().buildAemParser().parseMessage(source);
260 
261         AemWriter writer = new WriterBuilder().buildAemWriter();
262         final CharArrayWriter caw = new CharArrayWriter();
263         writer.writeMessage(new KvnGenerator(caw, 0, "", 60), aem);
264 
265         String[] lines2 = caw.toString().split("\n");
266 
267         assertEquals("2002-12-18T12:00:00.331 0.5674807981623039 0.031460044248583355 0.4568906426171408 0.6842709624277855", lines2[26]);
268         assertEquals("2002-12-18T12:01:00.331 0.4231908397172568 -0.4569709067454213 0.23784047193542462 0.7453314789254544", lines2[27]);
269         assertEquals("2002-12-18T12:02:00.331 -0.8453188238242068 0.2697396246845473 -0.0653199091139417 0.4565193647993977", lines2[28]);
270     }
271 
272     private static void compareAemAttitudeBlocks(AemSegment segment1, AemSegment segment2) {
273 
274         // compare metadata
275         AemMetadata meta1 = segment1.getMetadata();
276         AemMetadata meta2 = segment2.getMetadata();
277         assertEquals(meta1.getObjectID(),                            meta2.getObjectID());
278         assertEquals(meta1.getObjectName(),                          meta2.getObjectName());
279         assertEquals(meta1.getCenter().getName(),                    meta2.getCenter().getName());
280         assertEquals(meta1.getTimeSystem().name(), meta2.getTimeSystem().name());
281         assertEquals(meta1.getLaunchYear(),                          meta2.getLaunchYear());
282         assertEquals(meta1.getLaunchNumber(),                        meta2.getLaunchNumber());
283         assertEquals(meta1.getLaunchPiece(),                         meta2.getLaunchPiece());
284         assertEquals(meta1.getHasCreatableBody(),                    meta2.getHasCreatableBody());
285         assertEquals(meta1.getInterpolationDegree(),                 meta2.getInterpolationDegree());
286 
287         // compare data
288         assertEquals(0.0, segment1.getStart().durationFrom(segment2.getStart()), DATE_PRECISION);
289         assertEquals(0.0, segment1.getStop().durationFrom(segment2.getStop()),   DATE_PRECISION);
290         assertEquals(segment1.getInterpolationMethod(), segment2.getInterpolationMethod());
291         assertEquals(segment1.getAngularCoordinates().size(), segment2.getAngularCoordinates().size());
292         for (int i = 0; i < segment1.getAngularCoordinates().size(); i++) {
293             TimeStampedAngularCoordinates c1 = segment1.getAngularCoordinates().get(i);
294             Rotation rot1 = c1.getRotation();
295             TimeStampedAngularCoordinates c2 = segment2.getAngularCoordinates().get(i);
296             Rotation rot2 = c2.getRotation();
297             assertEquals(0.0, c1.getDate().durationFrom(c2.getDate()), DATE_PRECISION);
298             assertEquals(rot1.getQ0(), rot2.getQ0(), QUATERNION_PRECISION);
299             assertEquals(rot1.getQ1(), rot2.getQ1(), QUATERNION_PRECISION);
300             assertEquals(rot1.getQ2(), rot2.getQ2(), QUATERNION_PRECISION);
301             assertEquals(rot1.getQ3(), rot2.getQ3(), QUATERNION_PRECISION);
302         }
303     }
304 
305     static void compareAems(Aem file1, Aem file2) {
306         assertEquals(file1.getHeader().getOriginator(), file2.getHeader().getOriginator());
307         assertEquals(file1.getSegments().size(), file2.getSegments().size());
308         for (int i = 0; i < file1.getSegments().size(); i++) {
309             compareAemAttitudeBlocks(file1.getSegments().get(i), file2.getSegments().get(i));
310         }
311     }
312 
313     private class StandAloneEphemerisFile
314         implements AttitudeEphemerisFile<TimeStampedAngularCoordinates, AemSegment> {
315         private final Map<String, AemSatelliteEphemeris> satEphem;
316 
317         public StandAloneEphemerisFile() {
318             this.satEphem = new HashMap<>();
319         }
320 
321         private void generate(final String objectID, final String objectName,
322                               final AttitudeType type, final Frame referenceFrame,
323                               final TimeStampedAngularCoordinates ac0,
324                               final double duration, final double step) {
325 
326             AemMetadata metadata = dummyMetadata();
327             metadata.addComment("metadata for " + objectName);
328             metadata.setObjectID(objectID);
329             metadata.setObjectName(objectName);
330             metadata.getEndpoints().setFrameA(FrameFacade.map(referenceFrame));
331             metadata.setAttitudeType(type);
332             metadata.setStartTime(ac0.getDate());
333             metadata.setStopTime(ac0.getDate().shiftedBy(duration));
334             metadata.setUseableStartTime(metadata.getStartTime().shiftedBy(step));
335             metadata.setUseableStartTime(metadata.getStopTime().shiftedBy(-step));
336 
337             AemData data = new AemData();
338             data.addComment("generated data for " + objectName);
339             data.addComment("duration was set to " + duration + " s");
340             data.addComment("step was set to " + step + " s");
341             for (double dt = 0; dt < duration; dt += step) {
342                 data.addData(ac0.shiftedBy(dt));
343             }
344 
345             if (!satEphem.containsKey(objectID)) {
346                 satEphem.put(objectID, new AemSatelliteEphemeris(objectID, Collections.emptyList()));
347             }
348 
349             List<AemSegment> segments = new ArrayList<>(satEphem.get(objectID).getSegments());
350             segments.add(new AemSegment(metadata, data));
351             satEphem.put(objectID, new AemSatelliteEphemeris(objectID, segments));
352 
353         }
354 
355         @Override
356         public Map<String, AemSatelliteEphemeris> getSatellites() {
357             return satEphem;
358         }
359 
360     }
361 
362     private AemMetadata dummyMetadata() {
363         AemMetadata metadata = new AemMetadata(4);
364         metadata.setTimeSystem(TimeSystem.TT);
365         metadata.setObjectID("9999-999ZZZ");
366         metadata.setObjectName("transgalactic");
367         metadata.getEndpoints().setFrameA(new FrameFacade(FramesFactory.getGCRF(), CelestialBodyFrame.GCRF,
368                                                           null, null, "GCRF"));
369         metadata.getEndpoints().setFrameB(new FrameFacade(null, null, null,
370                                                           new SpacecraftBodyFrame(SpacecraftBodyFrame.BaseEquipment.GYRO_FRAME, "1"),
371                                                           "GYRO 1"));
372         metadata.getEndpoints().setA2b(true);
373         metadata.setStartTime(AbsoluteDate.J2000_EPOCH.shiftedBy(80 * Constants.JULIAN_CENTURY));
374         metadata.setStopTime(metadata.getStartTime().shiftedBy(Constants.JULIAN_YEAR));
375         metadata.setAttitudeType(AttitudeType.QUATERNION_DERIVATIVE);
376         metadata.setIsFirst(true);
377         return metadata;
378     }
379 
380 }