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.TimeStampedPVCoordinates;

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

  56.     /** Underlying writer. */
  57.     private final OcmWriter writer;

  58.     /** Header. */
  59.     private final OdmHeader header;

  60.     /** File metadata. */
  61.     private final OcmMetadata metadata;

  62.     /** Current trajectory metadata. */
  63.     private final TrajectoryStateHistoryMetadata trajectoryMetadata;

  64.     /** File format to use. */
  65.     private final FileFormat fileFormat;

  66.     /** Output name for error messages. */
  67.     private final String outputName;

  68.     /** Column number for aligning units. */
  69.     private final int unitsColumn;

  70.     /** Maximum offset for relative dates. */
  71.     private final double maxRelativeOffset;

  72.     /** Central body.
  73.      * @since 12.0
  74.      */
  75.     private final OneAxisEllipsoid body;

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

  119.     /** {@inheritDoc}
  120.      * <p>
  121.      * As {@code EphemerisFile.SatelliteEphemeris} does not have all the entries
  122.      * from {@link OcmMetadata}, the only values that will be extracted from the
  123.      * {@code ephemerisFile} will be the start time, stop time, reference frame, interpolation
  124.      * method and interpolation degree. The missing values (like object name, local spacecraft
  125.      * body frame...) will be inherited from the template  metadata set at writer
  126.      * {@link #EphemerisOcmWriter(OcmWriter, OdmHeader, OcmMetadata, TrajectoryStateHistoryMetadata,
  127.      * FileFormat, String, double, int) construction}.
  128.      * </p>
  129.      */
  130.     @Override
  131.     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
  132.         void write(final Appendable appendable, final EphemerisFile<C, S> ephemerisFile)
  133.         throws IOException {

  134.         if (appendable == null) {
  135.             throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
  136.         }

  137.         if (ephemerisFile == null) {
  138.             return;
  139.         }

  140.         final String name;
  141.         if (metadata.getObjectName() != null) {
  142.             name = metadata.getObjectName();
  143.         } else if (metadata.getInternationalDesignator() != null) {
  144.             name = metadata.getInternationalDesignator();
  145.         } else if (metadata.getObjectDesignator() != null) {
  146.             name = metadata.getObjectDesignator();
  147.         } else {
  148.             name = Ocm.UNKNOWN_OBJECT;
  149.         }
  150.         final SatelliteEphemeris<C, S> satEphem = ephemerisFile.getSatellites().get(name);
  151.         if (satEphem == null) {
  152.             throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
  153.                                                      name, "ephemerisFile");
  154.         }

  155.         // Get trajectory blocks to output.
  156.         final List<S> blocks = satEphem.getSegments();
  157.         if (blocks.isEmpty()) {
  158.             // No data -> No output
  159.             return;
  160.         }

  161.         try (Generator generator = fileFormat == FileFormat.KVN ?
  162.                                    new KvnGenerator(appendable, OcmWriter.KVN_PADDING_WIDTH, outputName,
  163.                                                     maxRelativeOffset, unitsColumn) :
  164.                                    new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName,
  165.                                                     maxRelativeOffset, unitsColumn > 0, null)) {

  166.             writer.writeHeader(generator, header);

  167.             if (generator.getFormat() == FileFormat.XML) {
  168.                 generator.enterSection(XmlStructureKey.segment.name());
  169.             }

  170.             // write single segment metadata
  171.             metadata.setStartTime(blocks.get(0).getStart());
  172.             metadata.setStopTime(blocks.get(blocks.size() - 1).getStop());
  173.             new OcmMetadataWriter(metadata, writer.getTimeConverter()).write(generator);

  174.             if (generator.getFormat() == FileFormat.XML) {
  175.                 generator.enterSection(XmlStructureKey.data.name());
  176.             }

  177.             // Loop on trajectory blocks
  178.             double lastZ = Double.NaN;
  179.             for (final S block : blocks) {

  180.                 // prepare metadata
  181.                 trajectoryMetadata.setTrajNextID(TrajectoryStateHistoryMetadata.incrementTrajID(trajectoryMetadata.getTrajID()));
  182.                 trajectoryMetadata.setUseableStartTime(block.getStart());
  183.                 trajectoryMetadata.setUseableStopTime(block.getStop());
  184.                 trajectoryMetadata.setInterpolationDegree(block.getInterpolationSamples() - 1);

  185.                 // prepare data
  186.                 final OrbitElementsType type      = trajectoryMetadata.getTrajType();
  187.                 final Frame             frame     = trajectoryMetadata.getTrajReferenceFrame().asFrame();
  188.                 int                     crossings = 0;
  189.                 final List<TrajectoryState> states = new ArrayList<>(block.getCoordinates().size());
  190.                 for (final C pv : block.getCoordinates()) {
  191.                     if (lastZ < 0.0 && pv.getPosition().getZ() >= 0.0) {
  192.                         // we crossed ascending node
  193.                         ++crossings;
  194.                     }
  195.                     lastZ = pv.getPosition().getZ();
  196.                     states.add(new TrajectoryState(type, pv.getDate(), type.toRawElements(pv, frame, body, block.getMu())));
  197.                 }
  198.                 final TrajectoryStateHistory history = new TrajectoryStateHistory(trajectoryMetadata, states,
  199.                                                                                   body, block.getMu());

  200.                 // write trajectory block
  201.                 final TrajectoryStateHistoryWriter trajectoryWriter =
  202.                                 new TrajectoryStateHistoryWriter(history, writer.getTimeConverter());
  203.                 trajectoryWriter.write(generator);

  204.                 // update the trajectory IDs
  205.                 trajectoryMetadata.setTrajPrevID(trajectoryMetadata.getTrajID());
  206.                 trajectoryMetadata.setTrajID(trajectoryMetadata.getTrajNextID());

  207.                 if (trajectoryMetadata.getOrbRevNum() >= 0) {
  208.                     // update the orbits revolution number
  209.                     trajectoryMetadata.setOrbRevNum(trajectoryMetadata.getOrbRevNum() + crossings);
  210.                 }

  211.             }

  212.             if (generator.getFormat() == FileFormat.XML) {
  213.                 generator.exitSection(); // exit data
  214.                 generator.exitSection(); // exit segment
  215.             }

  216.             writer.writeFooter(generator);

  217.         }

  218.     }

  219. }