1   /* Copyright 2002-2021 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.IOException;
20  
21  import org.hipparchus.exception.LocalizedCoreFormats;
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.files.ccsds.definitions.FrameFacade;
25  import org.orekit.files.ccsds.section.Header;
26  import org.orekit.files.ccsds.utils.generation.Generator;
27  import org.orekit.propagation.Propagator;
28  import org.orekit.propagation.SpacecraftState;
29  import org.orekit.propagation.sampling.OrekitFixedStepHandler;
30  import org.orekit.time.AbsoluteDate;
31  
32  /**
33   * A writer for AEM files.
34   *
35   * <p> Each instance corresponds to a single AEM file.
36   *
37   * <p> This class can be used as a step handler for a {@link Propagator}.
38   *
39   * <pre>{@code
40   * Propagator propagator = ...; // pre-configured propagator
41   * AEMWriter  aemWriter  = ...; // pre-configured writer
42   *   try (Generator out = ...;  // set-up output stream
43   *        StreamingAemWriter sw = new StreamingAemWriter(out, aemWriter)) { // set-up streaming writer
44   *
45   *     // write segment 1
46   *     propagator.getMultiplexer().add(step, sw.newSegment());
47   *     propagator.propagate(startDate1, stopDate1);
48   *
49   *     ...
50   *
51   *     // write segment n
52   *     propagator.getMultiplexer().clear();
53   *     propagator.getMultiplexer().add(step, sw.newSegment());
54   *     propagator.propagate(startDateN, stopDateN);
55   *
56   *   }
57   * }</pre>
58   * @author Bryan Cazabonne
59   * @author Luc Maisonobe
60   * @see <a href="https://public.ccsds.org/Pubs/504x0b1c1.pdf">CCSDS 504.0-B-1 Attitude Data Messages</a>
61   * @see AemWriter
62   * @since 10.2
63   */
64  public class StreamingAemWriter implements AutoCloseable {
65  
66      /** Generator for AEM output. */
67      private final Generator generator;
68  
69      /** Writer for the AEM message format. */
70      private final AemWriter writer;
71  
72      /** Header. */
73      private final Header header;
74  
75      /** Current metadata. */
76      private final AemMetadata metadata;
77  
78      /** Indicator for writing header. */
79      private boolean headerWritePending;
80  
81      /** Simple constructor.
82       * @param generator generator for AEM output
83       * @param writer writer for the AEM message format
84       * @param header file header (may be null)
85       * @param template template for metadata
86       * @since 11.0
87       */
88      public StreamingAemWriter(final Generator generator, final AemWriter writer,
89                                final Header header, final AemMetadata template) {
90          this.generator          = generator;
91          this.writer             = writer;
92          this.header             = header;
93          this.metadata           = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
94          this.headerWritePending = true;
95      }
96  
97      /**
98       * Create a writer for a new AEM attitude ephemeris segment.
99       * <p> The returned writer can only write a single attitude ephemeris segment in an AEM.
100      * This method must be called to create a writer for each attitude ephemeris segment.
101      * @return a new AEM segment writer, ready for use.
102      */
103     public SegmentWriter newSegment() {
104         return new SegmentWriter();
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public void close() throws IOException {
110         writer.writeFooter(generator);
111     }
112 
113     /** A writer for a segment of an AEM. */
114     public class SegmentWriter implements OrekitFixedStepHandler {
115 
116         /**
117          * {@inheritDoc}
118          *
119          * <p> Sets the {@link AemMetadataKey#START_TIME} and {@link AemMetadataKey#STOP_TIME} in this
120          * segment's metadata if not already set by the user. Then calls {@link AemWriter#writeHeader(Generator, Header)
121          * writeHeader} if it is the first segment) and {@link AemWriter#writeMetadata(Generator, AemMetadata)} to start the segment.
122          */
123         @Override
124         public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
125             try {
126                 final AbsoluteDate date = s0.getDate();
127                 if (t.isBefore(date)) {
128                     throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
129                             date, t, date.durationFrom(t));
130                 }
131 
132                 if (headerWritePending) {
133                     // we write the header only for the first segment
134                     writer.writeHeader(generator, header);
135                     headerWritePending = false;
136                 }
137 
138                 metadata.setStartTime(date);
139                 metadata.setUseableStartTime(null);
140                 metadata.setUseableStopTime(null);
141                 metadata.setStopTime(t);
142                 if (metadata.getEndpoints().getFrameA() == null ||
143                     metadata.getEndpoints().getFrameA().asSpacecraftBodyFrame() == null) {
144                     // the external frame must be frame A
145                     metadata.getEndpoints().setFrameA(FrameFacade.map(s0.getAttitude().getReferenceFrame()));
146                 } else {
147                     // the external frame must be frame B
148                     metadata.getEndpoints().setFrameB(FrameFacade.map(s0.getAttitude().getReferenceFrame()));
149                 }
150                 writer.writeMetadata(generator, metadata);
151                 writer.startAttitudeBlock(generator);
152             } catch (IOException e) {
153                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
154             }
155         }
156 
157         /** {@inheritDoc}. */
158         @Override
159         public void handleStep(final SpacecraftState currentState) {
160             try {
161                 writer.writeAttitudeEphemerisLine(generator, metadata, currentState.getAttitude().getOrientation());
162             } catch (IOException e) {
163                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
164             }
165         }
166 
167         /** {@inheritDoc}. */
168         @Override
169         public void finish(final SpacecraftState finalState) {
170             try {
171                 writer.endAttitudeBlock(generator);
172             } catch (IOException e) {
173                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
174             }
175         }
176 
177     }
178 
179 }