OrekitEphemerisFile.java

/* Copyright 2016 Applied Defense Solutions (ADS)
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * ADS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.files.general;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.orekit.annotation.DefaultDataContext;
import org.orekit.bodies.CelestialBody;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.propagation.SpacecraftState;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.CartesianDerivativesFilter;
import org.orekit.utils.TimeStampedPVCoordinates;

/**
 * A class for encapsulating Orekit propagators within an {@link EphemerisFile}
 * complaint object that makes for easy serialization to external ephemeris
 * formats like OEM.
 *
 * @author Hank Grabowski
 * @since 9.0
 *
 */
public class OrekitEphemerisFile
    implements EphemerisFile<TimeStampedPVCoordinates, OrekitEphemerisFile.OrekitEphemerisSegment> {

    /** Hashmap of satellite ephemeris. **/
    private final Map<String, OrekitSatelliteEphemeris> satellites;

    /**
     * Standard default constructor.
     */
    public OrekitEphemerisFile() {
        this.satellites = new ConcurrentHashMap<String, OrekitSatelliteEphemeris>();
    }

    /** {@inheritDoc} */
    @Override
    public Map<String, OrekitSatelliteEphemeris> getSatellites() {
        return Collections.unmodifiableMap(satellites);
    }

    /**
     * Adds a new satellite to this object.
     *
     * @param id
     *            ID to use for this satellite
     * @return the new satellite object
     */
    public OrekitSatelliteEphemeris addSatellite(final String id) {
        final OrekitSatelliteEphemeris newSat = new OrekitSatelliteEphemeris(id);
        this.satellites.put(id, newSat);
        return newSat;
    }

    /**
     * Inner class of {@link OrekitEphemerisFile} that defines the
     * {@link OrekitSatelliteEphemeris} corresponding object for this ephemeris type.
     */
    public static class OrekitSatelliteEphemeris
        implements EphemerisFile.SatelliteEphemeris<TimeStampedPVCoordinates, OrekitEphemerisSegment> {

        /**
         * Defines the default interpolation sample size if it is not specified
         * on a segment.
         **/
        public static final int DEFAULT_INTERPOLATION_SIZE = 2;

        /** ID of the space object encapsulated here. **/
        private final String id;

        /** Earliest date of this file. **/
        private AbsoluteDate startDate;

        /** Latest date of this file. **/
        private AbsoluteDate stopDate;

        /** List of segments in the file. **/
        private final List<OrekitEphemerisSegment> segments;

        /**
         * Standard constructor for building the satellite Ephemeris object.
         *
         * @param id
         *            the ID of the space object for this data
         */
        public OrekitSatelliteEphemeris(final String id) {
            this.id = id;
            this.segments = new ArrayList<>();
        }

        /** {@inheritDoc} */
        @Override
        public String getId() {
            return id;
        }

        /** {@inheritDoc} */
        @Override
        public double getMu() {
            if (this.segments.size() == 0) {
                return 0;
            } else {
                return this.segments.get(0).getMu();
            }
        }

        /** {@inheritDoc} */
        @Override
        public List<OrekitEphemerisSegment> getSegments() {
            return Collections.unmodifiableList(this.segments);
        }

        /** {@inheritDoc} */
        @Override
        public AbsoluteDate getStart() {
            return this.startDate;
        }

        /** {@inheritDoc} */
        @Override
        public AbsoluteDate getStop() {
            return this.stopDate;
        }

        /**
         * Injects pre-computed satellite states into this ephemeris file
         * object, returning the generated {@link OrekitEphemerisSegment} that
         * has been stored internally. Defaults the celestial body to earth and
         * the interpolation size to the default.
         *
         * <p>This method uses the {@link DataContext#getDefault() default data context}.
         *
         * @param states
         *            a list of {@link SpacecraftState} that will comprise this
         *            new unit.
         * @return the generated {@link OrekitEphemerisSegment}
         * @see #addNewSegment(List, CelestialBody, int, TimeScale)
         */
        @DefaultDataContext
        public OrekitEphemerisSegment addNewSegment(final List<SpacecraftState> states) {
            return this.addNewSegment(states, DEFAULT_INTERPOLATION_SIZE);
        }

        /**
         * Injects pre-computed satellite states into this ephemeris file
         * object, returning the generated {@link OrekitEphemerisSegment} that
         * has been stored internally. Defaults the Celestial Body to be Earths
         *
         * <p>This method uses the {@link DataContext#getDefault() default data context}.
         *
         * @param states
         *            a list of {@link SpacecraftState} that will comprise this
         *            new unit.
         * @param interpolationSampleSize
         *            the number of interpolation samples that should be used
         *            when processed by another system
         * @return the generated {@link OrekitEphemerisSegment}
         * @see #addNewSegment(List, CelestialBody, int, TimeScale)
         */
        @DefaultDataContext
        public OrekitEphemerisSegment addNewSegment(final List<SpacecraftState> states,
                final int interpolationSampleSize) {
            return this.addNewSegment(
                    states,
                    DataContext.getDefault().getCelestialBodies().getEarth(),
                    interpolationSampleSize);
        }

        /**
         * Injects pre-computed satellite states into this ephemeris file
         * object, returning the generated {@link OrekitEphemerisSegment} that
         * has been stored internally.
         *
         * <p>This method uses the {@link DataContext#getDefault() default data context}.
         *
         * @param states
         *            a list of {@link SpacecraftState} that will comprise this
         *            new unit.
         * @param body
         *            the celestial body the state's frames are with respect to
         * @param interpolationSampleSize
         *            the number of interpolation samples that should be used
         *            when processed by another system
         * @return the generated {@link OrekitEphemerisSegment}
         * @see #addNewSegment(List, CelestialBody, int, TimeScale)
         */
        @DefaultDataContext
        public OrekitEphemerisSegment addNewSegment(final List<SpacecraftState> states, final CelestialBody body,
                                                    final int interpolationSampleSize) {
            return addNewSegment(states, body, interpolationSampleSize,
                    DataContext.getDefault().getTimeScales().getUTC());
        }

        /**
         * Injects pre-computed satellite states into this ephemeris file
         * object, returning the generated {@link OrekitEphemerisSegment} that
         * has been stored internally.
         *
         * @param states
         *            a list of {@link SpacecraftState} that will comprise this
         *            new unit.
         * @param body
         *            the celestial body from which the frames are defined
         * @param interpolationSampleSize
         *            the number of interpolation samples that should be used
         *            when processed by another system
         * @param timeScale
         *            the time scale used in the new segment.
         * @return the generated {@link OrekitEphemerisSegment}
         * @since 10.1
         */
        public OrekitEphemerisSegment addNewSegment(final List<SpacecraftState> states,
                                                    final CelestialBody body,
                                                    final int interpolationSampleSize,
                                                    final TimeScale timeScale) {
            final int minimumSampleSize = 2;
            if (states == null || states.size() == 0) {
                throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "states");
            }

            if (interpolationSampleSize < minimumSampleSize) {
                throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_DATA_FOR_INTERPOLATION,
                        interpolationSampleSize);
            }

            final AbsoluteDate start = states.get(0).getDate();
            final AbsoluteDate stop = states.get(states.size() - 1).getDate();

            if (this.startDate == null || start.compareTo(this.startDate) < 0) {
                this.startDate = start;
            }

            if (this.stopDate == null || stop.compareTo(this.stopDate) > 0) {
                this.stopDate = stop;
            }

            final List<TimeStampedPVCoordinates> coordinates = new ArrayList<>();
            for (SpacecraftState state : states) {
                coordinates.add(state.getPVCoordinates());
            }

            final Frame frame = states.get(0).getFrame();

            final OrekitEphemerisSegment newSeg =
                            new OrekitEphemerisSegment(coordinates, frame, body.getGM(), interpolationSampleSize);
            this.segments.add(newSeg);

            return newSeg;
        }
    }

    public static class OrekitEphemerisSegment
        implements EphemerisFile.EphemerisSegment<TimeStampedPVCoordinates> {

        /** Coordinates for this ephemeris segment. **/
        private final List<TimeStampedPVCoordinates> coordinates;

        /** The reference frame for this ephemeris segment. **/
        private final Frame frame;

        /** Standard gravitational parameter for the satellite. **/
        private final double mu;

        /** The number of interpolation samples. */
        private final int interpolationSamples;

        /**
         * constructor for OrekitEphemerisSegment.
         *
         * @param coordinates
         *            coordinates making up the ephemeris for this segment
         * @param frame
         *            the frame the coordinates are in
         * @param mu
         *            the gravitational constant used in force model evaluations
         * @param interpolationSamples
         *            the number of samples to use during interpolation
         */
        public OrekitEphemerisSegment(final List<TimeStampedPVCoordinates> coordinates, final Frame frame,
                                      final double mu, final int interpolationSamples) {
            this.coordinates          = coordinates;
            this.frame                = frame;
            this.mu                   = mu;
            this.interpolationSamples = interpolationSamples;
        }

        /** {@inheritDoc} */
        @Override
        public double getMu() {
            return mu;
        }

        /** {@inheritDoc} */
        @Override
        public Frame getFrame() {
            return frame;
        }

        /** {@inheritDoc} */
        @Override
        public Frame getInertialFrame() {
            return frame;
        }

        /** {@inheritDoc} */
        @Override
        public int getInterpolationSamples() {
            return interpolationSamples;
        }

        /** {@inheritDoc} */
        @Override
        public CartesianDerivativesFilter getAvailableDerivatives() {
            return CartesianDerivativesFilter.USE_PV;
        }

        /** {@inheritDoc} */
        @Override
        public List<TimeStampedPVCoordinates> getCoordinates() {
            return Collections.unmodifiableList(coordinates);
        }

        /** {@inheritDoc} */
        @Override
        public AbsoluteDate getStart() {
            return coordinates.get(0).getDate();
        }

        /** {@inheritDoc} */
        @Override
        public AbsoluteDate getStop() {
            return coordinates.get(coordinates.size() - 1).getDate();
        }

    }
}