GNSSDate.java

  1. /* Copyright 2002-2025 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 final AtomicReference<DateComponents> ROLLOVER_REFERENCE = new AtomicReference<>(null);

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

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

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

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

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

  109.     /**
  110.      * Build an instance corresponding to a GNSS date.
  111.      * <p>
  112.      * GNSS dates are provided as a week number starting at the GNSS reference epoch and
  113.      * as a number of seconds since week start.
  114.      * </p>
  115.      * <p>
  116.      * Many interfaces provide week number modulo the constellation week cycle. In order
  117.      * to cope with this, when the week number is smaller than the week cycle, this
  118.      * constructor assumes a modulo operation has been performed and it will fix the week
  119.      * number according to the reference date set up for handling rollover (see {@link
  120.      * #setRolloverReference(DateComponents) setRolloverReference(reference)}). If the
  121.      * week number is equal to the week cycle or larger, it will be used without any
  122.      * correction.
  123.      * </p>
  124.      *
  125.      * @param weekNumber    week number
  126.      * @param secondsInWeek number of seconds since week start
  127.      * @param system        satellite system to consider
  128.      * @param timeScales    the set of time scales. Used to retrieve the appropriate time
  129.      *                      scale for the given {@code system}.
  130.      * @since 12.0
  131.      */
  132.     public GNSSDate(final int weekNumber, final double secondsInWeek,
  133.                     final SatelliteSystem system, final TimeScales timeScales) {
  134.         this(weekNumber, new TimeOffset(secondsInWeek), system, timeScales);
  135.     }

  136.     /**
  137.      * Build an instance corresponding to a GNSS date.
  138.      * <p>
  139.      * GNSS dates are provided as a week number starting at the GNSS reference epoch and
  140.      * as a number of seconds since week start.
  141.      * </p>
  142.      * <p>
  143.      * Many interfaces provide week number modulo the constellation week cycle. In order
  144.      * to cope with this, when the week number is smaller than the week cycle, this
  145.      * constructor assumes a modulo operation has been performed and it will fix the week
  146.      * number according to the reference date set up for handling rollover (see {@link
  147.      * #setRolloverReference(DateComponents) setRolloverReference(reference)}). If the
  148.      * week number is equal to the week cycle or larger, it will be used without any
  149.      * correction.
  150.      * </p>
  151.      *
  152.      * @param weekNumber    week number
  153.      * @param secondsInWeek number of seconds since week start
  154.      * @param system        satellite system to consider
  155.      * @param timeScales    the set of time scales. Used to retrieve the appropriate time
  156.      *                      scale for the given {@code system}.
  157.      * @since 13.0
  158.      */
  159.     public GNSSDate(final int weekNumber, final TimeOffset secondsInWeek,
  160.                     final SatelliteSystem system, final TimeScales timeScales) {

  161.         final int day = (int) (secondsInWeek.getSeconds() / TimeOffset.DAY.getSeconds());
  162.         final TimeOffset secondsInDay = new TimeOffset(secondsInWeek.getSeconds() % TimeOffset.DAY.getSeconds(),
  163.                                                        secondsInWeek.getAttoSeconds());

  164.         int w = weekNumber;
  165.         DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
  166.         final int cycleW = GNSSDateType.getRollOverWeek(system);
  167.         if (weekNumber < cycleW) {

  168.             DateComponents reference = ROLLOVER_REFERENCE.get();
  169.             if (reference == null) {
  170.                 // lazy setting of a default reference, using end of EOP entries
  171.                 final UT1Scale       ut1       = timeScales.getUT1(IERSConventions.IERS_2010, true);
  172.                 final List<EOPEntry> eop       = ut1.getEOPHistory().getEntries();
  173.                 final int            lastMJD   = eop.get(eop.size() - 1).getMjd();
  174.                 reference = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, lastMJD);
  175.                 ROLLOVER_REFERENCE.compareAndSet(null, reference);
  176.             }

  177.             // fix GNSS week rollover
  178.             final int cycleD = WEEK_D * cycleW;
  179.             while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
  180.                 dc = new DateComponents(dc, cycleD);
  181.                 w += cycleW;
  182.             }

  183.         }

  184.         this.weekNumber    = w;
  185.         this.secondsInWeek = secondsInWeek;

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

  187.     }

  188.     /**
  189.      * Build an instance corresponding to a GNSS date.
  190.      * <p>
  191.      * GNSS dates are provided as a week number starting at the GNSS reference epoch and
  192.      * as a number of seconds since week start.
  193.      * </p>
  194.      *
  195.      * @param weekNumber    week number
  196.      * @param secondsInWeek number of seconds since week start
  197.      * @param system        satellite system to consider
  198.      * @param reference     reference date for rollover, the generated date will be less
  199.      *                      than one half cycle from this date
  200.      * @param timeScales    the set of time scales. Used to retrieve the appropriate time
  201.      *                      scale for the given {@code system}.
  202.      * @since 12.0
  203.      */
  204.     public GNSSDate(final int weekNumber, final double secondsInWeek,
  205.                     final SatelliteSystem system, final DateComponents reference,
  206.                     final TimeScales timeScales) {
  207.         this(weekNumber, new TimeOffset(secondsInWeek), system, reference, timeScales);
  208.     }

  209.     /**
  210.      * Build an instance corresponding to a GNSS date.
  211.      * <p>
  212.      * GNSS dates are provided as a week number starting at the GNSS reference epoch and
  213.      * as a number of seconds since week start.
  214.      * </p>
  215.      *
  216.      * @param weekNumber    week number
  217.      * @param secondsInWeek number of seconds since week start
  218.      * @param system        satellite system to consider
  219.      * @param reference     reference date for rollover, the generated date will be less
  220.      *                      than one half cycle from this date
  221.      * @param timeScales    the set of time scales. Used to retrieve the appropriate time
  222.      *                      scale for the given {@code system}.
  223.      * @since 13.0
  224.      */
  225.     public GNSSDate(final int weekNumber, final TimeOffset secondsInWeek,
  226.                     final SatelliteSystem system, final DateComponents reference,
  227.                     final TimeScales timeScales) {

  228.         final int day = (int) (secondsInWeek.getSeconds() / TimeOffset.DAY.getSeconds());
  229.         final TimeOffset secondsInDay = new TimeOffset(secondsInWeek.getSeconds() % TimeOffset.DAY.getSeconds(),
  230.                                                        secondsInWeek.getAttoSeconds());

  231.         int w = weekNumber;
  232.         DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
  233.         final int cycleW = GNSSDateType.getRollOverWeek(system);
  234.         if (weekNumber < cycleW) {

  235.             // fix GNSS week rollover
  236.             final int cycleD = WEEK_D * cycleW;
  237.             while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
  238.                 dc = new DateComponents(dc, cycleD);
  239.                 w += cycleW;
  240.             }

  241.         }

  242.         this.weekNumber    = w;
  243.         this.secondsInWeek = secondsInWeek;

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

  245.     }

  246.     /** Build an instance from an absolute date.
  247.      *
  248.      * <p>This method uses the {@link DataContext#getDefault() default data context}.
  249.      *
  250.      * @param date absolute date to consider
  251.      * @param system satellite system to consider
  252.      * @see #GNSSDate(AbsoluteDate, SatelliteSystem, TimeScales)
  253.      */
  254.     @DefaultDataContext
  255.     public GNSSDate(final AbsoluteDate date, final SatelliteSystem system) {
  256.         this(date, system, DataContext.getDefault().getTimeScales());
  257.     }

  258.     /**
  259.      * Build an instance from an absolute date.
  260.      *
  261.      * @param date       absolute date to consider
  262.      * @param system     satellite system to consider
  263.      * @param timeScales the set of time scales. Used to retrieve the appropriate time
  264.      *                   scale for the given {@code system}.
  265.      * @since 10.1
  266.      */
  267.     public GNSSDate(final AbsoluteDate date,
  268.                     final SatelliteSystem system,
  269.                     final TimeScales timeScales) {

  270.         final AbsoluteDate epoch = getWeekReferenceAbsoluteDate(system, timeScales);
  271.         this.weekNumber  = (int) FastMath.floor(date.durationFrom(epoch) / WEEK_S);
  272.         final AbsoluteDate weekStart = new AbsoluteDate(epoch, WEEK_S * weekNumber);
  273.         this.secondsInWeek = date.accurateDurationFrom(weekStart);
  274.         this.date          = date;

  275.     }

  276.     /** Set a reference date for ensuring continuity across GNSS week rollover.
  277.      * <p>
  278.      * Instance created using the {@link #GNSSDate(int, double, SatelliteSystem) GNSSDate(weekNumber, secondsInWeek, system)}
  279.      * constructor and with a week number between 0 and the constellation week cycle (cycleW) after this method has been called will
  280.      * fix the week number to ensure they correspond to dates between {@code reference - cycleW / 2 weeks}
  281.      * and {@code reference + cycleW / 2 weeks}.
  282.      * </p>
  283.      * <p>
  284.      * If this method is never called, a default reference date for rollover will be set using
  285.      * the date of the last known EOP entry retrieved from {@link UT1Scale#getEOPHistory() UT1}
  286.      * time scale.
  287.      * </p>
  288.      * @param reference reference date for GNSS week rollover
  289.      * @see #getRolloverReference()
  290.      * @see #GNSSDate(int, double, SatelliteSystem)
  291.      * @since 9.3.1
  292.      */
  293.     public static void setRolloverReference(final DateComponents reference) {
  294.         ROLLOVER_REFERENCE.set(reference);
  295.     }

  296.     /** Get the reference date ensuring continuity across GNSS week rollover.
  297.      * @return reference reference date for GNSS week rollover
  298.      * @see #setRolloverReference(DateComponents)
  299.      * @see #GNSSDate(int, double, SatelliteSystem)
  300.      * @since 9.3.1
  301.      */
  302.     public static DateComponents getRolloverReference() {
  303.         return ROLLOVER_REFERENCE.get();
  304.     }

  305.     /** Get the week number since the GNSS reference epoch.
  306.      * <p>
  307.      * The week number returned here has been fixed for GNSS week rollover, i.e.
  308.      * it may be larger than the corresponding week cycle of the constellation.
  309.      * </p>
  310.      * @return week number since the GNSS reference epoch
  311.      */
  312.     public int getWeekNumber() {
  313.         return weekNumber;
  314.     }

  315.     /** Get the number of milliseconds since week start.
  316.      * @return number of milliseconds since week start
  317.      */
  318.     public double getMilliInWeek() {
  319.         return getSecondsInWeek() * 1000.0;
  320.     }

  321.     /** Get the number of seconds since week start.
  322.      * @return number of seconds since week start
  323.      * @since 12.0
  324.      */
  325.     public double getSecondsInWeek() {
  326.         return getSplitSecondsInWeek().toDouble();
  327.     }

  328.     /** Get the number of seconds since week start.
  329.      * @return number of seconds since week start
  330.      * @since 13.0
  331.      */
  332.     public TimeOffset getSplitSecondsInWeek() {
  333.         return secondsInWeek;
  334.     }

  335.     /** {@inheritDoc} */
  336.     @Override
  337.     public AbsoluteDate getDate() {
  338.         return date;
  339.     }

  340.     /** Get the time scale related to the given satellite system.
  341.      * @param satellite satellite system
  342.      * @param timeScales set of time scales.
  343.      * @return the time scale
  344.      */
  345.     private TimeScale getTimeScale(final SatelliteSystem satellite,
  346.                                    final TimeScales timeScales) {
  347.         switch (satellite) {
  348.             case GPS     : return timeScales.getGPS();
  349.             case GALILEO : return timeScales.getGST();
  350.             case QZSS    : return timeScales.getQZSS();
  351.             case BEIDOU  : return timeScales.getBDT();
  352.             case NAVIC   : return timeScales.getNavIC();
  353.             case SBAS    : return timeScales.getGPS();
  354.             default      : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
  355.         }
  356.     }

  357.     /** Get the reference epoch of the week number for the given satellite system.
  358.      * <p> Returned parameter is an AbsoluteDate. </p>
  359.      * @param satellite satellite system
  360.      * @param timeScales set of time scales.
  361.      * @return the reference epoch
  362.      */
  363.     private AbsoluteDate getWeekReferenceAbsoluteDate(final SatelliteSystem satellite,
  364.                                                       final TimeScales timeScales) {
  365.         switch (satellite) {
  366.             case GPS     : return timeScales.getGpsEpoch();
  367.             case GALILEO : return timeScales.getGalileoEpoch();
  368.             case QZSS    : return timeScales.getQzssEpoch();
  369.             case BEIDOU  : return timeScales.getBeidouEpoch();
  370.             case NAVIC   : return timeScales.getNavicEpoch();
  371.             case SBAS    : return timeScales.getGpsEpoch();
  372.             default      : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
  373.         }
  374.     }

  375.     /** Get the reference epoch of the week number for the given satellite system.
  376.      * <p> Returned parameter is a DateComponents. </p>
  377.      * @param satellite satellite system
  378.      * @return the reference epoch
  379.      */
  380.     private DateComponents getWeekReferenceDateComponents(final SatelliteSystem satellite) {
  381.         switch (satellite) {
  382.             case GPS     : return DateComponents.GPS_EPOCH;
  383.             case GALILEO : return DateComponents.GALILEO_EPOCH;
  384.             case QZSS    : return DateComponents.QZSS_EPOCH;
  385.             case BEIDOU  : return DateComponents.BEIDOU_EPOCH;
  386.             case NAVIC   : return DateComponents.NAVIC_EPOCH;
  387.             case SBAS    : return DateComponents.GPS_EPOCH;
  388.             default      : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
  389.         }
  390.     }

  391.     /** Enumerate for GNSS data. */
  392.     public enum GNSSDateType {

  393.         /** GPS. */
  394.         GPS(SatelliteSystem.GPS, 1024),

  395.         /** Galileo. */
  396.         GALILEO(SatelliteSystem.GALILEO, 4096),

  397.         /** QZSS. */
  398.         QZSS(SatelliteSystem.QZSS, 1024),

  399.         /** BeiDou. */
  400.         BEIDOU(SatelliteSystem.BEIDOU, 8192),

  401.         /** NavIC. */
  402.         NAVIC(SatelliteSystem.NAVIC, 1024),

  403.         /** SBAS. */
  404.         SBAS(SatelliteSystem.SBAS, 1024);

  405.         /** Map for the number of week in one GNSS rollover cycle. */
  406.         private static final Map<SatelliteSystem, Integer> CYCLE_MAP = new HashMap<>();
  407.         static {
  408.             for (final GNSSDateType type : values()) {
  409.                 final int             val       = type.getRollOverCycle();
  410.                 final SatelliteSystem satellite = type.getSatelliteSystem();
  411.                 CYCLE_MAP.put(satellite, val);
  412.             }
  413.         }

  414.         /** Number of week in one rollover cycle. */
  415.         private final int numberOfWeek;

  416.         /** Satellite system. */
  417.         private final SatelliteSystem satelliteSystem;

  418.         /**
  419.          * Build a new instance.
  420.          *
  421.          * @param system satellite system
  422.          * @param rollover number of week in one rollover cycle
  423.          */
  424.         GNSSDateType(final SatelliteSystem system, final int rollover) {
  425.             this.satelliteSystem = system;
  426.             this.numberOfWeek    = rollover;
  427.         }

  428.         /** Get the number of week in one rollover cycle.
  429.          * @return  the number of week in one rollover cycle
  430.          */
  431.         public int getRollOverCycle() {
  432.             return numberOfWeek;
  433.         }

  434.         /** Get the satellite system.
  435.          * @return the satellite system
  436.          */
  437.         public SatelliteSystem getSatelliteSystem() {
  438.             return satelliteSystem;
  439.         }

  440.         /** Get the number of week in one rollover cycle for the given satellite system.
  441.          *
  442.          * @param satellite satellite system
  443.          * @return the number of week in one rollover cycle for the given satellite system
  444.          */
  445.         public static int getRollOverWeek(final SatelliteSystem satellite) {
  446.             return CYCLE_MAP.get(satellite);
  447.         }

  448.     }
  449. }