EphemerisOcmWriter.java

  1. /* Copyright 2016 Applied Defense Solutions (ADS)
  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.  * ADS 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.odm.ocm;

  18. import java.io.IOException;
  19. import java.util.ArrayList;
  20. import java.util.List;

  21. import org.orekit.bodies.OneAxisEllipsoid;
  22. import org.orekit.errors.OrekitIllegalArgumentException;
  23. import org.orekit.errors.OrekitMessages;
  24. import org.orekit.files.ccsds.ndm.odm.OdmHeader;
  25. import org.orekit.files.ccsds.section.XmlStructureKey;
  26. import org.orekit.files.ccsds.utils.FileFormat;
  27. import org.orekit.files.ccsds.utils.generation.Generator;
  28. import org.orekit.files.ccsds.utils.generation.KvnGenerator;
  29. import org.orekit.files.ccsds.utils.generation.XmlGenerator;
  30. import org.orekit.files.general.EphemerisFile;
  31. import org.orekit.files.general.EphemerisFile.SatelliteEphemeris;
  32. import org.orekit.files.general.EphemerisFileWriter;
  33. import org.orekit.frames.Frame;
  34. import org.orekit.utils.AccurateFormatter;
  35. import org.orekit.utils.Formatter;
  36. import org.orekit.utils.TimeStampedPVCoordinates;

  37. /** An {@link EphemerisFileWriter} generating {@link Ocm OCM} files.
  38.  * <p>
  39.  * This writer is intended to write only trajectory state history blocks.
  40.  * It does not writes physical properties, covariance data, maneuver data,
  41.  * perturbations parameters, orbit determination or user-defined parameters.
  42.  * If these blocks are needed, then {@link OcmWriter OcmWriter} must be
  43.  * used as it handles all OCM data blocks.
  44.  * </p>
  45.  * <p>
  46.  * The trajectory blocks metadata identifiers ({@code TRAJ_ID},
  47.  * {@code TRAJ_PREV_ID}, {@code TRAJ_NEXT_ID}) are updated automatically
  48.  * using {@link TrajectoryStateHistoryMetadata#incrementTrajID(String)},
  49.  * so users should generally only set {@link TrajectoryStateHistoryMetadata#setTrajID(String)}
  50.  * in the template.
  51.  * </p>
  52.  * @author Luc Maisonobe
  53.  * @since 12.0
  54.  * @see OcmWriter
  55.  * @see StreamingOcmWriter
  56.  */
  57. public class EphemerisOcmWriter implements EphemerisFileWriter {

  58.     /** Underlying writer. */
  59.     private final OcmWriter writer;

  60.     /** Header. */
  61.     private final OdmHeader header;

  62.     /** File metadata. */
  63.     private final OcmMetadata metadata;

  64.     /** Current trajectory metadata. */
  65.     private final TrajectoryStateHistoryMetadata trajectoryMetadata;

  66.     /** File format to use. */
  67.     private final FileFormat fileFormat;

  68.     /** Output name for error messages. */
  69.     private final String outputName;

  70.     /** Column number for aligning units. */
  71.     private final int unitsColumn;

  72.     /** Maximum offset for relative dates. */
  73.     private final double maxRelativeOffset;

  74.     /** Used to format dates and doubles to string. */
  75.     private final Formatter formatter;

  76.     /** Central body.
  77.      * @since 12.0
  78.      */
  79.     private final OneAxisEllipsoid body;

  80.     /**
  81.      * Constructor used to create a new OCM writer configured with the necessary parameters
  82.      * to successfully fill in all required fields that aren't part of a standard object.
  83.      * <p>
  84.      * If the mandatory header entries are not present (or if header is null),
  85.      * built-in defaults will be used
  86.      * </p>
  87.      * <p>
  88.      * The writer is built from the complete header and partial metadata. The template
  89.      * metadata is used to initialize and independent local copy, that will be updated
  90.      * as new segments are written (with at least the segment start and stop will change,
  91.      * but some other parts may change too). The {@code template} argument itself is not
  92.      * changed.
  93.      * </p>
  94.      * @param writer underlying writer
  95.      * @param header file header (may be null)
  96.      * @param metadata  file metadata
  97.      * @param template  template for trajectory metadata
  98.      * @param fileFormat file format to use
  99.      * @param outputName output name for error messages
  100.      * @param maxRelativeOffset maximum offset in seconds to use relative dates
  101.      * @param formatter used to format date and double to string.
  102.      * (if a date is too far from reference, it will be displayed as calendar elements)
  103.      * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
  104.      */
  105.     public EphemerisOcmWriter(final OcmWriter writer,
  106.                               final OdmHeader header, final OcmMetadata metadata,
  107.                               final TrajectoryStateHistoryMetadata template,
  108.                               final FileFormat fileFormat, final String outputName,
  109.                               final double maxRelativeOffset, final int unitsColumn, final Formatter formatter) {
  110.         this.writer             = writer;
  111.         this.header             = header;
  112.         this.metadata           = metadata.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
  113.         this.trajectoryMetadata = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
  114.         this.fileFormat         = fileFormat;
  115.         this.outputName         = outputName;
  116.         this.maxRelativeOffset  = maxRelativeOffset;
  117.         this.unitsColumn        = unitsColumn;
  118.         this.body               = Double.isNaN(writer.getEquatorialRadius()) ?
  119.                                   null :
  120.                                   new OneAxisEllipsoid(writer.getEquatorialRadius(),
  121.                                                        writer.getFlattening(),
  122.                                                        template.getTrajReferenceFrame().asFrame());
  123.         this.formatter = formatter;
  124.     }

  125.     /**
  126.      * Constructor used to create a new OCM writer configured with the necessary parameters
  127.      * to successfully fill in all required fields that aren't part of a standard object.
  128.      * <p>
  129.      * If the mandatory header entries are not present (or if header is null),
  130.      * built-in defaults will be used
  131.      * </p>
  132.      * <p>
  133.      * The writer is built from the complete header and partial metadata. The template
  134.      * metadata is used to initialize and independent local copy, that will be updated
  135.      * as new segments are written (with at least the segment start and stop will change,
  136.      * but some other parts may change too). The {@code template} argument itself is not
  137.      * changed.
  138.      * </p>
  139.      * @param writer underlying writer
  140.      * @param header file header (may be null)
  141.      * @param metadata  file metadata
  142.      * @param template  template for trajectory metadata
  143.      * @param fileFormat file format to use
  144.      * @param outputName output name for error messages
  145.      * @param maxRelativeOffset maximum offset in seconds to use relative dates
  146.      * (if a date is too far from reference, it will be displayed as calendar elements)
  147.      * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
  148.      */
  149.     public EphemerisOcmWriter(final OcmWriter writer,
  150.                               final OdmHeader header, final OcmMetadata metadata,
  151.                               final TrajectoryStateHistoryMetadata template,
  152.                               final FileFormat fileFormat, final String outputName,
  153.                               final double maxRelativeOffset, final int unitsColumn) {
  154.         this(writer, header, metadata, template, fileFormat, outputName, maxRelativeOffset, unitsColumn, new AccurateFormatter());
  155.     }

  156.     /** {@inheritDoc}
  157.      * <p>
  158.      * As {@code EphemerisFile.SatelliteEphemeris} does not have all the entries
  159.      * from {@link OcmMetadata}, the only values that will be extracted from the
  160.      * {@code ephemerisFile} will be the start time, stop time, reference frame, interpolation
  161.      * method and interpolation degree. The missing values (like object name, local spacecraft
  162.      * body frame...) will be inherited from the template  metadata set at writer
  163.      * {@link #EphemerisOcmWriter(OcmWriter, OdmHeader, OcmMetadata, TrajectoryStateHistoryMetadata,
  164.      * FileFormat, String, double, int) construction}.
  165.      * </p>
  166.      */
  167.     @Override
  168.     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
  169.         void write(final Appendable appendable, final EphemerisFile<C, S> ephemerisFile)
  170.         throws IOException {

  171.         if (appendable == null) {
  172.             throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
  173.         }

  174.         if (ephemerisFile == null) {
  175.             return;
  176.         }

  177.         final String name;
  178.         if (metadata.getObjectName() != null) {
  179.             name = metadata.getObjectName();
  180.         } else if (metadata.getInternationalDesignator() != null) {
  181.             name = metadata.getInternationalDesignator();
  182.         } else if (metadata.getObjectDesignator() != null) {
  183.             name = metadata.getObjectDesignator();
  184.         } else {
  185.             name = Ocm.UNKNOWN_OBJECT;
  186.         }
  187.         final SatelliteEphemeris<C, S> satEphem = ephemerisFile.getSatellites().get(name);
  188.         if (satEphem == null) {
  189.             throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
  190.                                                      name, "ephemerisFile");
  191.         }

  192.         // Get trajectory blocks to output.
  193.         final List<S> blocks = satEphem.getSegments();
  194.         if (blocks.isEmpty()) {
  195.             // No data -> No output
  196.             return;
  197.         }

  198.         try (Generator generator = fileFormat == FileFormat.KVN ?
  199.                                    new KvnGenerator(appendable, OcmWriter.KVN_PADDING_WIDTH, outputName,
  200.                                                     maxRelativeOffset, unitsColumn, formatter) :
  201.                                    new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName,
  202.                                                     maxRelativeOffset, unitsColumn > 0, null, formatter)) {

  203.             writer.writeHeader(generator, header);

  204.             if (generator.getFormat() == FileFormat.XML) {
  205.                 generator.enterSection(XmlStructureKey.segment.name());
  206.             }

  207.             // write single segment metadata
  208.             metadata.setStartTime(blocks.get(0).getStart());
  209.             metadata.setStopTime(blocks.get(blocks.size() - 1).getStop());
  210.             new OcmMetadataWriter(metadata, writer.getTimeConverter()).write(generator);

  211.             if (generator.getFormat() == FileFormat.XML) {
  212.                 generator.enterSection(XmlStructureKey.data.name());
  213.             }

  214.             // Loop on trajectory blocks
  215.             double lastZ = Double.NaN;
  216.             for (final S block : blocks) {

  217.                 // prepare metadata
  218.                 trajectoryMetadata.setTrajNextID(TrajectoryStateHistoryMetadata.incrementTrajID(trajectoryMetadata.getTrajID()));
  219.                 trajectoryMetadata.setUseableStartTime(block.getStart());
  220.                 trajectoryMetadata.setUseableStopTime(block.getStop());
  221.                 trajectoryMetadata.setInterpolationDegree(block.getInterpolationSamples() - 1);

  222.                 // prepare data
  223.                 final OrbitElementsType type      = trajectoryMetadata.getTrajType();
  224.                 final Frame             frame     = trajectoryMetadata.getTrajReferenceFrame().asFrame();
  225.                 int                     crossings = 0;
  226.                 final List<TrajectoryState> states = new ArrayList<>(block.getCoordinates().size());
  227.                 for (final C pv : block.getCoordinates()) {
  228.                     if (lastZ < 0.0 && pv.getPosition().getZ() >= 0.0) {
  229.                         // we crossed ascending node
  230.                         ++crossings;
  231.                     }
  232.                     lastZ = pv.getPosition().getZ();
  233.                     states.add(new TrajectoryState(type, pv.getDate(), type.toRawElements(pv, frame, body, block.getMu())));
  234.                 }
  235.                 final TrajectoryStateHistory history = new TrajectoryStateHistory(trajectoryMetadata, states,
  236.                                                                                   body, block.getMu());

  237.                 // write trajectory block
  238.                 final TrajectoryStateHistoryWriter trajectoryWriter =
  239.                                 new TrajectoryStateHistoryWriter(history, writer.getTimeConverter());
  240.                 trajectoryWriter.write(generator);

  241.                 // update the trajectory IDs
  242.                 trajectoryMetadata.setTrajPrevID(trajectoryMetadata.getTrajID());
  243.                 trajectoryMetadata.setTrajID(trajectoryMetadata.getTrajNextID());

  244.                 if (trajectoryMetadata.getOrbRevNum() >= 0) {
  245.                     // update the orbits revolution number
  246.                     trajectoryMetadata.setOrbRevNum(trajectoryMetadata.getOrbRevNum() + crossings);
  247.                 }

  248.             }

  249.             if (generator.getFormat() == FileFormat.XML) {
  250.                 generator.exitSection(); // exit data
  251.                 generator.exitSection(); // exit segment
  252.             }

  253.             writer.writeFooter(generator);

  254.         }

  255.     }

  256. }