GNSSDate.java

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

  18. import java.io.Serializable;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.concurrent.atomic.AtomicReference;

  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.annotation.DefaultDataContext;
  25. import org.orekit.data.DataContext;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitMessages;
  28. import org.orekit.frames.EOPEntry;
  29. import org.orekit.gnss.SatelliteSystem;
  30. import org.orekit.utils.Constants;
  31. import org.orekit.utils.IERSConventions;

  32. /** Container for date in GNSS form.
  33.  * <p> This class can be used to handle {@link SatelliteSystem#GPS GPS},
  34.  * {@link SatelliteSystem#GALILEO Galileo}, {@link SatelliteSystem#BEIDOU BeiDou}
  35.  * and {@link SatelliteSystem#QZSS QZSS} dates. </p>
  36.  * @author Luc Maisonobe (original code)
  37.  * @author Bryan Cazabonne (generalization to all GNSS constellations)
  38.  * @see AbsoluteDate
  39.  */
  40. public class GNSSDate implements Serializable, TimeStamped {

  41.     /** Serializable UID. */
  42.     private static final long serialVersionUID = 20221228L;

  43.     /** Duration of a week in days. */
  44.     private static final int WEEK_D = 7;

  45.     /** Duration of a week in seconds. */
  46.     private static final double WEEK_S = WEEK_D * Constants.JULIAN_DAY;

  47.     /** Reference date for ensuring continuity across GNSS week rollover.
  48.      * @since 9.3.1
  49.      */
  50.     private static AtomicReference<DateComponents> rolloverReference = new AtomicReference<DateComponents>(null);

  51.     /** Week number since the GNSS reference epoch. */
  52.     private final int weekNumber;

  53.     /** Number of seconds since week start. */
  54.     private final double secondsInWeek;

  55.     /** Satellite system to consider. */
  56.     private final SatelliteSystem system;

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

  59.     /** Build an instance corresponding to a GNSS date.
  60.      * <p>
  61.      * GNSS dates are provided as a week number starting at
  62.      * the GNSS reference epoch and as a number of seconds
  63.      * since week start.
  64.      * </p>
  65.      * <p>
  66.      * Many interfaces provide week number modulo the constellation week cycle. In order to cope with
  67.      * this, when the week number is smaller than the week cycle, this constructor assumes a modulo operation
  68.      * has been performed and it will fix the week number according to the reference date set up for
  69.      * handling rollover (see {@link #setRolloverReference(DateComponents) setRolloverReference(reference)}).
  70.      * If the week number is equal to the week cycle or larger, it will be used without any correction.
  71.      * </p>
  72.      *
  73.      * <p>This method uses the {@link DataContext#getDefault() default data context}.
  74.      *
  75.      * @param weekNumber week number
  76.      * @param secondsInWeek number of seconds since week start
  77.      * @param system satellite system to consider
  78.      * @see #GNSSDate(int, double, SatelliteSystem, TimeScales)
  79.      * @since 12.0
  80.      */
  81.     @DefaultDataContext
  82.     public GNSSDate(final int weekNumber, final double secondsInWeek, final SatelliteSystem system) {
  83.         this(weekNumber, secondsInWeek, system, DataContext.getDefault().getTimeScales());
  84.     }

  85.     /**
  86.      * Build an instance corresponding to a GNSS date.
  87.      * <p>
  88.      * GNSS dates are provided as a week number starting at the GNSS reference epoch and
  89.      * as a number of seconds since week start.
  90.      * </p>
  91.      * <p>
  92.      * Many interfaces provide week number modulo the constellation week cycle. In order
  93.      * to cope with this, when the week number is smaller than the week cycle, this
  94.      * constructor assumes a modulo operation has been performed and it will fix the week
  95.      * number according to the reference date set up for handling rollover (see {@link
  96.      * #setRolloverReference(DateComponents) setRolloverReference(reference)}). If the
  97.      * week number is equal to the week cycle or larger, it will be used without any
  98.      * correction.
  99.      * </p>
  100.      *
  101.      * @param weekNumber    week number
  102.      * @param secondsInWeek number of seconds since week start
  103.      * @param system        satellite system to consider
  104.      * @param timeScales    the set of time scales. Used to retrieve the appropriate time
  105.      *                      scale for the given {@code system}.
  106.      * @since 12.0
  107.      */
  108.     public GNSSDate(final int weekNumber, final double secondsInWeek,
  109.                     final SatelliteSystem system, final TimeScales timeScales) {

  110.         final int day = (int) FastMath.floor(secondsInWeek / Constants.JULIAN_DAY);
  111.         final double secondsInDay = secondsInWeek - day * Constants.JULIAN_DAY;

  112.         int w = weekNumber;
  113.         DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
  114.         final int cycleW = GNSSDateType.getRollOverWeek(system);
  115.         if (weekNumber < cycleW) {

  116.             DateComponents reference = rolloverReference.get();
  117.             if (reference == null) {
  118.                 // lazy setting of a default reference, using end of EOP entries
  119.                 final UT1Scale       ut1       = timeScales.getUT1(IERSConventions.IERS_2010, true);
  120.                 final List<EOPEntry> eop       = ut1.getEOPHistory().getEntries();
  121.                 final int            lastMJD   = eop.get(eop.size() - 1).getMjd();
  122.                 reference = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, lastMJD);
  123.                 rolloverReference.compareAndSet(null, reference);
  124.             }

  125.             // fix GNSS week rollover
  126.             final int cycleD = WEEK_D * cycleW;
  127.             while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
  128.                 dc = new DateComponents(dc, cycleD);
  129.                 w += cycleW;
  130.             }

  131.         }

  132.         this.weekNumber    = w;
  133.         this.secondsInWeek = secondsInWeek;
  134.         this.system        = system;

  135.         date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), getTimeScale(system, timeScales));

  136.     }

  137.     /**
  138.      * Build an instance corresponding to a GNSS date.
  139.      * <p>
  140.      * GNSS dates are provided as a week number starting at the GNSS reference epoch and
  141.      * as a number of seconds since week start.
  142.      * </p>
  143.      *
  144.      * @param weekNumber    week number
  145.      * @param secondsInWeek number of seconds since week start
  146.      * @param system        satellite system to consider
  147.      * @param reference     reference date for rollover, the generated date will be less
  148.      *                      than one half cycle from this date
  149.      * @param timeScales    the set of time scales. Used to retrieve the appropriate time
  150.      *                      scale for the given {@code system}.
  151.      * @since 12.0
  152.      */
  153.     public GNSSDate(final int weekNumber, final double secondsInWeek,
  154.                     final SatelliteSystem system, final DateComponents reference,
  155.                     final TimeScales timeScales) {

  156.         final int day = (int) FastMath.floor(secondsInWeek / Constants.JULIAN_DAY);
  157.         final double secondsInDay = secondsInWeek - day * Constants.JULIAN_DAY;

  158.         int w = weekNumber;
  159.         DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
  160.         final int cycleW = GNSSDateType.getRollOverWeek(system);
  161.         if (weekNumber < cycleW) {

  162.             // fix GNSS week rollover
  163.             final int cycleD = WEEK_D * cycleW;
  164.             while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
  165.                 dc = new DateComponents(dc, cycleD);
  166.                 w += cycleW;
  167.             }

  168.         }

  169.         this.weekNumber    = w;
  170.         this.secondsInWeek = secondsInWeek;
  171.         this.system        = system;

  172.         date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), getTimeScale(system, timeScales));

  173.     }

  174.     /** Build an instance from an absolute date.
  175.      *
  176.      * <p>This method uses the {@link DataContext#getDefault() default data context}.
  177.      *
  178.      * @param date absolute date to consider
  179.      * @param system satellite system to consider
  180.      * @see #GNSSDate(AbsoluteDate, SatelliteSystem, TimeScales)
  181.      */
  182.     @DefaultDataContext
  183.     public GNSSDate(final AbsoluteDate date, final SatelliteSystem system) {
  184.         this(date, system, DataContext.getDefault().getTimeScales());
  185.     }

  186.     /**
  187.      * Build an instance from an absolute date.
  188.      *
  189.      * @param date       absolute date to consider
  190.      * @param system     satellite system to consider
  191.      * @param timeScales the set of time scales. Used to retrieve the appropriate time
  192.      *                   scale for the given {@code system}.
  193.      * @since 10.1
  194.      */
  195.     public GNSSDate(final AbsoluteDate date,
  196.                     final SatelliteSystem system,
  197.                     final TimeScales timeScales) {

  198.         this.system = system;
  199.         final AbsoluteDate epoch = getWeekReferenceAbsoluteDate(system, timeScales);
  200.         this.weekNumber  = (int) FastMath.floor(date.durationFrom(epoch) / WEEK_S);
  201.         final AbsoluteDate weekStart = new AbsoluteDate(epoch, WEEK_S * weekNumber);
  202.         this.secondsInWeek = date.durationFrom(weekStart);
  203.         this.date          = date;

  204.     }

  205.     /** Set a reference date for ensuring continuity across GNSS week rollover.
  206.      * <p>
  207.      * Instance created using the {@link #GNSSDate(int, double, SatelliteSystem) GNSSDate(weekNumber, secondsInWeek, system)}
  208.      * constructor and with a week number between 0 and the constellation week cycle (cycleW) after this method has been called will
  209.      * fix the week number to ensure they correspond to dates between {@code reference - cycleW / 2 weeks}
  210.      * and {@code reference + cycleW / 2 weeks}.
  211.      * </p>
  212.      * <p>
  213.      * If this method is never called, a default reference date for rollover will be set using
  214.      * the date of the last known EOP entry retrieved from {@link UT1Scale#getEOPHistory() UT1}
  215.      * time scale.
  216.      * </p>
  217.      * @param reference reference date for GNSS week rollover
  218.      * @see #getRolloverReference()
  219.      * @see #GNSSDate(int, double, SatelliteSystem)
  220.      * @since 9.3.1
  221.      */
  222.     public static void setRolloverReference(final DateComponents reference) {
  223.         rolloverReference.set(reference);
  224.     }

  225.     /** Get the reference date ensuring continuity across GNSS week rollover.
  226.      * @return reference reference date for GNSS week rollover
  227.      * @see #setRolloverReference(DateComponents)
  228.      * @see #GNSSDate(int, double, SatelliteSystem)
  229.      * @since 9.3.1
  230.      */
  231.     public static DateComponents getRolloverReference() {
  232.         return rolloverReference.get();
  233.     }

  234.     /** Get the week number since the GNSS reference epoch.
  235.      * <p>
  236.      * The week number returned here has been fixed for GNSS week rollover, i.e.
  237.      * it may be larger than the corresponding week cycle of the constellation.
  238.      * </p>
  239.      * @return week number since since the GNSS reference epoch
  240.      */
  241.     public int getWeekNumber() {
  242.         return weekNumber;
  243.     }

  244.     /** Get the number of milliseconds since week start.
  245.      * @return number of milliseconds since week start
  246.      */
  247.     public double getMilliInWeek() {
  248.         return getSecondsInWeek() * 1000.0;
  249.     }

  250.     /** Get the number of seconds since week start.
  251.      * @return number of seconds since week start
  252.      * @since 12.0
  253.      */
  254.     public double getSecondsInWeek() {
  255.         return secondsInWeek;
  256.     }

  257.     /** {@inheritDoc} */
  258.     @Override
  259.     public AbsoluteDate getDate() {
  260.         return date;
  261.     }

  262.     /** Get the time scale related to the given satellite system.
  263.      * @param satellite satellite system
  264.      * @param timeScales set of time scales.
  265.      * @return the time scale
  266.      */
  267.     private TimeScale getTimeScale(final SatelliteSystem satellite,
  268.                                    final TimeScales timeScales) {
  269.         switch (satellite) {
  270.             case GPS     : return timeScales.getGPS();
  271.             case GALILEO : return timeScales.getGST();
  272.             case QZSS    : return timeScales.getQZSS();
  273.             case BEIDOU  : return timeScales.getBDT();
  274.             case IRNSS   : return timeScales.getIRNSS();
  275.             case SBAS    : return timeScales.getGPS();
  276.             default      : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
  277.         }
  278.     }

  279.     /** Get the reference epoch of the week number for the given satellite system.
  280.      * <p> Returned parameter is an AbsoluteDate. </p>
  281.      * @param satellite satellite system
  282.      * @param timeScales set of time scales.
  283.      * @return the reference epoch
  284.      */
  285.     private AbsoluteDate getWeekReferenceAbsoluteDate(final SatelliteSystem satellite,
  286.                                                       final TimeScales timeScales) {
  287.         switch (satellite) {
  288.             case GPS     : return timeScales.getGpsEpoch();
  289.             case GALILEO : return timeScales.getGalileoEpoch();
  290.             case QZSS    : return timeScales.getQzssEpoch();
  291.             case BEIDOU  : return timeScales.getBeidouEpoch();
  292.             case IRNSS   : return timeScales.getIrnssEpoch();
  293.             case SBAS    : return timeScales.getGpsEpoch();
  294.             default      : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
  295.         }
  296.     }

  297.     /** Get the reference epoch of the week number for the given satellite system.
  298.      * <p> Returned parameter is a DateComponents. </p>
  299.      * @param satellite satellite system
  300.      * @return the reference epoch
  301.      */
  302.     private DateComponents getWeekReferenceDateComponents(final SatelliteSystem satellite) {
  303.         switch (satellite) {
  304.             case GPS     : return DateComponents.GPS_EPOCH;
  305.             case GALILEO : return DateComponents.GALILEO_EPOCH;
  306.             case QZSS    : return DateComponents.QZSS_EPOCH;
  307.             case BEIDOU  : return DateComponents.BEIDOU_EPOCH;
  308.             case IRNSS   : return DateComponents.IRNSS_EPOCH;
  309.             case SBAS    : return DateComponents.GPS_EPOCH;
  310.             default      : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
  311.         }
  312.     }

  313.     /** Replace the instance with a data transfer object for serialization.
  314.      * @return data transfer object that will be serialized
  315.      */
  316.     @DefaultDataContext
  317.     private Object writeReplace() {
  318.         return new DataTransferObject(weekNumber, secondsInWeek, system);
  319.     }

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

  323.         /** Serializable UID. */
  324.         private static final long serialVersionUID = 20221228L;

  325.         /** Week number since the GNSS reference epoch. */
  326.         private final int weekNumber;

  327.         /** Number of seconds since week start. */
  328.         private final double secondsInWeek;

  329.         /** Satellite system to consider. */
  330.         private final SatelliteSystem system;

  331.         /** Simple constructor.
  332.          * @param weekNumber week number since the GNSS reference epoch
  333.          * @param secondsInWeek number of seconds since week start
  334.          * @param system satellite system to consider
  335.          */
  336.         DataTransferObject(final int weekNumber, final double secondsInWeek,
  337.                            final SatelliteSystem system) {
  338.             this.weekNumber    = weekNumber;
  339.             this.secondsInWeek = secondsInWeek;
  340.             this.system        = system;
  341.         }

  342.         /** Replace the deserialized data transfer object with a {@link GNSSDate}.
  343.          * @return replacement {@link GNSSDate}
  344.          */
  345.         private Object readResolve() {
  346.             return new GNSSDate(weekNumber, secondsInWeek, system);
  347.         }

  348.     }

  349.     /** Enumerate for GNSS data. */
  350.     private enum GNSSDateType {

  351.         /** GPS. */
  352.         GPS(SatelliteSystem.GPS, 1024),

  353.         /** Galileo. */
  354.         GALILEO(SatelliteSystem.GALILEO, 4096),

  355.         /** QZSS. */
  356.         QZSS(SatelliteSystem.QZSS, 1024),

  357.         /** BeiDou. */
  358.         BEIDOU(SatelliteSystem.BEIDOU, 8192),

  359.         /** IRNSS. */
  360.         IRNSS(SatelliteSystem.IRNSS, 1024),

  361.         /** SBAS. */
  362.         SBAS(SatelliteSystem.SBAS, 1024);

  363.         /** Map for the number of week in one GNSS rollover cycle. */
  364.         private static final Map<SatelliteSystem, Integer> CYCLE_MAP = new HashMap<SatelliteSystem, Integer>();
  365.         static {
  366.             for (final GNSSDateType type : values()) {
  367.                 final int             val       = type.getRollOverCycle();
  368.                 final SatelliteSystem satellite = type.getSatelliteSystem();
  369.                 CYCLE_MAP.put(satellite, val);
  370.             }
  371.         }

  372.         /** Number of week in one rollover cycle. */
  373.         private final int numberOfWeek;

  374.         /** Satellite system. */
  375.         private final SatelliteSystem satelliteSystem;

  376.         /**
  377.          * Build a new instance.
  378.          *
  379.          * @param system satellite system
  380.          * @param rollover number of week in one rollover cycle
  381.          */
  382.         GNSSDateType(final SatelliteSystem system, final int rollover) {
  383.             this.satelliteSystem = system;
  384.             this.numberOfWeek    = rollover;
  385.         }

  386.         /** Get the number of week in one rollover cycle.
  387.          * @return  the number of week in one rollover cycle
  388.          */
  389.         private int getRollOverCycle() {
  390.             return numberOfWeek;
  391.         }

  392.         /** Get the satellite system.
  393.          * @return the satellite system
  394.          */
  395.         private SatelliteSystem getSatelliteSystem() {
  396.             return satelliteSystem;
  397.         }

  398.         /** Get the number of week in one rollover cycle for the given satellite system.
  399.          *
  400.          * @param satellite satellite system
  401.          * @return the number of week in one rollover cycle for the given satellite system
  402.          */
  403.         private static int getRollOverWeek(final SatelliteSystem satellite) {
  404.             return CYCLE_MAP.get(satellite);
  405.         }

  406.     }
  407. }