GPSDate.java

  1. /* Copyright 2002-2019 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.List;
  20. import java.util.concurrent.atomic.AtomicReference;

  21. import org.hipparchus.util.FastMath;
  22. import org.orekit.frames.EOPEntry;
  23. import org.orekit.utils.Constants;
  24. import org.orekit.utils.IERSConventions;

  25. /** Container for date in GPS form.
  26.  * @author Luc Maisonobe
  27.  * @see AbsoluteDate
  28.  * @since 9.3
  29.  */
  30. public class GPSDate implements Serializable, TimeStamped {

  31.     /** Serializable UID. */
  32.     private static final long serialVersionUID = 20180633L;

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

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

  37.     /** Number of weeks in one rollover cycle. */
  38.     private static final int CYCLE_W = 1024;

  39.     /** Number of days in one rollover cycle. */
  40.     private static final int CYCLE_D = WEEK_D * CYCLE_W;

  41.     /** Conversion factor from seconds to milliseconds. */
  42.     private static final double S_TO_MS = 1000.0;

  43.     /** Reference date for ensuring continuity across GPS week rollover.
  44.      * @since 9.3.1
  45.      */
  46.     private static AtomicReference<DateComponents> rolloverReference = new AtomicReference<DateComponents>(null);

  47.     /** Week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}. */
  48.     private final int weekNumber;

  49.     /** Number of milliseconds since week start. */
  50.     private final double milliInWeek;

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

  53.     /** Build an instance corresponding to a GPS date.
  54.      * <p>
  55.      * GPS dates are provided as a week number starting at {@link AbsoluteDate#GPS_EPOCH GPS epoch}
  56.      * and as a number of milliseconds since week start.
  57.      * </p>
  58.      * <p>
  59.      * Many interfaces provide only the 10 lower bits of the GPS week number, just as it comes from
  60.      * the GPS signal. In other words they use a week number modulo 1024. In order to cope with
  61.      * this, when the week number is smaller than 1024, this constructor assumes a modulo operation
  62.      * has been performed and it will fix the week number according to the reference date set up for
  63.      * handling rollover (see {@link #setRolloverReference(DateComponents) setRolloverReference(reference)}).
  64.      * If the week number is 1024 or larger, it will be used without any correction.
  65.      * </p>
  66.      * @param weekNumber week number, either absolute or modulo 1024
  67.      * @param milliInWeek number of milliseconds since week start
  68.      */
  69.     public GPSDate(final int weekNumber, final double milliInWeek) {

  70.         final int day = (int) FastMath.floor(milliInWeek / (Constants.JULIAN_DAY * S_TO_MS));
  71.         final double secondsInDay = milliInWeek / S_TO_MS - day * Constants.JULIAN_DAY;

  72.         int w = weekNumber;
  73.         DateComponents dc = new DateComponents(DateComponents.GPS_EPOCH, weekNumber * 7 + day);
  74.         if (weekNumber < 1024) {

  75.             DateComponents reference = rolloverReference.get();
  76.             if (reference == null) {
  77.                 // lazy setting of a default reference, using end of EOP entries
  78.                 final UT1Scale       ut1       = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
  79.                 final List<EOPEntry> eop       = ut1.getEOPHistory().getEntries();
  80.                 final int            lastMJD   = eop.get(eop.size() - 1).getMjd();
  81.                 reference = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, lastMJD);
  82.                 rolloverReference.compareAndSet(null, reference);
  83.             }

  84.             // fix GPS week rollover
  85.             while (dc.getJ2000Day() < reference.getJ2000Day() - CYCLE_D / 2) {
  86.                 dc = new DateComponents(dc, CYCLE_D);
  87.                 w += CYCLE_W;
  88.             }

  89.         }

  90.         this.weekNumber  = w;
  91.         this.milliInWeek = milliInWeek;

  92.         date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), TimeScalesFactory.getGPS());

  93.     }

  94.     /** Build an instance from an absolute date.
  95.      * @param date absolute date to consider
  96.      */
  97.     public GPSDate(final AbsoluteDate date) {

  98.         this.weekNumber  = (int) FastMath.floor(date.durationFrom(AbsoluteDate.GPS_EPOCH) / WEEK_S);
  99.         final AbsoluteDate weekStart = new AbsoluteDate(AbsoluteDate.GPS_EPOCH, WEEK_S * weekNumber);
  100.         this.milliInWeek = date.durationFrom(weekStart) * S_TO_MS;
  101.         this.date        = date;

  102.     }

  103.     /** Set a reference date for ensuring continuity across GPS week rollover.
  104.      * <p>
  105.      * Instance created using the {@link #GPSDate(int, double) GPSDate(weekNumber, milliInWeek)}
  106.      * constructor and with a week number between 0 and 1024 after this method has been called will
  107.      * fix the week number to ensure they correspond to dates between {@code reference - 512 weeks}
  108.      * and {@code reference + 512 weeks}.
  109.      * </p>
  110.      * <p>
  111.      * If this method is never called, a default reference date for rollover will be set using
  112.      * the date of the last known EOP entry retrieved from {@link UT1Scale#getEOPHistory() UT1}
  113.      * time scale.
  114.      * </p>
  115.      * @param reference reference date for GPS week rollover
  116.      * @see #getRolloverReference()
  117.      * @see #GPSDate(int, double)
  118.      * @since 9.3.1
  119.      */
  120.     public static void setRolloverReference(final DateComponents reference) {
  121.         rolloverReference.set(reference);
  122.     }

  123.     /** Get the reference date ensuring continuity across GPS week rollover.
  124.      * @return reference reference date for GPS week rollover
  125.      * @see #setRolloverReference(AbsoluteDate)
  126.      * @see #GPSDate(int, double)
  127.      * @since 9.3.1
  128.      */
  129.     public static DateComponents getRolloverReference() {
  130.         return rolloverReference.get();
  131.     }

  132.     /** Get the week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}.
  133.      * <p>
  134.      * The week number returned here has been fixed for GPS week rollover, i.e.
  135.      * it may be larger than 1024.
  136.      * </p>
  137.      * @return week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}
  138.      */
  139.     public int getWeekNumber() {
  140.         return weekNumber;
  141.     }

  142.     /** Get the number of milliseconds since week start.
  143.      * @return number of milliseconds since week start
  144.      */
  145.     public double getMilliInWeek() {
  146.         return milliInWeek;
  147.     }

  148.     /** {@inheritDoc} */
  149.     @Override
  150.     public AbsoluteDate getDate() {
  151.         return date;
  152.     }

  153.     /** Replace the instance with a data transfer object for serialization.
  154.      * @return data transfer object that will be serialized
  155.      */
  156.     private Object writeReplace() {
  157.         return new DataTransferObject(weekNumber, milliInWeek);
  158.     }

  159.     /** Internal class used only for serialization. */
  160.     private static class DataTransferObject implements Serializable {

  161.         /** Serializable UID. */
  162.         private static final long serialVersionUID = 20180633L;

  163.         /** Week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}. */
  164.         private final int weekNumber;

  165.         /** Number of milliseconds since week start. */
  166.         private final double milliInWeek;

  167.         /** Simple constructor.
  168.          * @param weekNumber week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}
  169.          * @param milliInWeek number of milliseconds since week start
  170.          */
  171.         DataTransferObject(final int weekNumber, final double milliInWeek) {
  172.             this.weekNumber  = weekNumber;
  173.             this.milliInWeek = milliInWeek;
  174.         }

  175.         /** Replace the deserialized data transfer object with a {@link GPSDate}.
  176.          * @return replacement {@link GPSDate}
  177.          */
  178.         private Object readResolve() {
  179.             return new GPSDate(weekNumber, milliInWeek);
  180.         }

  181.     }

  182. }