GLONASSDate.java

/* Copyright 2002-2022 CS GROUP
 * 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.time;

import java.io.Serializable;

import org.hipparchus.util.FastMath;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.propagation.analytical.gnss.data.GNSSConstants;
import org.orekit.utils.Constants;

/**
 * Container for date in GLONASS form.
 * @author Bryan Cazabonne
 * @see AbsoluteDate
 * @see "GLONASS Interface Control Document v1.0, 2016"
 * @since 10.0
 */
public class GLONASSDate implements Serializable, TimeStamped {

    /** Serializable UID. */
    private static final long serialVersionUID = 20190131L;

    /** Constant for date computation. */
    private static final int C1 = 44195;

    /** Constant for date computation. */
    private static final int C2 = 45290;

    /** The number of the current day in a four year interval N<sub>a</sub>. */
    private final int na;

    /** The number of the current four year interval N<sub>4</sub>. */
    private final int n4;

    /** Number of seconds since N<sub>a</sub>. */
    private final double secInNa;

    /** Current Julian date JD0. */
    private double jd0;

    /** Greenwich Mean Sidereal Time (rad). */
    private double gmst;

    /** Corresponding date. */
    private final transient AbsoluteDate date;

    /** Build an instance corresponding to a GLONASS date.
     *
     * <p>This method uses the {@link DataContext#getDefault() default data context}.
     *
     * @param na the number of the current day in a four year interval
     * @param n4 the number of the current four year interval
     * @param secInNa the number of seconds since na start
     * @see #GLONASSDate(int, int, double, TimeScale)
     */
    @DefaultDataContext
    public GLONASSDate(final int na, final int n4, final double secInNa) {
        this(na, n4, secInNa, DataContext.getDefault().getTimeScales().getGLONASS());
    }

    /**
     * Build an instance corresponding to a GLONASS date.
     *
     * @param na      the number of the current day in a four year interval
     * @param n4      the number of the current four year interval
     * @param secInNa the number of seconds since na start
     * @param glonass time scale.
     * @since 10.1
     */
    public GLONASSDate(final int na,
                       final int n4,
                       final double secInNa,
                       final TimeScale glonass) {
        this.na      = na;
        this.n4      = n4;
        this.secInNa = secInNa;
        // Compute JD0
        final int ratio = FastMath.round((float) (na - 3) / (25 + C1 + C2));
        this.jd0  = 1461 * (n4 - 1) + na + 2450082.5 - ratio;
        // GMST
        this.gmst = computeGMST();
        this.date = computeDate(glonass);
    }

    /** Build an instance from an absolute date.
     *
     * <p>This method uses the {@link DataContext#getDefault() default data context}.
     *
     * @param date absolute date to consider
     * @see #GLONASSDate(AbsoluteDate, TimeScale)
     */
    @DefaultDataContext
    public GLONASSDate(final AbsoluteDate date) {
        this(date, DataContext.getDefault().getTimeScales().getGLONASS());
    }

    /**
     * Build an instance from an absolute date.
     *
     * @param date    absolute date to consider
     * @param glonass time scale.
     * @since 10.1
     */
    public GLONASSDate(final AbsoluteDate date, final TimeScale glonass) {
        final DateTimeComponents dateTime = date.getComponents(glonass);
        // N4
        final int year = dateTime.getDate().getYear();
        this.n4 = ((int) (year - 1996) / 4) + 1;
        // Na
        final int start = 1996 + 4 * (n4 - 1);
        final double duration = date.durationFrom(new AbsoluteDate(start, 1, 1, glonass));
        this.na = (int) (duration / 86400) + 1;
        this.secInNa = dateTime.getTime().getSecondsInLocalDay();
        // Compute JD0
        final int ratio = FastMath.round((float) (na - 3) / (25 + C1 + C2));
        this.jd0 = 1461 * (n4 - 1) + na + 2450082.5 - ratio;
        // GMST
        this.gmst = computeGMST();
        this.date = date;
    }

    @Override
    public AbsoluteDate getDate() {
        return date;
    }

    /** Get the number of seconds since N<sub>a</sub> start.
     * @return number of seconds since N<sub>a</sub> start
     */
    public double getSecInDay() {
        return secInNa;
    }

    /** Get the number of the current day in a four year interval.
     * @return the number of the current day in a four year interval
     */
    public int getDayNumber() {
        return na;
    }

    /** Get the number of the current four year interval.
     * @return the number of the current four year interval
     */
    public int getIntervalNumber() {
        return n4;
    }

    /** Get the current Julian date JD0.
     * @return the current date JD0
     */
    public double getJD0() {
        return jd0;
    }

    /** Get the Greenwich Mean Sidereal Time.
     * @return the Greenwich Mean Sidereal Time (rad)
     */
    public double getGMST() {
        return gmst;
    }

    /** Compute the Greenwich Mean Sidereal Time using the current Julian date JD0.
     * @return the Greenwich Mean Sidereal Time (rad)
     */
    private double computeGMST() {
        final double ref = 2451545.0;
        // Earth's rotation angle in radians
        final double era = 2. * GNSSConstants.GLONASS_PI *
                        (0.7790572732640 + 1.00273781191135448 * (jd0 - ref));
        // Time from Epoch 2000 (1st January, 00:00 UTC) till current Epoch in Julian centuries
        final double time = (jd0 - ref) / Constants.JULIAN_CENTURY;
        // Time to the power n
        final double time2 = time * time;
        final double time3 = time2 * time;
        final double time4 = time2 * time2;
        final double time5 = time2 * time3;
        // GMST computation
        final double gTime = era + 7.03270726e-8 + time * 2.23603658710194e-2 +
                        time2 * 6.7465784654e-6 - time3 * 2.1332e-12 - time4 * 1.452308e-10 - time5 * 1.784e-13;
        return gTime;
    }

    /** Compute the GLONASS date.
     * @return the date
     * @param glonass time scale.
     */
    private AbsoluteDate computeDate(final TimeScale glonass) {
        // Compute the number of Julian day for the current date
        final double jdn = jd0 + 0.5;
        // Coefficients
        final int a = (int) (jdn + 32044);
        final int b = (4 * a + 3) / 146097;
        final int c = a - (146097 * b) / 4;
        final int d = (4 * c + 3) / 1461;
        final int e = c - (1461 * d) / 4;
        final int m = (5 * e + 2) / 153;
        // Year, month and day
        final int day   = e - (153 * m + 2) / 5 + 1;
        final int month = m + 3 - 12 * (m / 10);
        final int year  = 100 * b + d - 4800 + m / 10;
        return new AbsoluteDate(new DateComponents(year, month, day),
                                new TimeComponents(secInNa),
                                glonass);
    }

    /** Replace the instance with a data transfer object for serialization.
     * @return data transfer object that will be serialized
     */
    @DefaultDataContext
    private Object writeReplace() {
        return new DataTransferObject(na, n4, secInNa);
    }

    /** Internal class used only for serialization. */
    @DefaultDataContext
    private static class DataTransferObject implements Serializable {

        /** Serializable UID. */
        private static final long serialVersionUID = 20190131L;

        /** The number of the current day in a four year interval N<sub>a</sub>. */
        private final int na;

        /** The number of the current four year interval N<sub>4</sub>. */
        private final int n4;

        /** Number of seconds since N<sub>a</sub>. */
        private final double secInNa;

        /** Simple constructor.
         * @param na the number of the current day in a four year interval
         * @param n4 the number of the current four year interval
         * @param secInNa the number of seconds since na start
         */
        DataTransferObject(final int na, final int n4, final double secInNa) {
            this.na      = na;
            this.n4      = n4;
            this.secInNa = secInNa;
        }

        /** Replace the deserialized data transfer object with a {@link GPSDate}.
         * @return replacement {@link GPSDate}
         */
        private Object readResolve() {
            return new GLONASSDate(na, n4, secInNa);
        }

    }

}