1   /* Copyright 2002-2026 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.frames;
18  
19  import java.io.Serial;
20  import java.io.Serializable;
21  import java.util.function.Function;
22  import java.util.function.ToDoubleFunction;
23  
24  import org.orekit.errors.OrekitException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.time.AbsoluteDate;
27  import org.orekit.time.DateComponents;
28  import org.orekit.time.TimeStamped;
29  
30  /** This class holds an Earth Orientation Parameters entry.
31   * @author Luc Maisonobe
32   */
33  public class EOPEntry implements TimeStamped, Serializable {
34  
35      /** Serializable UID. */
36      @Serial
37      private static final long serialVersionUID = 20260923L;
38  
39      /** Entry date (modified julian day, 00h00 UTC scale). */
40      private final int mjd;
41  
42      /** Entry date (absolute date). */
43      private final AbsoluteDate date;
44  
45      /** UT1-UTC. */
46      private final double dt;
47  
48      /** Length of day. */
49      private final double lod;
50  
51      /** X component of pole motion. */
52      private final double x;
53  
54      /** Y component of pole motion. */
55      private final double y;
56  
57      /** X component of pole motion rate.
58       * @since 12.0
59       */
60      private final double xRate;
61  
62      /** Y component of pole motion rate.
63       * @since 12.0
64       */
65      private final double yRate;
66  
67      /** Correction for nutation in longitude. */
68      private final double ddPsi;
69  
70      /** Correction for nutation in obliquity. */
71      private final double ddEps;
72  
73      /** Correction for nutation in Celestial Intermediate Pole (CIP) coordinates. */
74      private final double dx;
75  
76      /** Correction for nutation in Celestial Intermediate Pole (CIP) coordinates. */
77      private final double dy;
78  
79      /** ITRF version this entry defines. */
80      private final ITRFVersion itrfType;
81  
82      /** EOP data type. */
83      private final EopDataType eopDataType;
84  
85      /** Origin for X, Y, UT1-UTC and LOD.
86       * @since 14.0
87       */
88      private final EOPOrigin dtOrigin;
89  
90      /** Origin for nutation.
91       * @since 14.0
92       */
93      private final EOPOrigin nutOrigin;
94  
95      /** Origin for Celestial Intermediate Pole.
96       * @since 14.0
97       */
98      private final EOPOrigin cipOrigin;
99  
100     /** Constructor from raw elements.
101      * @param mjd         entry date (modified Julian day, 00h00 UTC scale)
102      * @param dt          UT1-UTC in seconds
103      * @param lod         length of day
104      * @param x           X component of pole motion
105      * @param y           Y component of pole motion
106      * @param xRate       X component of pole motion rate (NaN if absent)
107      * @param yRate       Y component of pole motion rate (NaN if absent)
108      * @param ddPsi       correction for nutation in longitude δΔΨ
109      * @param ddEps       correction for nutation in obliquity δΔε
110      * @param dx          correction for Celestial Intermediate Pole (CIP) coordinates
111      * @param dy          correction for Celestial Intermediate Pole (CIP) coordinates
112      * @param itrfType    ITRF version this entry defines
113      * @param date        corresponding to {@code mjd}
114      * @param eopDataType EOP data type
115      * @param dtOrigin    publication date for X, Y, UT1-UTC and LOD
116      * @param nutOrigin   publication date for nutation (null means same as {@code dtOrigin})
117      * @param cipOrigin   publication date for Celestial Intermediate Pole (null means same as {@code dtOrigin})
118      * @since 14.0
119      */
120     public EOPEntry(final int mjd, final double dt, final double lod,
121                     final double x, final double y, final double xRate, final double yRate,
122                     final double ddPsi, final double ddEps,
123                     final double dx, final double dy,
124                     final ITRFVersion itrfType, final AbsoluteDate date, final EopDataType eopDataType,
125                     final EOPOrigin dtOrigin, final EOPOrigin nutOrigin, final EOPOrigin cipOrigin) {
126         this.mjd         = mjd;
127         this.date        = date;
128         this.dt          = dt;
129         this.lod         = lod;
130         this.x           = x;
131         this.y           = y;
132         this.xRate       = xRate;
133         this.yRate       = yRate;
134         this.ddPsi       = ddPsi;
135         this.ddEps       = ddEps;
136         this.dx          = dx;
137         this.dy          = dy;
138         this.itrfType    = itrfType;
139         this.eopDataType = eopDataType;
140         this.dtOrigin    = dtOrigin;
141         this.nutOrigin   = nutOrigin == null ? dtOrigin : nutOrigin;
142         this.cipOrigin   = cipOrigin == null ? dtOrigin : cipOrigin;
143     }
144 
145     /** Combination constructor.
146      * <p>
147      * This constructor allows to merge data from two potentially incomplete entries
148      * corresponding to the same date.
149      * </p>
150      * <p>
151      * Incomplete entries occur in particular when parsing Bulletin A data, as
152      * xp, yp and UT1-UTC data are published on a weekly basis for rapid data and
153      * monthly for final data with a one-month delay. The pole offsets Δδψ/Δδε and
154      * x/y are published monthly with a two months delay.
155      * </p>
156      * <p>
157      * If some fields are initialized in one entry and non-initialized (i.e. NaN) in
158      * the other argument, then the initialized fields will be used for construction.
159      * If some fields are initialized in both entries, then the one published later
160      * will be used in construction. The publication date will be set to the latest
161      * of both publication dates. This implies that entries are combined.
162      * </p>
163      * @param entry1 first entry
164      * @param entry2 second entry
165      * @since 14.0
166      */
167     public EOPEntry(final EOPEntry entry1, final EOPEntry entry2) {
168 
169         // safety check
170         if (entry1.mjd != entry2.mjd) {
171             throw new OrekitException(OrekitMessages.INCOMPATIBLE_EARTH_ORIENTATION_PARAMETERS,
172                                       new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, entry1.mjd),
173                                       new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, entry2.mjd));
174         }
175 
176         // the dates are the same
177         this.mjd   = entry1.mjd;
178         this.date  = entry1.date;
179 
180         // combine fields
181         this.dt    = select(entry1, entry2, entry -> entry.dt,    entry -> entry.dtOrigin);
182         this.lod   = select(entry1, entry2, entry -> entry.lod,   entry -> entry.dtOrigin);
183         this.x     = select(entry1, entry2, entry -> entry.x,     entry -> entry.dtOrigin);
184         this.y     = select(entry1, entry2, entry -> entry.y,     entry -> entry.dtOrigin);
185         this.xRate = select(entry1, entry2, entry -> entry.xRate, entry -> entry.dtOrigin);
186         this.yRate = select(entry1, entry2, entry -> entry.yRate, entry -> entry.dtOrigin);
187         this.ddPsi = select(entry1, entry2, entry -> entry.ddPsi, entry -> entry.nutOrigin);
188         this.ddEps = select(entry1, entry2, entry -> entry.ddEps, entry -> entry.nutOrigin);
189         this.dx    = select(entry1, entry2, entry -> entry.dx,    entry -> entry.cipOrigin);
190         this.dy    = select(entry1, entry2, entry -> entry.dy,    entry -> entry.cipOrigin);
191 
192         if (entry1.dtOrigin.compareTo(entry2.dtOrigin) >= 0) {
193             this.itrfType    = entry1.itrfType;
194             this.eopDataType = entry1.eopDataType;
195             this.dtOrigin    = entry1.dtOrigin;
196         } else {
197             this.itrfType    = entry2.itrfType;
198             this.eopDataType = entry2.eopDataType;
199             this.dtOrigin    = entry2.dtOrigin;
200         }
201 
202         this.nutOrigin = entry1.nutOrigin.compareTo(entry2.nutOrigin) >= 0 ? entry1.nutOrigin : entry2.nutOrigin;
203         this.cipOrigin = entry1.cipOrigin.compareTo(entry2.cipOrigin) >= 0 ? entry1.cipOrigin : entry2.cipOrigin;
204 
205     }
206 
207     /** Select either initialized or published last EOP field.
208      * @param entry1 first entry
209      * @param entry2 second entry
210      * @param field  selector for field
211      * @param origin selector for origin
212      * @return selected field
213      * @since 14.0
214      */
215     private double select(final EOPEntry entry1, final EOPEntry entry2,
216                           final ToDoubleFunction<EOPEntry> field,
217                           final Function<EOPEntry, EOPOrigin> origin) {
218         final double field1 = field.applyAsDouble(entry1);
219         final double field2 = field.applyAsDouble(entry2);
220         if (Double.isNaN(field1)) {
221             // the field is not initialized in entry1, we select the field from entry2
222             return field2;
223         } else if (Double.isNaN(field2)) {
224             // the field is not initialized in entry2, we select the field from entry1
225             return field1;
226         } else {
227             // the field is initialized in both entries, we select the one published later
228             return origin.apply(entry1).compareTo(origin.apply(entry2)) >= 0 ? field1 : field2;
229         }
230     }
231 
232     /** Get the entry date (modified julian day, 00h00 UTC scale).
233      * @return entry date
234      * @see #getDate()
235      */
236     public int getMjd() {
237         return mjd;
238     }
239 
240     /** {@inheritDoc} */
241     public AbsoluteDate getDate() {
242         return date;
243     }
244 
245     /** Get the UT1-UTC value.
246      * @return UT1-UTC in seconds
247      */
248     public double getUT1MinusUTC() {
249         return dt;
250     }
251 
252     /** Get the LoD (Length of Day) value.
253      * @return LoD in seconds
254      */
255     public double getLOD() {
256         return lod;
257     }
258 
259     /** Get the X component of the pole motion.
260      * @return X component of pole motion
261      */
262     public double getX() {
263         return x;
264     }
265 
266     /** Get the Y component of the pole motion.
267      * @return Y component of pole motion
268      */
269     public double getY() {
270         return y;
271     }
272 
273     /** Get the X component of the pole motion rate.
274      * @return X component of pole motion rate
275      * @since 12.0
276      */
277     public double getXRate() {
278         return xRate;
279     }
280 
281     /** Get the Y component of the pole motion rate.
282      * @return Y component of pole motion rate
283      * @since 12.0
284      */
285     public double getYRate() {
286         return yRate;
287     }
288 
289     /** Get the correction for nutation in longitude δΔΨ.
290      * @return correction for nutation in longitude  δΔΨ
291      */
292     public double getDdPsi() {
293         return ddPsi;
294     }
295 
296     /** Get the correction for nutation in obliquity δΔε.
297      * @return correction for nutation in obliquity δΔε
298      */
299     public double getDdEps() {
300         return ddEps;
301     }
302 
303     /** Get the correction for Celestial Intermediate Pole (CIP) coordinates.
304      * @return correction for Celestial Intermediate Pole (CIP) coordinates
305      */
306     public double getDx() {
307         return dx;
308     }
309 
310     /** Get the correction for Celestial Intermediate Pole (CIP) coordinates.
311      * @return correction for Celestial Intermediate Pole (CIP) coordinates
312      */
313     public double getDy() {
314         return dy;
315     }
316 
317     /** Get the ITRF version this entry defines.
318      * @return ITRF version this entry defines
319      * @since 9.2
320      */
321     public ITRFVersion getITRFType() {
322         return itrfType;
323     }
324 
325     /** Get the EOP data type.
326      * @return EOP data type
327      * @since 13.1.1
328      */
329     public EopDataType getEopDataType() { return eopDataType; }
330 
331     /** Get the origin for X, Y, UT1-UTC and LOD.
332      * @return origin for X, Y, UT1-UTC and LOD
333      * @since 14.0
334      */
335     public EOPOrigin getDtOrigin() {
336         return dtOrigin;
337     }
338 
339     /** Get the origin for nutation.
340      * @return origin for nutation
341      * @since 14.0
342      */
343     public EOPOrigin getNutOrigin() {
344         return nutOrigin;
345     }
346 
347     /** Get the origin for Celestial Intermediate Pole.
348      * @return origin for Celestial Intermediate Pole
349      * @since 14.0
350      */
351     public EOPOrigin getCipOrigin() {
352         return cipOrigin;
353     }
354 
355 }