SP3File.java

/* Copyright 2002-2012 Space Applications Services
 * 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.
 * CS 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.sp3;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.files.general.EphemerisFile;
import org.orekit.frames.Frame;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.CartesianDerivativesFilter;
import org.orekit.utils.TimeStampedPVCoordinates;

/**
 * Represents a parsed SP3 orbit file.
 * @author Thomas Neidhart
 * @author Evan Ward
 */
public class SP3File implements EphemerisFile {
    /** String representation of the center of ephemeris coordinate system. **/
    public static final String SP3_FRAME_CENTER_STRING = "EARTH";

    /** File type indicator. */
    public enum SP3FileType {
        /** GPS only file. */
        GPS,
        /** Mixed file. */
        MIXED,
        /** GLONASS only file. */
        GLONASS,
        /** LEO only file. */
        LEO,
        /** Galileo only file. */
        GALILEO,
        /** COMPASS only file. */
        COMPASS,
        /** QZSS only file. */
        QZSS,
        /** undefined file format. */
        UNDEFINED
    }

    /** Orbit type indicator. */
    public enum SP3OrbitType {
        /** fitted. */
        FIT,
        /** extrapolated or predicted. */
        EXT,
        /** broadcast. */
        BCT,
        /** fitted after applying a Helmert transformation. */
        HLM,
        /** other type, defined by SP3 file producing agency.
         * @since 9.3
         */
        OTHER;

        /** Parse a string to get the type.
         * @param s string to parse
         * @return the type corresponding to the string
         */
        public static SP3OrbitType parseType(final String s) {
            final String normalizedString = s.trim().toUpperCase(Locale.US);
            if ("EST".equals(normalizedString)) {
                return FIT;
            } else if ("BHN".equals(normalizedString)) {
                // ESOC navigation team uses BHN for files produced
                // by their main parameter estimation program Bahn
                return FIT;
            } else if ("PRO".equals(normalizedString)) {
                // ESOC navigation team uses PRO for files produced
                // by their orbit propagation program Propag
                return EXT;
            } else {
                try {
                    return valueOf(normalizedString);
                } catch (IllegalArgumentException iae) {
                    return OTHER;
                }
            }
        }

    }

    /** Time system used throughout this SP3 file. */
    public enum TimeSystem {
        /** Global Positioning System. */
        GPS,
        /** GLONASS. */
        GLO,
        /** GALILEO. */
        GAL,
        /** International Atomic Time. */
        TAI,
        /** Coordinated Universal Time. */
        UTC,
        /** Quasi-Zenith System. */
        QZS
    }

    /** File type. */
    private SP3FileType type;

    /** Time system. */
    private TimeSystem timeSystem;

    /** Epoch of the file. */
    private AbsoluteDate epoch;

    /** GPS week. */
    private int gpsWeek;

    /** Seconds of the current GPS week. */
    private double secondsOfWeek;

    /** Julian day. */
    private int julianDay;

    /** Day fraction. */
    private double dayFraction;

    /** Time-interval between epochs. */
    private double epochInterval;

    /** Number of epochs. */
    private int numberOfEpochs;

    /** Coordinate system. */
    private String coordinateSystem;

    /** Data used indicator. */
    private String dataUsed;

    /** Orbit type. */
    private SP3OrbitType orbitType;

    /** Key for orbit type.
     * @since 9.3
     */
    private String orbitTypeKey;

    /** Agency providing the file. */
    private String agency;

    /** Indicates if data contains velocity or not. */
    private CartesianDerivativesFilter filter;

    /** Time scale of dates in the ephemeris file. */
    private TimeScale timeScale;

    /** Time scale, as specified in the file. */
    private String timeScaleString;

    /** Standard gravitational parameter in m^3 / s^2. */
    private final double mu;

    /** Number of samples to use when interpolating. */
    private final int interpolationSamples;

    /** Maps {@link #coordinateSystem} to a {@link Frame}. */
    private final Function<? super String, ? extends Frame> frameBuilder;

    /** A map containing satellite information. */
    private Map<String, SP3Ephemeris> satellites;

    /**
     * Create a new SP3 file object.
     *
     * @param mu                   is the standard gravitational parameter in m^3 / s^2.
     * @param interpolationSamples number of samples to use in interpolation.
     * @param frameBuilder         for constructing a reference frame from the identifier
     */
    SP3File(final double mu,
            final int interpolationSamples,
            final Function<? super String, ? extends Frame> frameBuilder) {
        this.mu = mu;
        this.interpolationSamples = interpolationSamples;
        this.frameBuilder = frameBuilder;
        // must be linked has map to preserve order of satellites in the file.
        satellites = new LinkedHashMap<>();
    }

    /**
     * Set the derivatives filter.
     *
     * @param filter that indicates which derivatives of position are available.
     */
    void setFilter(final CartesianDerivativesFilter filter) {
        this.filter = filter;
    }

    /**
     * Set the time scale.
     *
     * @param timeScale use to parse dates in this file.
     */
    void setTimeScale(final TimeScale timeScale) {
        this.timeScale = timeScale;
    }

    /**
     * Set the string used to define the time scale.
     *
     * @param timeScaleString the time scale identifier used in the file.
     */
    void setTimeScaleString(final String timeScaleString) {
        this.timeScaleString = timeScaleString;
    }

    /** Returns the {@link SP3FileType} associated with this SP3 file.
     * @return the file type for this SP3 file
     */
    public SP3FileType getType() {
        return type;
    }

    /** Set the file type for this SP3 file.
     * @param fileType the file type to be set
     */
    void setType(final SP3FileType fileType) {
        this.type = fileType;
    }

    /** Returns the {@link TimeSystem} used to time-stamp position entries.
     * @return the {@link TimeSystem} of the orbit file
     */
    public TimeSystem getTimeSystem() {
        return timeSystem;
    }

    /** Set the time system used in this SP3 file.
     * @param system the time system to be set
     */
    void setTimeSystem(final TimeSystem system) {
        this.timeSystem = system;
    }

    /** Returns the data used indicator from the SP3 file.
     * @return the data used indicator (unparsed)
     */
    public String getDataUsed() {
        return dataUsed;
    }

    /** Set the data used indicator for this SP3 file.
     * @param data the data used indicator to be set
     */
    void setDataUsed(final String data) {
        this.dataUsed = data;
    }

    /** Returns the start epoch of the orbit file.
     * @return the start epoch
     */
    public AbsoluteDate getEpoch() {
        return epoch;
    }

    /** Set the epoch of the SP3 file.
     * @param time the epoch to be set
     */
    void setEpoch(final AbsoluteDate time) {
        this.epoch = time;
    }

    /** Returns the GPS week as contained in the SP3 file.
     * @return the GPS week of the SP3 file
     */
    public int getGpsWeek() {
        return gpsWeek;
    }

    /** Set the GPS week of the SP3 file.
     * @param week the GPS week to be set
     */
    void setGpsWeek(final int week) {
        this.gpsWeek = week;
    }

    /** Returns the seconds of the GPS week as contained in the SP3 file.
     * @return the seconds of the GPS week
     */
    public double getSecondsOfWeek() {
        return secondsOfWeek;
    }

    /** Set the seconds of the GPS week for this SP3 file.
     * @param seconds the seconds to be set
     */
    void setSecondsOfWeek(final double seconds) {
        this.secondsOfWeek = seconds;
    }

    /** Returns the julian day for this SP3 file.
     * @return the julian day
     */
    public int getJulianDay() {
        return julianDay;
    }

    /** Set the julian day for this SP3 file.
     * @param day the julian day to be set
     */
    void setJulianDay(final int day) {
        this.julianDay = day;
    }

    /** Returns the day fraction for this SP3 file.
     * @return the day fraction
     */
    public double getDayFraction() {
        return dayFraction;
    }

    /** Set the day fraction for this SP3 file.
     * @param fraction the day fraction to be set
     */
    void setDayFraction(final double fraction) {
        this.dayFraction = fraction;
    }

    /** Returns the time interval between epochs (in seconds).
     * @return the time interval between epochs
     */
    public double getEpochInterval() {
        return epochInterval;
    }

    /** Set the epoch interval for this SP3 file.
     * @param interval the interval between orbit entries
     */
    void setEpochInterval(final double interval) {
        this.epochInterval = interval;
    }

    /** Returns the number of epochs contained in this orbit file.
     * @return the number of epochs
     */
    public int getNumberOfEpochs() {
        return numberOfEpochs;
    }

    /** Set the number of epochs as contained in the SP3 file.
     * @param epochCount the number of epochs to be set
     */
    void setNumberOfEpochs(final int epochCount) {
        this.numberOfEpochs = epochCount;
    }

    /** Returns the coordinate system of the entries in this orbit file.
     * @return the coordinate system
     */
    public String getCoordinateSystem() {
        return coordinateSystem;
    }

    /** Set the coordinate system used for the orbit entries.
     * @param system the coordinate system to be set
     */
    void setCoordinateSystem(final String system) {
        this.coordinateSystem = system;
    }

    /** Returns the {@link SP3OrbitType} for this SP3 file.
     * @return the orbit type
     */
    public SP3OrbitType getOrbitType() {
        return orbitType;
    }

    /** Returns the orbit type key for this SP3 file.
     * @return the orbit type key
     * @since 9.3
     */
    public String getOrbitTypeKey() {
        return orbitTypeKey;
    }

    /** Set the orbit type key for this SP3 file.
     * @param oTypeKey the orbit type key to be set
     * @since 9.3
     */
    void setOrbitTypeKey(final String oTypeKey) {
        this.orbitTypeKey = oTypeKey;
        this.orbitType    = SP3OrbitType.parseType(oTypeKey);
    }

    /** Returns the agency that prepared this SP3 file.
     * @return the agency
     */
    public String getAgency() {
        return agency;
    }

    /** Set the agency string for this SP3 file.
     * @param agencyStr the agency string to be set
     */
    void setAgency(final String agencyStr) {
        this.agency = agencyStr;
    }

    /** Add a new satellite with a given identifier to the list of
     * stored satellites.
     * @param satId the satellite identifier
     */
    public void addSatellite(final String satId) {
        // only add satellites which have not been added before
        satellites.putIfAbsent(satId, new SP3Ephemeris(satId));
    }

    @Override
    public Map<String, SP3Ephemeris> getSatellites() {
        return Collections.unmodifiableMap(satellites);
    }

    /** Get the number of satellites contained in this orbit file.
     * @return the number of satellites
     */
    public int getSatelliteCount() {
        return satellites.size();
    }

    /**
     * Set the formal accuracy for a satellite.
     *
     * @param index    is the index of the satellite.
     * @param accuracy of the satellite, in m.
     */
    void setAccuracy(final int index, final double accuracy) {
        int n = index;
        for (final SP3Ephemeris ephemeris : satellites.values()) {
            if (n == 0) {
                ephemeris.setAccuracy(accuracy);
                return;
            }
            n--;
        }
    }

    /**
     * Get the formal accuracy for a satellite.
     *
     * @param index    is the index of the satellite.
     * @return accuracy of the satellite, in m.
     */
    double getAccuracy(final int index) {
        int n = index;
        for (final SP3Ephemeris ephemeris : satellites.values()) {
            if (n == 0) {
                return ephemeris.getAccuracy();
            }
            n--;
        }
        return Double.NaN;
    }

    /** Tests whether a satellite with the given id is contained in this orbit
     * file.
     * @param satId the satellite id
     * @return {@code true} if the satellite is contained in the file,
     *         {@code false} otherwise
     */
    public boolean containsSatellite(final String satId) {
        return satellites.containsKey(satId);
    }

    /**
     * Adds a new P/V coordinate for a given satellite.
     *
     * @param satId the satellite identifier
     * @param coord the P/V coordinate of the satellite
     */
    void addSatelliteCoordinate(final String satId, final SP3Coordinate coord) {
        satellites.get(satId).coordinates.add(coord);
    }

    /** An ephemeris for a single satellite in a SP3 file. */
    public class SP3Ephemeris implements SatelliteEphemeris, EphemerisSegment {

        /** Satellite ID. */
        private final String id;
        /** Ephemeris Data. */
        private final List<SP3Coordinate> coordinates;
        /** Accuracy in m. */
        private double accuracy;

        /**
         * Create an ephemeris for a single satellite.
         *
         * @param id of the satellite.
         */
        SP3Ephemeris(final String id) {
            this.id = id;
            this.coordinates = new ArrayList<>();
        }

        @Override
        public String getId() {
            return this.id;
        }

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

        @Override
        public String getFrameCenterString() {
            return SP3_FRAME_CENTER_STRING;
        }

        @Override
        public String getFrameString() {
            return getCoordinateSystem();
        }

        @Override
        public Frame getFrame() {
            return frameBuilder.apply(getFrameString());
        }

        @Override
        public String getTimeScaleString() {
            return timeScaleString;
        }

        @Override
        public TimeScale getTimeScale() {
            return timeScale;
        }

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

        @Override
        public CartesianDerivativesFilter getAvailableDerivatives() {
            return filter;
        }

        @Override
        public List<SP3Coordinate> getCoordinates() {
            return Collections.unmodifiableList(this.coordinates);
        }

        /** Returns a list containing only {@code this}. */
        @Override
        public List<SP3Ephemeris> getSegments() {
            return Collections.singletonList(this);
        }

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

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

        @Override
        public BoundedPropagator getPropagator() {
            return EphemerisSegment.super.getPropagator();
        }

        /**
         * Set the accuracy for this satellite.
         *
         * @param accuracy in m.
         */
        void setAccuracy(final double accuracy) {
            this.accuracy = accuracy;
        }

        /**
         * Get the formal accuracy for this satellite.
         *
         * <p>The accuracy is limited by the SP3 standard to be a power of 2 in mm.
         * The value returned here is in meters.</p>
         *
         * @return magnitude of one standard deviation, in m.
         */
        public double getAccuracy() {
            return accuracy;
        }

    }

    /** A single record of position clock and possibly derivatives in an SP3 file. */
    public static class SP3Coordinate extends TimeStampedPVCoordinates {

        /** Serializable UID. */
        private static final long serialVersionUID = 20161116L;
        /** Clock correction in s. */
        private final double clock;
        /** Clock rate in s / s. */
        private final double clockRate;

        /**
         * Create a coordinate with only position.
         *
         * @param date     of validity.
         * @param position of the satellite.
         * @param clock    correction in s.
         */
        SP3Coordinate(final AbsoluteDate date,
                      final Vector3D position,
                      final double clock) {
            this(date, position, Vector3D.ZERO, clock, 0);
        }

        /**
         * Create a coordinate with position and velocity.
         *
         * @param date      of validity.
         * @param position  of the satellite.
         * @param velocity  of the satellite.
         * @param clock     correction in s.
         * @param clockRate in s / s.
         */
        SP3Coordinate(final AbsoluteDate date,
                      final Vector3D position,
                      final Vector3D velocity,
                      final double clock,
                      final double clockRate) {
            super(date, position, velocity, Vector3D.ZERO);
            this.clock = clock;
            this.clockRate = clockRate;
        }

        /**
         * Returns the clock correction value.
         *
         * @return the clock correction in s.
         */
        public double getClockCorrection() {
            return clock;
        }

        /**
         * Returns the clock rate.
         *
         * @return the clock rate of change in s/s.
         */
        public double getClockRateChange() {
            return clockRate;
        }

    }

}