AttitudeWriter.java

  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. import java.io.IOException;
  19. import java.util.List;

  20. import org.orekit.errors.OrekitIllegalArgumentException;
  21. import org.orekit.errors.OrekitMessages;
  22. import org.orekit.files.ccsds.definitions.FrameFacade;
  23. import org.orekit.files.ccsds.ndm.adm.AdmHeader;
  24. import org.orekit.files.ccsds.utils.FileFormat;
  25. import org.orekit.files.ccsds.utils.generation.Generator;
  26. import org.orekit.files.ccsds.utils.generation.KvnGenerator;
  27. import org.orekit.files.ccsds.utils.generation.XmlGenerator;
  28. import org.orekit.files.general.AttitudeEphemerisFile;
  29. import org.orekit.files.general.AttitudeEphemerisFile.SatelliteAttitudeEphemeris;
  30. import org.orekit.files.general.AttitudeEphemerisFileWriter;
  31. import org.orekit.utils.AccurateFormatter;
  32. import org.orekit.utils.Formatter;
  33. import org.orekit.utils.TimeStampedAngularCoordinates;

  34. /** An {@link AttitudeEphemerisFileWriter} generating {@link Aem AEM} files.
  35.  * @author Bryan Cazabonne
  36.  * @since 11.0
  37.  */
  38. public class AttitudeWriter implements AttitudeEphemerisFileWriter {

  39.     /** Underlying writer. */
  40.     private final AemWriter writer;

  41.     /** Header. */
  42.     private final AdmHeader header;

  43.     /** Current metadata. */
  44.     private final AemMetadata metadata;

  45.     /** File format to use. */
  46.     private final FileFormat fileFormat;

  47.     /** Output name for error messages. */
  48.     private final String outputName;

  49.     /** Maximum offset for relative dates.
  50.      * @since 12.0
  51.      */
  52.     private final double maxRelativeOffset;

  53.     /** Column number for aligning units. */
  54.     private final int unitsColumn;

  55.     /** Used to format dates and doubles to string. */
  56.     private final Formatter formatter;

  57.     /**
  58.      * Constructor used to create a new AEM writer configured with the necessary parameters
  59.      * to successfully fill in all required fields that aren't part of a standard object.
  60.      * <p>
  61.      * If the mandatory header entries are not present (or if header is null),
  62.      * built-in defaults will be used
  63.      * </p>
  64.      * <p>
  65.      * The writer is built from the complete header and partial metadata. The template
  66.      * metadata is used to initialize and independent local copy, that will be updated
  67.      * as new segments are written (with at least the segment start and stop will change,
  68.      * but some other parts may change too). The {@code template} argument itself is not
  69.      * changed.
  70.      * </p>
  71.      * <p>
  72.      * Calling this constructor directly is not recommended. Users should rather use
  73.      * {@link org.orekit.files.ccsds.ndm.WriterBuilder#buildAemWriter()}.
  74.      * </p>
  75.      * @param writer underlying writer
  76.      * @param header file header (may be null)
  77.      * @param template template for metadata
  78.      * @param fileFormat file format to use
  79.      * @param outputName output name for error messages
  80.      * @param maxRelativeOffset maximum offset in seconds to use relative dates
  81.      * (if a date is too far from reference, it will be displayed as calendar elements)
  82.      * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
  83.      * @param formatter how to format date and double to string.
  84.      * @since 13.0
  85.      */
  86.     public AttitudeWriter(final AemWriter writer,
  87.                           final AdmHeader header, final AemMetadata template,
  88.                           final FileFormat fileFormat, final String outputName,
  89.                           final double maxRelativeOffset, final int unitsColumn, final Formatter formatter) {
  90.         this.writer            = writer;
  91.         this.header            = header;
  92.         this.metadata          = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
  93.         this.fileFormat        = fileFormat;
  94.         this.outputName        = outputName;
  95.         this.maxRelativeOffset = maxRelativeOffset;
  96.         this.unitsColumn       = unitsColumn;
  97.         this.formatter = formatter;
  98.     }

  99.     /**
  100.      * Constructor used to create a new AEM writer configured with the necessary parameters
  101.      * to successfully fill in all required fields that aren't part of a standard object.
  102.      * <p>
  103.      * If the mandatory header entries are not present (or if header is null),
  104.      * built-in defaults will be used
  105.      * </p>
  106.      * <p>
  107.      * The writer is built from the complete header and partial metadata. The template
  108.      * metadata is used to initialize and independent local copy, that will be updated
  109.      * as new segments are written (with at least the segment start and stop will change,
  110.      * but some other parts may change too). The {@code template} argument itself is not
  111.      * changed.
  112.      * </p>
  113.      * <p>
  114.      * Calling this constructor directly is not recommended. Users should rather use
  115.      * {@link org.orekit.files.ccsds.ndm.WriterBuilder#buildAemWriter()}.
  116.      * </p>
  117.      * @param writer underlying writer
  118.      * @param header file header (may be null)
  119.      * @param template template for metadata
  120.      * @param fileFormat file format to use
  121.      * @param outputName output name for error messages
  122.      * @param maxRelativeOffset maximum offset in seconds to use relative dates
  123.      * (if a date is too far from reference, it will be displayed as calendar elements)
  124.      * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
  125.      * @since 12.0
  126.      */
  127.     public AttitudeWriter(final AemWriter writer,
  128.                           final AdmHeader header, final AemMetadata template,
  129.                           final FileFormat fileFormat, final String outputName,
  130.                           final double maxRelativeOffset, final int unitsColumn) {
  131.         this(writer, header, template, fileFormat, outputName, maxRelativeOffset, unitsColumn, new AccurateFormatter());
  132.     }

  133.     /** {@inheritDoc}
  134.      * <p>
  135.      * As {@code AttitudeEphemerisFile.SatelliteAttitudeEphemeris} does not have all the entries
  136.      * from {@link AemMetadata}, the only values that will be extracted from the
  137.      * {@code ephemerisFile} will be the start time, stop time, reference frame, interpolation
  138.      * method and interpolation degree. The missing values (like object name, local spacecraft
  139.      * body frame, attitude type...) will be inherited from the template  metadata set at writer
  140.      * {@link #AttitudeWriter(AemWriter, AdmHeader, AemMetadata, FileFormat, String, double, int) construction}.
  141.      * </p>
  142.      */
  143.     @Override
  144.     public <C extends TimeStampedAngularCoordinates, S extends AttitudeEphemerisFile.AttitudeEphemerisSegment<C>>
  145.         void write(final Appendable appendable, final AttitudeEphemerisFile<C, S> ephemerisFile)
  146.         throws IOException {

  147.         if (appendable == null) {
  148.             throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
  149.         }

  150.         if (ephemerisFile == null) {
  151.             return;
  152.         }

  153.         final SatelliteAttitudeEphemeris<C, S> satEphem =
  154.                         ephemerisFile.getSatellites().get(metadata.getObjectID());
  155.         if (satEphem == null) {
  156.             throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
  157.                                                      metadata.getObjectID(), "ephemerisFile");
  158.         }

  159.         // Get attitude ephemeris segments to output.
  160.         final List<S> segments = satEphem.getSegments();
  161.         if (segments.isEmpty()) {
  162.             // No data -> No output
  163.             return;
  164.         }

  165.         try (Generator generator = fileFormat == FileFormat.KVN ?
  166.              new KvnGenerator(appendable, AemWriter.KVN_PADDING_WIDTH, outputName,
  167.                               maxRelativeOffset, unitsColumn, formatter) :
  168.              new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName,
  169.                               maxRelativeOffset, unitsColumn > 0, null, formatter)) {

  170.             writer.writeHeader(generator, header);

  171.             // Loop on segments
  172.             for (final S segment : segments) {
  173.                 writeSegment(generator, segment);
  174.             }

  175.             writer.writeFooter(generator);

  176.         }

  177.     }

  178.     /** Write one segment.
  179.      * @param generator generator to use for producing output
  180.      * @param segment segment to write
  181.      * @param <C> type of the angular coordinates
  182.      * @param <S> type of the segment
  183.      * @throws IOException if any buffer writing operations fails
  184.      */
  185.     private <C extends TimeStampedAngularCoordinates, S extends AttitudeEphemerisFile.AttitudeEphemerisSegment<C>>
  186.         void writeSegment(final Generator generator, final S segment) throws IOException {

  187.         // override template metadata with segment values
  188.         metadata.setStartTime(segment.getStart());
  189.         metadata.setStopTime(segment.getStop());
  190.         if (metadata.getEndpoints().getFrameA() == null ||
  191.             metadata.getEndpoints().getFrameA().asSpacecraftBodyFrame() == null) {
  192.             // the external frame must be frame A
  193.             metadata.getEndpoints().setFrameA(FrameFacade.map(segment.getReferenceFrame()));
  194.         } else {
  195.             // the external frame must be frame B
  196.             metadata.getEndpoints().setFrameB(FrameFacade.map(segment.getReferenceFrame()));
  197.         }
  198.         metadata.setInterpolationMethod(segment.getInterpolationMethod());
  199.         metadata.setInterpolationDegree(segment.getInterpolationSamples() - 1);
  200.         metadata.validate(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
  201.         writer.writeMetadata(generator,
  202.                              header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
  203.                              metadata);

  204.         // Loop on attitude data
  205.         writer.startAttitudeBlock(generator);
  206.         if (segment instanceof AemSegment) {
  207.             generator.writeComments(((AemSegment) segment).getData().getComments());
  208.         }
  209.         for (final TimeStampedAngularCoordinates coordinates : segment.getAngularCoordinates()) {
  210.             writer.writeAttitudeEphemerisLine(generator,
  211.                                               header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
  212.                                               metadata, coordinates);
  213.         }
  214.         writer.endAttitudeBlock(generator);

  215.     }

  216. }