1 /* Copyright 2002-2021 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
19 import java.io.Serializable;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.concurrent.atomic.AtomicReference;
24
25 import org.hipparchus.util.FastMath;
26 import org.orekit.annotation.DefaultDataContext;
27 import org.orekit.data.DataContext;
28 import org.orekit.errors.OrekitException;
29 import org.orekit.errors.OrekitMessages;
30 import org.orekit.frames.EOPEntry;
31 import org.orekit.gnss.SatelliteSystem;
32 import org.orekit.utils.Constants;
33 import org.orekit.utils.IERSConventions;
34
35 /** Container for date in GNSS form.
36 * <p> This class can be used to handle {@link SatelliteSystem#GPS GPS},
37 * {@link SatelliteSystem#GALILEO Galileo}, {@link SatelliteSystem#BEIDOU BeiDou}
38 * and {@link SatelliteSystem#QZSS QZSS} dates. </p>
39 * @author Luc Maisonobe (original code)
40 * @author Bryan Cazabonne (generalization to all GNSS constellations)
41 * @see AbsoluteDate
42 */
43 public class GNSSDate implements Serializable, TimeStamped {
44
45 /** Serializable UID. */
46 private static final long serialVersionUID = 201902141L;
47
48 /** Duration of a week in days. */
49 private static final int WEEK_D = 7;
50
51 /** Duration of a week in seconds. */
52 private static final double WEEK_S = WEEK_D * Constants.JULIAN_DAY;
53
54 /** Conversion factor from seconds to milliseconds. */
55 private static final double S_TO_MS = 1000.0;
56
57 /** Reference date for ensuring continuity across GNSS week rollover.
58 * @since 9.3.1
59 */
60 private static AtomicReference<DateComponents> rolloverReference = new AtomicReference<DateComponents>(null);
61
62 /** Week number since the GNSS reference epoch. */
63 private final int weekNumber;
64
65 /** Number of milliseconds since week start. */
66 private final double milliInWeek;
67
68 /** Satellite system to consider. */
69 private final SatelliteSystem system;
70
71 /** Corresponding date. */
72 private final transient AbsoluteDate date;
73
74 /** Build an instance corresponding to a GNSS date.
75 * <p>
76 * GNSS dates are provided as a week number starting at
77 * the GNSS reference epoch and as a number of milliseconds
78 * since week start.
79 * </p>
80 * <p>
81 * Many interfaces provide week number modulo the constellation week cycle. In order to cope with
82 * this, when the week number is smaller than the week cycle, this constructor assumes a modulo operation
83 * has been performed and it will fix the week number according to the reference date set up for
84 * handling rollover (see {@link #setRolloverReference(DateComponents) setRolloverReference(reference)}).
85 * If the week number is equal to the week cycle or larger, it will be used without any correction.
86 * </p>
87 *
88 * <p>This method uses the {@link DataContext#getDefault() default data context}.
89 *
90 * @param weekNumber week number
91 * @param milliInWeek number of milliseconds since week start
92 * @param system satellite system to consider
93 * @see #GNSSDate(int, double, SatelliteSystem, TimeScales)
94 */
95 @DefaultDataContext
96 public GNSSDate(final int weekNumber, final double milliInWeek,
97 final SatelliteSystem system) {
98 this(weekNumber, milliInWeek, system, DataContext.getDefault().getTimeScales());
99 }
100
101 /**
102 * Build an instance corresponding to a GNSS date.
103 * <p>
104 * GNSS dates are provided as a week number starting at the GNSS reference epoch and
105 * as a number of milliseconds since week start.
106 * </p>
107 * <p>
108 * Many interfaces provide week number modulo the constellation week cycle. In order
109 * to cope with this, when the week number is smaller than the week cycle, this
110 * constructor assumes a modulo operation has been performed and it will fix the week
111 * number according to the reference date set up for handling rollover (see {@link
112 * #setRolloverReference(DateComponents) setRolloverReference(reference)}). If the
113 * week number is equal to the week cycle or larger, it will be used without any
114 * correction.
115 * </p>
116 *
117 * @param weekNumber week number
118 * @param milliInWeek number of milliseconds since week start
119 * @param system satellite system to consider
120 * @param timeScales the set of time scales. Used to retrieve the appropriate time
121 * scale for the given {@code system}.
122 * @since 10.1
123 */
124 public GNSSDate(final int weekNumber,
125 final double milliInWeek,
126 final SatelliteSystem system,
127 final TimeScales timeScales) {
128
129 final int day = (int) FastMath.floor(milliInWeek / (Constants.JULIAN_DAY * S_TO_MS));
130 final double secondsInDay = milliInWeek / S_TO_MS - day * Constants.JULIAN_DAY;
131
132 int w = weekNumber;
133 DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
134 final int cycleW = GNSSDateType.getRollOverWeek(system);
135 if (weekNumber < cycleW) {
136
137 DateComponents reference = rolloverReference.get();
138 if (reference == null) {
139 // lazy setting of a default reference, using end of EOP entries
140 final UT1Scale ut1 = timeScales.getUT1(IERSConventions.IERS_2010, true);
141 final List<EOPEntry> eop = ut1.getEOPHistory().getEntries();
142 final int lastMJD = eop.get(eop.size() - 1).getMjd();
143 reference = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, lastMJD);
144 rolloverReference.compareAndSet(null, reference);
145 }
146
147 // fix GNSS week rollover
148 final int cycleD = WEEK_D * cycleW;
149 while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
150 dc = new DateComponents(dc, cycleD);
151 w += cycleW;
152 }
153
154 }
155
156 this.weekNumber = w;
157 this.milliInWeek = milliInWeek;
158 this.system = system;
159
160 date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), getTimeScale(system, timeScales));
161
162 }
163
164 /** Build an instance from an absolute date.
165 *
166 * <p>This method uses the {@link DataContext#getDefault() default data context}.
167 *
168 * @param date absolute date to consider
169 * @param system satellite system to consider
170 * @see #GNSSDate(AbsoluteDate, SatelliteSystem, TimeScales)
171 */
172 @DefaultDataContext
173 public GNSSDate(final AbsoluteDate date, final SatelliteSystem system) {
174 this(date, system, DataContext.getDefault().getTimeScales());
175 }
176
177 /**
178 * Build an instance from an absolute date.
179 *
180 * @param date absolute date to consider
181 * @param system satellite system to consider
182 * @param timeScales the set of time scales. Used to retrieve the appropriate time
183 * scale for the given {@code system}.
184 * @since 10.1
185 */
186 public GNSSDate(final AbsoluteDate date,
187 final SatelliteSystem system,
188 final TimeScales timeScales) {
189
190 this.system = system;
191 final AbsoluteDate epoch = getWeekReferenceAbsoluteDate(system, timeScales);
192 this.weekNumber = (int) FastMath.floor(date.durationFrom(epoch) / WEEK_S);
193 final AbsoluteDate weekStart = new AbsoluteDate(epoch, WEEK_S * weekNumber);
194 this.milliInWeek = date.durationFrom(weekStart) * S_TO_MS;
195 this.date = date;
196
197 }
198
199 /** Set a reference date for ensuring continuity across GNSS week rollover.
200 * <p>
201 * Instance created using the {@link #GNSSDate(int, double, SatelliteSystem) GNSSDate(weekNumber, milliInWeek, system)}
202 * constructor and with a week number between 0 and the constellation week cycle (cycleW) after this method has been called will
203 * fix the week number to ensure they correspond to dates between {@code reference - cycleW / 2 weeks}
204 * and {@code reference + cycleW / 2 weeks}.
205 * </p>
206 * <p>
207 * If this method is never called, a default reference date for rollover will be set using
208 * the date of the last known EOP entry retrieved from {@link UT1Scale#getEOPHistory() UT1}
209 * time scale.
210 * </p>
211 * @param reference reference date for GNSS week rollover
212 * @see #getRolloverReference()
213 * @see #GNSSDate(int, double, SatelliteSystem)
214 * @since 9.3.1
215 */
216 public static void setRolloverReference(final DateComponents reference) {
217 rolloverReference.set(reference);
218 }
219
220 /** Get the reference date ensuring continuity across GNSS week rollover.
221 * @return reference reference date for GNSS week rollover
222 * @see #setRolloverReference(DateComponents)
223 * @see #GNSSDate(int, double, SatelliteSystem)
224 * @since 9.3.1
225 */
226 public static DateComponents getRolloverReference() {
227 return rolloverReference.get();
228 }
229
230 /** Get the week number since the GNSS reference epoch.
231 * <p>
232 * The week number returned here has been fixed for GNSS week rollover, i.e.
233 * it may be larger than the corresponding week cycle of the constellation.
234 * </p>
235 * @return week number since since the GNSS reference epoch
236 */
237 public int getWeekNumber() {
238 return weekNumber;
239 }
240
241 /** Get the number of milliseconds since week start.
242 * @return number of milliseconds since week start
243 */
244 public double getMilliInWeek() {
245 return milliInWeek;
246 }
247
248 /** {@inheritDoc} */
249 @Override
250 public AbsoluteDate getDate() {
251 return date;
252 }
253
254 /** Get the time scale related to the given satellite system.
255 * @param satellite satellite system
256 * @param timeScales set of time scales.
257 * @return the time scale
258 */
259 private TimeScale getTimeScale(final SatelliteSystem satellite,
260 final TimeScales timeScales) {
261 switch (satellite) {
262 case GPS : return timeScales.getGPS();
263 case GALILEO : return timeScales.getGST();
264 case QZSS : return timeScales.getQZSS();
265 case BEIDOU : return timeScales.getBDT();
266 case IRNSS : return timeScales.getIRNSS();
267 case SBAS : return timeScales.getGPS();
268 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
269 }
270 }
271
272 /** Get the reference epoch of the week number for the given satellite system.
273 * <p> Returned parameter is an AbsoluteDate. </p>
274 * @param satellite satellite system
275 * @param timeScales set of time scales.
276 * @return the reference epoch
277 */
278 private AbsoluteDate getWeekReferenceAbsoluteDate(final SatelliteSystem satellite,
279 final TimeScales timeScales) {
280 switch (satellite) {
281 case GPS : return timeScales.getGpsEpoch();
282 case GALILEO : return timeScales.getGalileoEpoch();
283 case QZSS : return timeScales.getQzssEpoch();
284 case BEIDOU : return timeScales.getBeidouEpoch();
285 case IRNSS : return timeScales.getIrnssEpoch();
286 case SBAS : return timeScales.getGpsEpoch();
287 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
288 }
289 }
290
291 /** Get the reference epoch of the week number for the given satellite system.
292 * <p> Returned parameter is a DateComponents. </p>
293 * @param satellite satellite system
294 * @return the reference epoch
295 */
296 private DateComponents getWeekReferenceDateComponents(final SatelliteSystem satellite) {
297 switch (satellite) {
298 case GPS : return DateComponents.GPS_EPOCH;
299 case GALILEO : return DateComponents.GALILEO_EPOCH;
300 case QZSS : return DateComponents.QZSS_EPOCH;
301 case BEIDOU : return DateComponents.BEIDOU_EPOCH;
302 case IRNSS : return DateComponents.IRNSS_EPOCH;
303 case SBAS : return DateComponents.GPS_EPOCH;
304 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
305 }
306 }
307
308 /** Replace the instance with a data transfer object for serialization.
309 * @return data transfer object that will be serialized
310 */
311 @DefaultDataContext
312 private Object writeReplace() {
313 return new DataTransferObject(weekNumber, milliInWeek, system);
314 }
315
316 /** Internal class used only for serialization. */
317 @DefaultDataContext
318 private static class DataTransferObject implements Serializable {
319
320 /** Serializable UID. */
321 private static final long serialVersionUID = 201902141L;
322
323 /** Week number since the GNSS reference epoch. */
324 private final int weekNumber;
325
326 /** Number of milliseconds since week start. */
327 private final double milliInWeek;
328
329 /** Satellite system to consider. */
330 private final SatelliteSystem system;
331
332 /** Simple constructor.
333 * @param weekNumber week number since the GNSS reference epoch
334 * @param milliInWeek number of milliseconds since week start
335 * @param system satellite system to consider
336 */
337 DataTransferObject(final int weekNumber, final double milliInWeek,
338 final SatelliteSystem system) {
339 this.weekNumber = weekNumber;
340 this.milliInWeek = milliInWeek;
341 this.system = system;
342 }
343
344 /** Replace the deserialized data transfer object with a {@link GNSSDate}.
345 * @return replacement {@link GNSSDate}
346 */
347 private Object readResolve() {
348 return new GNSSDate(weekNumber, milliInWeek, system);
349 }
350
351 }
352
353 /** Enumerate for GNSS data. */
354 private enum GNSSDateType {
355
356 /** GPS. */
357 GPS(SatelliteSystem.GPS, 1024),
358
359 /** Galileo. */
360 GALILEO(SatelliteSystem.GALILEO, 4096),
361
362 /** QZSS. */
363 QZSS(SatelliteSystem.QZSS, 1024),
364
365 /** BeiDou. */
366 BEIDOU(SatelliteSystem.BEIDOU, 8192),
367
368 /** IRNSS. */
369 IRNSS(SatelliteSystem.IRNSS, 1024),
370
371 /** SBAS. */
372 SBAS(SatelliteSystem.SBAS, 1024);
373
374 /** Map for the number of week in one GNSS rollover cycle. */
375 private static final Map<SatelliteSystem, Integer> CYCLE_MAP = new HashMap<SatelliteSystem, Integer>();
376 static {
377 for (final GNSSDateType type : values()) {
378 final int val = type.getRollOverCycle();
379 final SatelliteSystem satellite = type.getSatelliteSystem();
380 CYCLE_MAP.put(satellite, val);
381 }
382 }
383
384 /** Number of week in one rollover cycle. */
385 private final int numberOfWeek;
386
387 /** Satellite system. */
388 private final SatelliteSystem satelliteSystem;
389
390 /**
391 * Build a new instance.
392 *
393 * @param system satellite system
394 * @param rollover number of week in one rollover cycle
395 */
396 GNSSDateType(final SatelliteSystem system, final int rollover) {
397 this.satelliteSystem = system;
398 this.numberOfWeek = rollover;
399 }
400
401 /** Get the number of week in one rollover cycle.
402 * @return the number of week in one rollover cycle
403 */
404 private int getRollOverCycle() {
405 return numberOfWeek;
406 }
407
408 /** Get the satellite system.
409 * @return the satellite system
410 */
411 private SatelliteSystem getSatelliteSystem() {
412 return satelliteSystem;
413 }
414
415 /** Get the number of week in one rollover cycle for the given satellite system.
416 *
417 * @param satellite satellite system
418 * @return the number of week in one rollover cycle for the given satellite system
419 */
420 private static int getRollOverWeek(final SatelliteSystem satellite) {
421 return CYCLE_MAP.get(satellite);
422 }
423
424 }
425 }