1   /* Copyright 2002-2026 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  
18  package org.orekit.files.ccsds.ndm.odm.ocm;
19  
20  import java.io.IOException;
21  import java.util.List;
22  
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.files.ccsds.definitions.DutyCycleType;
27  import org.orekit.files.ccsds.definitions.TimeConverter;
28  import org.orekit.files.ccsds.section.AbstractWriter;
29  import org.orekit.files.ccsds.utils.FileFormat;
30  import org.orekit.files.ccsds.utils.generation.Generator;
31  import org.orekit.utils.Formatter;
32  import org.orekit.utils.units.Unit;
33  
34  /** Writer for maneuvers history data.
35   * @author Luc Maisonobe
36   * @since 11.0
37   */
38  class OrbitManeuverHistoryWriter extends AbstractWriter {
39  
40      /** Maneuvers history block. */
41      private final OrbitManeuverHistory history;
42  
43      /** Converter for dates. */
44      private final TimeConverter timeConverter;
45  
46      /** Create a writer.
47       * @param maneuverHistory maneuvers history to write
48       * @param timeConverter converter for dates
49       */
50      OrbitManeuverHistoryWriter(final OrbitManeuverHistory maneuverHistory,
51                            final TimeConverter timeConverter) {
52          super(OcmDataSubStructureKey.man.name(), OcmDataSubStructureKey.MAN.name());
53          this.history       = maneuverHistory;
54          this.timeConverter = timeConverter;
55      }
56  
57      /** {@inheritDoc} */
58      @Override
59      protected void writeContent(final Generator generator) throws IOException {
60  
61          // maneuvers history block
62          final OrbitManeuverHistoryMetadata metadata = history.getMetadata();
63          generator.writeComments(metadata.getComments());
64  
65          // identifiers
66          generator.writeEntry(OrbitManeuverHistoryMetadataKey.MAN_ID.name(),        metadata.getManID(),       null, false);
67          generator.writeOptionalStringEntry(OrbitManeuverHistoryMetadataKey.MAN_PREV_ID.name(),   metadata.getManPrevID(),   null, false);
68          generator.writeOptionalStringEntry(OrbitManeuverHistoryMetadataKey.MAN_NEXT_ID.name(),   metadata.getManNextID(),   null, false);
69          generator.writeOptionalEnumEntry(OrbitManeuverHistoryMetadataKey.MAN_BASIS.name(),     metadata.getManBasis(),          false);
70          generator.writeOptionalStringEntry(OrbitManeuverHistoryMetadataKey.MAN_BASIS_ID.name(),  metadata.getManBasisID(),  null, false);
71          generator.writeEntry(OrbitManeuverHistoryMetadataKey.MAN_DEVICE_ID.name(), metadata.getManDeviceID(), null, false);
72  
73          // time
74          generator.writeOptionalDateEntry(OrbitManeuverHistoryMetadataKey.MAN_PREV_EPOCH.name(), timeConverter, metadata.getManPrevEpoch(), true, false);
75          generator.writeOptionalDateEntry(OrbitManeuverHistoryMetadataKey.MAN_NEXT_EPOCH.name(), timeConverter, metadata.getManNextEpoch(), true, false);
76  
77          // references
78          generator.writeEntry(OrbitManeuverHistoryMetadataKey.MAN_PURPOSE.name(),      metadata.getManPurpose(),                          false);
79          generator.writeOptionalStringEntry(OrbitManeuverHistoryMetadataKey.MAN_PRED_SOURCE.name(),  metadata.getManPredSource(),                 null, false);
80          generator.writeEntry(OrbitManeuverHistoryMetadataKey.MAN_REF_FRAME.name(),    metadata.getManReferenceFrame().getName(),   null, false);
81          if (!metadata.getManFrameEpoch().equals(timeConverter.getReferenceDate()) &&
82              metadata.getManReferenceFrame().asOrbitRelativeFrame().isEmpty() &&
83              metadata.getManReferenceFrame().asSpacecraftBodyFrame().isEmpty()) {
84              generator.writeEntry(OrbitManeuverHistoryMetadataKey.MAN_FRAME_EPOCH.name(),  timeConverter, metadata.getManFrameEpoch(),  true, false);
85          }
86          if (metadata.getGravitationalAssist().isPresent()) {
87              generator.writeEntry(OrbitManeuverHistoryMetadataKey.GRAV_ASSIST_NAME.name(), metadata.getGravitationalAssist().get().getName(), null, false);
88          }
89  
90          // duty cycle
91          final boolean notContinuous = metadata.getDcType() != DutyCycleType.CONTINUOUS;
92          final boolean timeAndAngle  = metadata.getDcType() == DutyCycleType.TIME_AND_ANGLE;
93          generator.writeEntry(OrbitManeuverHistoryMetadataKey.DC_TYPE.name(), metadata.getDcType(), false);
94          generator.writeOptionalDateEntry(OrbitManeuverHistoryMetadataKey.DC_WIN_OPEN.name(),  timeConverter, metadata.getDcWindowOpen(),  false, notContinuous);
95          generator.writeOptionalDateEntry(OrbitManeuverHistoryMetadataKey.DC_WIN_CLOSE.name(), timeConverter, metadata.getDcWindowClose(), false, notContinuous);
96          if (metadata.getDcMinCycles().isPresent()) {
97              generator.writeEntry(OrbitManeuverHistoryMetadataKey.DC_MIN_CYCLES.name(), metadata.getDcMinCycles().get(), false);
98          }
99          if (metadata.getDcMaxCycles().isPresent()) {
100             generator.writeEntry(OrbitManeuverHistoryMetadataKey.DC_MAX_CYCLES.name(), metadata.getDcMaxCycles().get(), false);
101         }
102         generator.writeOptionalDateEntry(OrbitManeuverHistoryMetadataKey.DC_EXEC_START.name(),          timeConverter, metadata.getDcExecStart(), false, notContinuous);
103         generator.writeOptionalDateEntry(OrbitManeuverHistoryMetadataKey.DC_EXEC_STOP.name(),           timeConverter, metadata.getDcExecStop(),  false, notContinuous);
104         generator.writeOptionalDateEntry(OrbitManeuverHistoryMetadataKey.DC_REF_TIME.name(),            timeConverter, metadata.getDcRefTime(),   false, notContinuous);
105         generator.writeOptionalDoubleEntry(OrbitManeuverHistoryMetadataKey.DC_TIME_PULSE_DURATION.name(), metadata.getDcTimePulseDuration(), Unit.SECOND,  notContinuous);
106         generator.writeOptionalDoubleEntry(OrbitManeuverHistoryMetadataKey.DC_TIME_PULSE_PERIOD.name(),   metadata.getDcTimePulsePeriod(),   Unit.SECOND,  notContinuous);
107         if (timeAndAngle) {
108             // If timeAnAngle, all values are mandatory. That's why we use .orElseThrow() method
109             generator.writeEntry(OrbitManeuverHistoryMetadataKey.DC_REF_DIR.name(),
110                     toString(metadata.getDcRefDir().orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_OPTIONAL_VALUE)), generator.getFormatter()),
111                     null, true);
112             generator.writeEntry(OrbitManeuverHistoryMetadataKey.DC_BODY_FRAME.name(),
113                                  metadata.getDcBodyFrame().orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_OPTIONAL_VALUE)).toString().replace(' ', '_'),
114                                  null, true);
115             generator.writeEntry(OrbitManeuverHistoryMetadataKey.DC_BODY_TRIGGER.name(),
116                     toString(metadata.getDcBodyTrigger().orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_OPTIONAL_VALUE)), generator.getFormatter()),
117                     null, true);
118             generator.writeOptionalDoubleEntry(OrbitManeuverHistoryMetadataKey.DC_PA_START_ANGLE.name(), metadata.getDcPhaseStartAngle(), Unit.DEGREE, true);
119             generator.writeOptionalDoubleEntry(OrbitManeuverHistoryMetadataKey.DC_PA_STOP_ANGLE.name(),  metadata.getDcPhaseStopAngle(),  Unit.DEGREE, true);
120         }
121 
122         // elements
123         final List<ManeuverFieldType> types       = metadata.getManComposition();
124         final StringBuilder           composition = new StringBuilder();
125         for (int i = 0; i < types.size(); ++i) {
126             if (i > 0) {
127                 composition.append(',');
128             }
129             composition.append(types.get(i).name());
130         }
131         generator.writeEntry(OrbitManeuverHistoryMetadataKey.MAN_COMPOSITION.name(), composition.toString(),                        null, false);
132         generator.writeEntry(OrbitManeuverHistoryMetadataKey.MAN_UNITS.name(), generator.unitsListToString(metadata.getManUnits()), null, false);
133 
134         // data
135         for (final OrbitManeuver maneuver : history.getManeuvers()) {
136             final StringBuilder line = new StringBuilder();
137             for (int i = 0; i < types.size(); ++i) {
138                 if (i > 0) {
139                     line.append(' ');
140                 }
141                 line.append(types.get(i).outputField(timeConverter, maneuver, generator.getFormatter()));
142             }
143             if (generator.getFormat() == FileFormat.XML) {
144                 generator.writeEntry(Ocm.MAN_LINE, line.toString(), null, true);
145             } else {
146                 generator.writeRawData(line);
147                 generator.newLine();
148             }
149         }
150     }
151 
152     /** Convert a vector to a space separated string.
153      * @param vector vector to convert
154      * @param formatter to use for double and date to string
155      * @return corresponding string
156      */
157     private String toString(final Vector3D vector, final Formatter formatter) {
158         final StringBuilder builder = new StringBuilder();
159         builder.append(formatter.toString(Unit.ONE.fromSI(vector.getX())));
160         builder.append(' ');
161         builder.append(formatter.toString(Unit.ONE.fromSI(vector.getY())));
162         builder.append(' ');
163         builder.append(formatter.toString(Unit.ONE.fromSI(vector.getZ())));
164         return builder.toString();
165     }
166 
167 }