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
19 import org.hipparchus.CalculusFieldElement;
20
21 /** Scale for on-board clock.
22 * @author Luc Maisonobe
23 * @since 11.0
24 */
25 public class SatelliteClockScale implements TimeScale {
26
27 /** Name of the scale. */
28 private final String name;
29
30 /** Reference epoch. */
31 private final AbsoluteDate epoch;
32
33 /** Reference epoch. */
34 private final DateTimeComponents epochDT;
35
36 /** Offset from TAI at epoch. */
37 private final TimeOffset offsetAtEpoch;
38
39 /** Clock count at epoch. */
40 private final double countAtEpoch;
41
42 /** Clock drift (i.e. clock count per SI second minus 1.0). */
43 private final double drift;
44
45 /** Clock rate. */
46 private final double rate;
47
48 /** Create a linear model for satellite clock.
49 * <p>
50 * Beware that we specify the model using its drift with respect to
51 * flow of time. For a perfect clock without any drift, the clock
52 * count would be one tick every SI second. A clock that is fast, say
53 * for example it generates 1000001 ticks every 1000000 SI second, would
54 * have a rate of 1.000001 tick per SI second and hence a drift of
55 * 1.0e-6 tick per SI second. In this constructor we use the drift
56 * (1.0e-6 in the previous example) rather than the rate (1.000001
57 * in the previous example) to specify the clock. The rationale is
58 * that for clocks that are intended to be used for representing absolute
59 * time, the drift is expected to be small (much smaller that 1.0e-6
60 * for a good clock), so using drift is numerically more stable than
61 * using rate and risking catastrophic cancellation when subtracting
62 * 1.0 in the internal computation.
63 * </p>
64 * <p>
65 * Despite what is explained in the previous paragraph, this class can
66 * handle spacecraft clocks that are not intended to be synchronized with
67 * SI seconds, for example clocks that ticks at 10 Hz. In such cases the
68 * drift would need to be set at 10.0 - 1.0 = 9.0, which is not intuitive.
69 * For these clocks, the methods {@link #countAtDate(AbsoluteDate)} and
70 * {@link #dateAtCount(double)} and perhaps {@link #offsetFromTAI(AbsoluteDate)}
71 * are still useful, whereas {@link #offsetToTAI(DateComponents, TimeComponents)}
72 * is probably not really meaningful.
73 * </p>
74 * @param name of the scale
75 * @param epoch reference epoch
76 * @param epochScale time scale in which the {@code epoch} was defined
77 * @param countAtEpoch clock count at {@code epoch}
78 * @param drift clock drift rate (i.e. clock count change per SI second minus 1.0)
79 */
80 public SatelliteClockScale(final String name,
81 final AbsoluteDate epoch, final TimeScale epochScale,
82 final double countAtEpoch, final double drift) {
83 this.name = name;
84 this.epoch = epoch;
85 this.epochDT = epoch.getComponents(epochScale);
86 this.offsetAtEpoch = epochScale.offsetFromTAI(epoch).add(new TimeOffset(countAtEpoch));
87 this.countAtEpoch = countAtEpoch;
88 this.drift = drift;
89 this.rate = 1.0 + drift;
90 }
91
92 /** {@inheritDoc} */
93 @Override
94 public TimeOffset offsetFromTAI(final AbsoluteDate date) {
95 return offsetAtEpoch.add(new TimeOffset(drift * date.durationFrom(epoch)));
96 }
97
98 /** {@inheritDoc} */
99 @Override
100 public TimeOffset offsetToTAI(final DateComponents date, final TimeComponents time) {
101 final long deltaDate = (date.getJ2000Day() - epochDT.getDate().getJ2000Day()) *
102 TimeOffset.DAY.getSeconds();
103 final TimeOffset deltaTime = time.getSplitSecondsInUTCDay().
104 subtract(epochDT.getTime().getSplitSecondsInUTCDay());
105 final double delta = deltaDate + deltaTime.toDouble();
106 final double timeSinceEpoch = (delta - countAtEpoch) / rate;
107 return offsetAtEpoch.add(new TimeOffset(drift * timeSinceEpoch)).negate();
108 }
109
110 /** Compute date corresponding to some clock count.
111 * @param count clock count
112 * @return date at {@code count}
113 */
114 public AbsoluteDate dateAtCount(final double count) {
115 return epoch.shiftedBy((count - countAtEpoch) / rate);
116 }
117
118 /** Compute clock count corresponding to some date.
119 * @param date date
120 * @return clock count at {@code date}
121 */
122 public double countAtDate(final AbsoluteDate date) {
123 return countAtEpoch + rate * date.durationFrom(epoch);
124 }
125
126 /** {@inheritDoc} */
127 @Override
128 public <T extends CalculusFieldElement<T>> T offsetFromTAI(final FieldAbsoluteDate<T> date) {
129 return date.durationFrom(epoch).multiply(drift).add(offsetAtEpoch.toDouble());
130 }
131
132 /** {@inheritDoc} */
133 public String getName() {
134 return name;
135 }
136
137 /** {@inheritDoc} */
138 public String toString() {
139 return getName();
140 }
141
142 }