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  
19  import java.io.Serializable;
20  import java.util.List;
21  import java.util.concurrent.atomic.AtomicReference;
22  
23  import org.hipparchus.util.FastMath;
24  import org.orekit.frames.EOPEntry;
25  import org.orekit.utils.Constants;
26  import org.orekit.utils.IERSConventions;
27  
28  /** Container for date in GPS form.
29   * @author Luc Maisonobe
30   * @see AbsoluteDate
31   * @since 9.3
32   */
33  public class GPSDate implements Serializable, TimeStamped {
34  
35      /** Serializable UID. */
36      private static final long serialVersionUID = 20180633L;
37  
38      /** Duration of a week in days. */
39      private static final int WEEK_D = 7;
40  
41      /** Duration of a week in seconds. */
42      private static final double WEEK_S = WEEK_D * Constants.JULIAN_DAY;
43  
44      /** Number of weeks in one rollover cycle. */
45      private static final int CYCLE_W = 1024;
46  
47      /** Number of days in one rollover cycle. */
48      private static final int CYCLE_D = WEEK_D * CYCLE_W;
49  
50      /** Conversion factor from seconds to milliseconds. */
51      private static final double S_TO_MS = 1000.0;
52  
53      /** Reference date for ensuring continuity across GPS week rollover.
54       * @since 9.3.1
55       */
56      private static AtomicReference<DateComponents> rolloverReference = new AtomicReference<DateComponents>(null);
57  
58      /** Week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}. */
59      private final int weekNumber;
60  
61      /** Number of milliseconds since week start. */
62      private final double milliInWeek;
63  
64      /** Corresponding date. */
65      private final transient AbsoluteDate date;
66  
67      /** Build an instance corresponding to a GPS date.
68       * <p>
69       * GPS dates are provided as a week number starting at {@link AbsoluteDate#GPS_EPOCH GPS epoch}
70       * and as a number of milliseconds since week start.
71       * </p>
72       * <p>
73       * Many interfaces provide only the 10 lower bits of the GPS week number, just as it comes from
74       * the GPS signal. In other words they use a week number modulo 1024. In order to cope with
75       * this, when the week number is smaller than 1024, this constructor assumes a modulo operation
76       * has been performed and it will fix the week number according to the reference date set up for
77       * handling rollover (see {@link #setRolloverReference(DateComponents) setRolloverReference(reference)}).
78       * If the week number is 1024 or larger, it will be used without any correction.
79       * </p>
80       * @param weekNumber week number, either absolute or modulo 1024
81       * @param milliInWeek number of milliseconds since week start
82       */
83      public GPSDate(final int weekNumber, final double milliInWeek) {
84  
85          final int day = (int) FastMath.floor(milliInWeek / (Constants.JULIAN_DAY * S_TO_MS));
86          final double secondsInDay = milliInWeek / S_TO_MS - day * Constants.JULIAN_DAY;
87  
88          int w = weekNumber;
89          DateComponents#DateComponents">DateComponents dc = new DateComponents(DateComponents.GPS_EPOCH, weekNumber * 7 + day);
90          if (weekNumber < 1024) {
91  
92              DateComponents reference = rolloverReference.get();
93              if (reference == null) {
94                  // lazy setting of a default reference, using end of EOP entries
95                  final UT1Scale       ut1       = TimeScalesFactory.getUT1(IERSConventions.IERS_2010, true);
96                  final List<EOPEntry> eop       = ut1.getEOPHistory().getEntries();
97                  final int            lastMJD   = eop.get(eop.size() - 1).getMjd();
98                  reference = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, lastMJD);
99                  rolloverReference.compareAndSet(null, reference);
100             }
101 
102             // fix GPS week rollover
103             while (dc.getJ2000Day() < reference.getJ2000Day() - CYCLE_D / 2) {
104                 dc = new DateComponents(dc, CYCLE_D);
105                 w += CYCLE_W;
106             }
107 
108         }
109 
110         this.weekNumber  = w;
111         this.milliInWeek = milliInWeek;
112 
113         date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), TimeScalesFactory.getGPS());
114 
115     }
116 
117     /** Build an instance from an absolute date.
118      * @param date absolute date to consider
119      */
120     public GPSDate(final AbsoluteDate date) {
121 
122         this.weekNumber  = (int) FastMath.floor(date.durationFrom(AbsoluteDate.GPS_EPOCH) / WEEK_S);
123         final AbsoluteDateteDate">AbsoluteDate weekStart = new AbsoluteDate(AbsoluteDate.GPS_EPOCH, WEEK_S * weekNumber);
124         this.milliInWeek = date.durationFrom(weekStart) * S_TO_MS;
125         this.date        = date;
126 
127     }
128 
129     /** Set a reference date for ensuring continuity across GPS week rollover.
130      * <p>
131      * Instance created using the {@link #GPSDate(int, double) GPSDate(weekNumber, milliInWeek)}
132      * constructor and with a week number between 0 and 1024 after this method has been called will
133      * fix the week number to ensure they correspond to dates between {@code reference - 512 weeks}
134      * and {@code reference + 512 weeks}.
135      * </p>
136      * <p>
137      * If this method is never called, a default reference date for rollover will be set using
138      * the date of the last known EOP entry retrieved from {@link UT1Scale#getEOPHistory() UT1}
139      * time scale.
140      * </p>
141      * @param reference reference date for GPS week rollover
142      * @see #getRolloverReference()
143      * @see #GPSDate(int, double)
144      * @since 9.3.1
145      */
146     public static void setRolloverReference(final DateComponents reference) {
147         rolloverReference.set(reference);
148     }
149 
150     /** Get the reference date ensuring continuity across GPS week rollover.
151      * @return reference reference date for GPS week rollover
152      * @see #setRolloverReference(AbsoluteDate)
153      * @see #GPSDate(int, double)
154      * @since 9.3.1
155      */
156     public static DateComponents getRolloverReference() {
157         return rolloverReference.get();
158     }
159 
160     /** Get the week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}.
161      * <p>
162      * The week number returned here has been fixed for GPS week rollover, i.e.
163      * it may be larger than 1024.
164      * </p>
165      * @return week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}
166      */
167     public int getWeekNumber() {
168         return weekNumber;
169     }
170 
171     /** Get the number of milliseconds since week start.
172      * @return number of milliseconds since week start
173      */
174     public double getMilliInWeek() {
175         return milliInWeek;
176     }
177 
178     /** {@inheritDoc} */
179     @Override
180     public AbsoluteDate getDate() {
181         return date;
182     }
183 
184     /** Replace the instance with a data transfer object for serialization.
185      * @return data transfer object that will be serialized
186      */
187     private Object writeReplace() {
188         return new DataTransferObject(weekNumber, milliInWeek);
189     }
190 
191     /** Internal class used only for serialization. */
192     private static class DataTransferObject implements Serializable {
193 
194         /** Serializable UID. */
195         private static final long serialVersionUID = 20180633L;
196 
197         /** Week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}. */
198         private final int weekNumber;
199 
200         /** Number of milliseconds since week start. */
201         private final double milliInWeek;
202 
203         /** Simple constructor.
204          * @param weekNumber week number since {@link AbsoluteDate#GPS_EPOCH GPS epoch}
205          * @param milliInWeek number of milliseconds since week start
206          */
207         DataTransferObject(final int weekNumber, final double milliInWeek) {
208             this.weekNumber  = weekNumber;
209             this.milliInWeek = milliInWeek;
210         }
211 
212         /** Replace the deserialized data transfer object with a {@link GPSDate}.
213          * @return replacement {@link GPSDate}
214          */
215         private Object readResolve() {
216             return new GPSDate(weekNumber, milliInWeek);
217         }
218 
219     }
220 
221 }