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.adm.aem;
18  
19  import java.io.ByteArrayInputStream;
20  import java.nio.charset.StandardCharsets;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import org.hipparchus.geometry.euclidean.threed.Rotation;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.junit.jupiter.api.Assertions;
27  import org.junit.jupiter.api.BeforeEach;
28  import org.junit.jupiter.api.Test;
29  import org.orekit.Utils;
30  import org.orekit.attitudes.AttitudeProvider;
31  import org.orekit.attitudes.FrameAlignedProvider;
32  import org.orekit.data.DataSource;
33  import org.orekit.files.ccsds.definitions.FrameFacade;
34  import org.orekit.files.ccsds.definitions.TimeSystem;
35  import org.orekit.files.ccsds.ndm.ParserBuilder;
36  import org.orekit.files.ccsds.ndm.WriterBuilder;
37  import org.orekit.files.ccsds.ndm.adm.AdmHeader;
38  import org.orekit.files.ccsds.ndm.adm.AttitudeType;
39  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
40  import org.orekit.frames.FramesFactory;
41  import org.orekit.orbits.CartesianOrbit;
42  import org.orekit.propagation.analytical.KeplerianPropagator;
43  import org.orekit.time.AbsoluteDate;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.PVCoordinates;
46  import org.orekit.utils.TimeStampedAngularCoordinates;
47  
48  
49  public class StreamingAemWriterTest {
50  
51      private static final double QUATERNION_PRECISION = 1e-5;
52      private static final double DATE_PRECISION = 1e-3;
53  
54      /** Set Orekit data. */
55      @BeforeEach
56      public void setUp() {
57          Utils.setDataRoot("regular-data");
58      }
59  
60      /**
61       * Check reading and writing an AEM both with and without using the step handler
62       * methods.
63       */
64      @Test
65      public void testWriteAemStepHandler() throws Exception {
66  
67          // Create a list of files
68          List<String> files = Collections.singletonList("/ccsds/adm/aem/AEMExample07.txt");
69          for (final String ex : files) {
70  
71              // Reference AEM file
72              final DataSource source0 = new DataSource(ex, () -> getClass().getResourceAsStream(ex));
73              AemParser parser = new ParserBuilder().buildAemParser();
74              Aem aem  = parser.parseMessage(source0);
75  
76              // Satellite attitude ephemeris as read from the reference file
77              AemSegment ephemerisBlock = aem.getSegments().get(0);
78  
79              // Meta data are extracted from the reference file
80              String            originator   = aem.getHeader().getOriginator();
81              String            objectName   = ephemerisBlock.getMetadata().getObjectName();
82              String            objectID     = ephemerisBlock.getMetadata().getObjectID();
83              String            headerCmt    = aem.getHeader().getComments().get(0);
84              FrameFacade       frameA       = ephemerisBlock.getMetadata().getEndpoints().getFrameA();
85              FrameFacade       frameB       = ephemerisBlock.getMetadata().getEndpoints().getFrameB();
86              boolean           a2b          = ephemerisBlock.getMetadata().getEndpoints().isA2b();
87              AttitudeType      attitudeType = ephemerisBlock.getMetadata().getAttitudeType();
88              boolean           isFirst      = ephemerisBlock.getMetadata().isFirst();
89  
90              // Initialize the header and metadata
91              // Here, we use only one data segment.
92              AdmHeader header = new AdmHeader();
93              header.setOriginator(originator);
94              header.addComment(headerCmt);
95  
96              AemMetadata metadata = new AemMetadata(1);
97              metadata.setTimeSystem(TimeSystem.UTC);
98              metadata.setObjectID(objectID);
99              metadata.setObjectName(objectName);
100             metadata.setAttitudeType(attitudeType);
101             metadata.setIsFirst(isFirst);
102             metadata.getEndpoints().setFrameA(frameA);
103             metadata.getEndpoints().setFrameB(frameB);
104             metadata.getEndpoints().setA2b(a2b);
105             metadata.setStartTime(AbsoluteDate.PAST_INFINITY);  // will be overwritten at propagation start
106             metadata.setStopTime(AbsoluteDate.FUTURE_INFINITY); // will be overwritten at propagation start
107 
108             StringBuilder buffer = new StringBuilder();
109             StreamingAemWriter writer =
110                             new StreamingAemWriter(new KvnGenerator(buffer, AemWriter.KVN_PADDING_WIDTH, ex + "-new",
111                                                                     Constants.JULIAN_DAY, 60),
112                                                    new WriterBuilder(). buildAemWriter(),
113                                                    header, metadata);
114 
115             // Initialize a Keplerian propagator with an Inertial attitude provider
116             // It is expected that all attitude data lines will have the same value
117             StreamingAemWriter.SegmentWriter segment = writer.newSegment();
118             KeplerianPropagator propagator =
119                             createPropagator(ephemerisBlock.getStart(),
120                                              new FrameAlignedProvider(ephemerisBlock.getAngularCoordinates().get(0).getRotation(),
121                                                                       FramesFactory.getEME2000()));
122 
123             // We propagate 60 seconds after the start date with a step equals to 10.0 seconds
124             // It is expected to have an attitude data block containing 7 data lines
125             double step = 10.0;
126             propagator.setStepHandler(step, segment);
127             propagator.propagate(ephemerisBlock.getStart().shiftedBy(60.0));
128             writer.close();
129 
130             // Generated AEM file
131             final DataSource source1 = new DataSource("buffer",
132                                                    () -> new ByteArrayInputStream(buffer.toString().getBytes(StandardCharsets.UTF_8)));
133             Aem generatedAem = parser.parseMessage(source1);
134 
135             // There is only one attitude ephemeris block
136             Assertions.assertEquals(1, generatedAem.getSegments().size());
137             AemSegment attitudeBlocks = generatedAem.getSegments().get(0);
138             // There are 7 data lines in the attitude ephemeris block
139             List<? extends TimeStampedAngularCoordinates> ac  = attitudeBlocks.getAngularCoordinates();
140             Assertions.assertEquals(7, ac.size());
141 
142             // Verify
143             for (int i = 0; i < 7; i++) {
144                 Assertions.assertEquals(step * i, ac.get(i).getDate().durationFrom(ephemerisBlock.getStart()), DATE_PRECISION);
145                 Rotation rot = ac.get(i).getRotation();
146                 Assertions.assertEquals(0.68427, rot.getQ0(), QUATERNION_PRECISION);
147                 Assertions.assertEquals(0.56748, rot.getQ1(), QUATERNION_PRECISION);
148                 Assertions.assertEquals(0.03146, rot.getQ2(), QUATERNION_PRECISION);
149                 Assertions.assertEquals(0.45689, rot.getQ3(), QUATERNION_PRECISION);
150             }
151 
152         }
153 
154     }
155 
156     /**
157      * Create a Keplerian propagator.
158      * @param date reference date
159      * @param attitudeProv attitude provider
160      * @return a Keplerian propagator
161      */
162     private KeplerianPropagator createPropagator(AbsoluteDate date,
163                                                  AttitudeProvider attitudeProv) {
164         Vector3D position = new Vector3D(-29536113.0, 30329259.0, -100125.0);
165         Vector3D velocity = new Vector3D(-2194.0, -2141.0, -8.0);
166         PVCoordinates pvCoordinates = new PVCoordinates( position, velocity);
167         double mu = 3.9860047e14;
168 
169         CartesianOrbit p = new CartesianOrbit(pvCoordinates, FramesFactory.getEME2000(), date, mu);
170 
171         return new KeplerianPropagator(p, attitudeProv);
172     }
173 
174 }