StreamingCpfWriter.java

  1. /* Copyright 2002-2023 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.ilrs;

  18. import java.io.IOException;
  19. import java.util.Locale;

  20. import org.hipparchus.exception.LocalizedCoreFormats;
  21. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  22. import org.orekit.errors.OrekitException;
  23. import org.orekit.frames.Frame;
  24. import org.orekit.propagation.Propagator;
  25. import org.orekit.propagation.SpacecraftState;
  26. import org.orekit.propagation.sampling.OrekitFixedStepHandler;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.time.DateTimeComponents;
  29. import org.orekit.time.TimeScale;
  30. import org.orekit.utils.TimeStampedPVCoordinates;

  31. /**
  32.  * A writer for CPF files.
  33.  *
  34.  * <p> Each instance corresponds to a single CPF file.
  35.  *
  36.  * <p> This class can be used as a step handler for a {@link Propagator}.
  37.  * The following example shows its use as a step handler.
  38.  *
  39.  * <p>
  40.  * <b>Note:</b> By default, only required header keys are wrote (H1 and H2). Furthermore, only position data can be written.
  41.  * Other keys (optionals) are simply ignored.
  42.  * Contributions are welcome to support more fields in the format.
  43.  *
  44.  * @author Bryan Cazabonne
  45.  * @since 10.3
  46.  */
  47. public class StreamingCpfWriter {

  48.     /** New line separator for output file. */
  49.     private static final String NEW_LINE = "\n";

  50.     /** String A2 Format. */
  51.     private static final String A1 = "%1s";

  52.     /** String A2 Format. */
  53.     private static final String A2 = "%2s";

  54.     /** String A3 Format. */
  55.     private static final String A3 = "%3s";

  56.     /** String A4 Format. */
  57.     private static final String A4 = "%4s";

  58.     /** String A8 Format. */
  59.     private static final String A8 = "%8s";

  60.     /** String A10 Format. */
  61.     private static final String A10 = "%10s";

  62.     /** Integer I1 Format. */
  63.     private static final String I1 = "%1d";

  64.     /** Integer I2 Format. */
  65.     private static final String I2 = "%2d";

  66.     /** Integer I3 Format. */
  67.     private static final String I3 = "%3d";

  68.     /** Integer I4 Format. */
  69.     private static final String I4 = "%4d";

  70.     /** Integer I5 Format. */
  71.     private static final String I5 = "%5d";

  72.     /** Real 13.6 Format. */
  73.     private static final String F13_6 = "%13.6f";

  74.     /** Real 17.3 Format. */
  75.     private static final String F17_3 = "%17.3f";

  76.     /** Real 19.6 Format. */
  77.     private static final String F19_6 = "%19.6f";

  78.     /** Space. */
  79.     private static final String SPACE = " ";

  80.     /** Empty string. */
  81.     private static final String EMPTY_STRING = "";

  82.     /** File format. */
  83.     private static final String FORMAT = "CPF";

  84.     /** Default locale. */
  85.     private static final Locale STANDARDIZED_LOCALE = Locale.US;

  86.     /** Default value for direction flag in position record. */
  87.     private static final int DEFAULT_DIRECTION_FLAG = 0;

  88.     /** Output stream. */
  89.     private final Appendable writer;

  90.     /** Time scale for all dates. */
  91.     private final TimeScale timeScale;

  92.     /** Container for header data. */
  93.     private final CPFHeader header;

  94.     /** Flag for optional velocity record. */
  95.     private final boolean velocityFlag;

  96.     /**
  97.      * Create a CPF writer than streams data to the given output stream.
  98.      * <p>
  99.      * Using this constructor, velocity data are not written.
  100.      * </p>
  101.      * @param writer     the output stream for the CPF file.
  102.      * @param timeScale  for all times in the CPF
  103.      * @param header     container for header data
  104.      * @see #StreamingCpfWriter(Appendable, TimeScale, CPFHeader, boolean)
  105.      */
  106.     public StreamingCpfWriter(final Appendable writer,
  107.                               final TimeScale timeScale,
  108.                               final CPFHeader header) {
  109.         this(writer, timeScale, header, false);
  110.     }

  111.     /**
  112.      * Create a CPF writer than streams data to the given output stream.
  113.      *
  114.      * @param writer       the output stream for the CPF file.
  115.      * @param timeScale    for all times in the CPF
  116.      * @param header       container for header data
  117.      * @param velocityFlag true if velocity must be written
  118.      * @since 11.2
  119.      */
  120.     public StreamingCpfWriter(final Appendable writer,
  121.                               final TimeScale timeScale,
  122.                               final CPFHeader header,
  123.                               final boolean velocityFlag) {
  124.         this.writer       = writer;
  125.         this.timeScale    = timeScale;
  126.         this.header       = header;
  127.         this.velocityFlag = velocityFlag;
  128.     }

  129.     /**
  130.      * Writes the CPF header for the file.
  131.      * @throws IOException if the stream cannot write to stream
  132.      */
  133.     public void writeHeader() throws IOException {

  134.         // Write H1
  135.         HeaderLineWriter.H1.write(header, writer, timeScale);
  136.         writer.append(NEW_LINE);

  137.         // Write H2
  138.         HeaderLineWriter.H2.write(header, writer, timeScale);
  139.         writer.append(NEW_LINE);

  140.         // End of header
  141.         writer.append("H9");
  142.         writer.append(NEW_LINE);

  143.     }

  144.     /**
  145.      * Write end of file.
  146.      * @throws IOException if the stream cannot write to stream
  147.      */
  148.     public void writeEndOfFile() throws IOException {
  149.         writer.append("99");
  150.     }

  151.     /**
  152.      * Create a writer for a new CPF ephemeris segment.
  153.      * <p>
  154.      * The returned writer can only write a single ephemeris segment in a CPF.
  155.      * </p>
  156.      * @param frame the reference frame to use for the segment.
  157.      * @return a new CPF segment, ready for writing.
  158.      */
  159.     public Segment newSegment(final Frame frame) {
  160.         return new Segment(frame);
  161.     }

  162.     /**
  163.      * Write a String value in the file.
  164.      * @param cpfWriter writer
  165.      * @param format format
  166.      * @param value value
  167.      * @param withSpace true if a space must be added
  168.      * @throws IOException if value cannot be written
  169.      */
  170.     private static void writeValue(final Appendable cpfWriter, final String format,
  171.                                    final String value, final boolean withSpace)
  172.         throws IOException {
  173.         cpfWriter.append(String.format(STANDARDIZED_LOCALE, format, value)).append(withSpace ? SPACE : EMPTY_STRING);
  174.     }

  175.     /**
  176.      * Write a integer value in the file.
  177.      * @param cpfWriter writer
  178.      * @param format format
  179.      * @param value value
  180.      * @param withSpace true if a space must be added
  181.      * @throws IOException if value cannot be written
  182.      */
  183.     private static void writeValue(final Appendable cpfWriter, final String format,
  184.                                    final int value, final boolean withSpace)
  185.         throws IOException {
  186.         cpfWriter.append(String.format(STANDARDIZED_LOCALE, format, value)).append(withSpace ? SPACE : EMPTY_STRING);
  187.     }

  188.     /**
  189.      * Write a real value in the file.
  190.      * @param cpfWriter writer
  191.      * @param format format
  192.      * @param value value
  193.      * @param withSpace true if a space must be added
  194.      * @throws IOException if value cannot be written
  195.      */
  196.     private static void writeValue(final Appendable cpfWriter, final String format,
  197.                                    final double value, final boolean withSpace)
  198.         throws IOException {
  199.         cpfWriter.append(String.format(STANDARDIZED_LOCALE, format, value)).append(withSpace ? SPACE : EMPTY_STRING);
  200.     }

  201.     /**
  202.      * Write a String value in the file.
  203.      * @param cpfWriter writer
  204.      * @param format format
  205.      * @param value value
  206.      * @param withSpace true if a space must be added
  207.      * @throws IOException if value cannot be written
  208.      */
  209.     private static void writeValue(final Appendable cpfWriter, final String format,
  210.                                    final boolean value, final boolean withSpace)
  211.         throws IOException {
  212.         // Change to an integer value
  213.         final int intValue = value ? 1 : 0;
  214.         writeValue(cpfWriter, format, intValue, withSpace);
  215.     }

  216.     /** A writer for a segment of a CPF. */
  217.     public class Segment implements OrekitFixedStepHandler {

  218.         /** Reference frame of the output states. */
  219.         private final Frame frame;

  220.         /**
  221.          * Create a new segment writer.
  222.          *
  223.          * @param frame    for the output states. Used by {@link #handleStep(SpacecraftState,
  224.          *                 boolean)}.
  225.          */
  226.         private Segment(final Frame frame) {
  227.             this.frame = frame;
  228.         }

  229.         /** {@inheritDoc}. */
  230.         @Override
  231.         public void handleStep(final SpacecraftState currentState) {
  232.             try {

  233.                 // Write ephemeris line
  234.                 writeEphemerisLine(currentState.getPVCoordinates(frame));

  235.             } catch (IOException e) {
  236.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE,
  237.                                           e.getLocalizedMessage());
  238.             }

  239.         }

  240.         /** {@inheritDoc}. */
  241.         @Override
  242.         public void finish(final SpacecraftState finalState) {
  243.             try {
  244.                 // Write ephemeris line
  245.                 writeEphemerisLine(finalState.getPVCoordinates(frame));

  246.                 // Write end of file
  247.                 writeEndOfFile();

  248.             } catch (IOException e) {
  249.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE,
  250.                                           e.getLocalizedMessage());
  251.             }

  252.         }

  253.         /**
  254.          * Write ephemeris lines.
  255.          * <p>
  256.          * If <code>velocityFlag</code> is equals to true, both
  257.          * position and velocity records are written. Otherwise,
  258.          * only the position data are used.
  259.          * </p>
  260.          * @param pv the time, position, and velocity to write.
  261.          * @throws IOException if the output stream throws one while writing.
  262.          */
  263.         public void writeEphemerisLine(final TimeStampedPVCoordinates pv)
  264.             throws IOException {

  265.             // Record type and direction flag
  266.             writeValue(writer, A2, "10",                                    true);
  267.             writeValue(writer, I1, DEFAULT_DIRECTION_FLAG,                  true);

  268.             // Epoch
  269.             final AbsoluteDate epoch = pv.getDate();
  270.             final DateTimeComponents dtc = epoch.getComponents(timeScale);
  271.             writeValue(writer, I5, dtc.getDate().getMJD(),                  true);
  272.             writeValue(writer, F13_6, dtc.getTime().getSecondsInLocalDay(), true);

  273.             // Leap second flag (default 0)
  274.             writeValue(writer, I2, 0, true);

  275.             // Position
  276.             final Vector3D position = pv.getPosition();
  277.             writeValue(writer, F17_3, position.getX(), true);
  278.             writeValue(writer, F17_3, position.getY(), true);
  279.             writeValue(writer, F17_3, position.getZ(), false);

  280.             // New line
  281.             writer.append(NEW_LINE);

  282.             // Write the velocity record
  283.             if (velocityFlag) {

  284.                 // Record type and direction flag
  285.                 writeValue(writer, A2, "20",                                    true);
  286.                 writeValue(writer, I1, DEFAULT_DIRECTION_FLAG,                  true);

  287.                 // Velocity
  288.                 final Vector3D velocity = pv.getVelocity();
  289.                 writeValue(writer, F19_6, velocity.getX(), true);
  290.                 writeValue(writer, F19_6, velocity.getY(), true);
  291.                 writeValue(writer, F19_6, velocity.getZ(), false);

  292.                 // New line
  293.                 writer.append(NEW_LINE);

  294.             }

  295.         }

  296.     }

  297.     /** Writer for specific header lines. */
  298.     public enum HeaderLineWriter {

  299.         /** Header first line. */
  300.         H1("H1") {

  301.             /** {@inheritDoc} */
  302.             @Override
  303.             public void write(final CPFHeader cpfHeader, final Appendable cpfWriter, final TimeScale timescale)
  304.                 throws IOException {

  305.                 // write first keys
  306.                 writeValue(cpfWriter, A2, getIdentifier(),                           true);
  307.                 writeValue(cpfWriter, A3, FORMAT,                                    true);
  308.                 writeValue(cpfWriter, I2, cpfHeader.getVersion(),                    true);
  309.                 writeValue(cpfWriter, A1, SPACE, false); // One additional column, see CPF v1 format
  310.                 writeValue(cpfWriter, A3, cpfHeader.getSource(),                     true);
  311.                 writeValue(cpfWriter, I4, cpfHeader.getProductionEpoch().getYear(),  true);
  312.                 writeValue(cpfWriter, I2, cpfHeader.getProductionEpoch().getMonth(), true);
  313.                 writeValue(cpfWriter, I2, cpfHeader.getProductionEpoch().getDay(),   true);
  314.                 writeValue(cpfWriter, I2, cpfHeader.getProductionHour(),             true);
  315.                 writeValue(cpfWriter, A1, SPACE, false); // One additional column, see CPF v1 format
  316.                 writeValue(cpfWriter, I3, cpfHeader.getSequenceNumber(),             true);

  317.                 // check file version
  318.                 if (cpfHeader.getVersion() == 2) {
  319.                     writeValue(cpfWriter, I2, cpfHeader.getSubDailySequenceNumber(), true);
  320.                 }

  321.                 // write target name from official list
  322.                 writeValue(cpfWriter, A10, cpfHeader.getName(),                      true);

  323.                 // write notes (not supported yet)
  324.                 writeValue(cpfWriter, A10, SPACE,                                    false);
  325.             }

  326.         },

  327.         /** Header second line. */
  328.         H2("H2") {

  329.             /** {@inheritDoc} */
  330.             @Override
  331.             public void write(final CPFHeader cpfHeader, final Appendable cpfWriter, final TimeScale timescale)
  332.                 throws IOException {

  333.                 // write identifiers
  334.                 writeValue(cpfWriter, A2, getIdentifier(),                                 true);
  335.                 writeValue(cpfWriter, A8, cpfHeader.getIlrsSatelliteId(),                  true);
  336.                 writeValue(cpfWriter, A4, cpfHeader.getSic(),                              true);
  337.                 writeValue(cpfWriter, A8, cpfHeader.getNoradId(),                          true);

  338.                 // write starting epoch
  339.                 final AbsoluteDate starting = cpfHeader.getStartEpoch();
  340.                 final DateTimeComponents dtcStart = starting.getComponents(timescale);
  341.                 writeValue(cpfWriter, I4, dtcStart.getDate().getYear(),                    true);
  342.                 writeValue(cpfWriter, I2, dtcStart.getDate().getMonth(),                   true);
  343.                 writeValue(cpfWriter, I2, dtcStart.getDate().getDay(),                     true);
  344.                 writeValue(cpfWriter, I2, dtcStart.getTime().getHour(),                    true);
  345.                 writeValue(cpfWriter, I2, dtcStart.getTime().getMinute(),                  true);
  346.                 writeValue(cpfWriter, I2, (int) dtcStart.getTime().getSecond(),            true);

  347.                 // write ending epoch
  348.                 final AbsoluteDate ending = cpfHeader.getEndEpoch();
  349.                 final DateTimeComponents dtcEnd = ending.getComponents(timescale);
  350.                 writeValue(cpfWriter, I4, dtcEnd.getDate().getYear(),                      true);
  351.                 writeValue(cpfWriter, I2, dtcEnd.getDate().getMonth(),                     true);
  352.                 writeValue(cpfWriter, I2, dtcEnd.getDate().getDay(),                       true);
  353.                 writeValue(cpfWriter, I2, dtcEnd.getTime().getHour(),                      true);
  354.                 writeValue(cpfWriter, I2, dtcEnd.getTime().getMinute(),                    true);
  355.                 writeValue(cpfWriter, I2, (int)  dtcEnd.getTime().getSecond(),             true);

  356.                 // write last keys
  357.                 writeValue(cpfWriter, I5, cpfHeader.getStep(),                             true);
  358.                 writeValue(cpfWriter, I1, cpfHeader.isCompatibleWithTIVs(),                true);
  359.                 writeValue(cpfWriter, I1, cpfHeader.getTargetClass(),                      true);
  360.                 writeValue(cpfWriter, I2, cpfHeader.getRefFrameId(),                       true);
  361.                 writeValue(cpfWriter, I1, cpfHeader.getRotationalAngleType(),              true);
  362.                 if (cpfHeader.getVersion() == 1) {
  363.                     writeValue(cpfWriter, I1, cpfHeader.isCenterOfMassCorrectionApplied(), false);
  364.                 } else {
  365.                     writeValue(cpfWriter, I1, cpfHeader.isCenterOfMassCorrectionApplied(), true);
  366.                     writeValue(cpfWriter, I2, cpfHeader.getTargetLocation(),               false);
  367.                 }

  368.             }

  369.         };

  370.         /** Identifier. */
  371.         private final String identifier;

  372.         /** Simple constructor.
  373.          * @param identifier regular expression for identifying line (i.e. first element)
  374.          */
  375.         HeaderLineWriter(final String identifier) {
  376.             this.identifier = identifier;
  377.         }

  378.         /** Write a line.
  379.          * @param cpfHeader container for header data
  380.          * @param cpfWriter writer
  381.          * @param timescale time scale for dates
  382.          * @throws IOException
  383.          *             if any buffer writing operations fail or if the underlying
  384.          *             format doesn't support a configuration in the file
  385.          */
  386.         public abstract void write(CPFHeader cpfHeader, Appendable cpfWriter, TimeScale timescale)  throws IOException;

  387.         /**
  388.          * Get the regular expression for identifying line.
  389.          * @return the regular expression for identifying line
  390.          */
  391.         public String getIdentifier() {
  392.             return identifier;
  393.         }

  394.     }

  395. }