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