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