1   /* Copyright 2002-2013 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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.Serializable;
20  import java.util.Collection;
21  import java.util.List;
22  
23  import org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.errors.TimeStampedCacheException;
27  import org.orekit.time.AbsoluteDate;
28  import org.orekit.time.TimeFunction;
29  import org.orekit.time.TimeStamped;
30  import org.orekit.utils.IERSConventions;
31  import org.orekit.utils.ImmutableTimeStampedCache;
32  
33  /** This class loads any kind of Earth Orientation Parameter data throughout a large time range.
34   * @author Pascal Parraud
35   */
36  public class EOPHistory implements Serializable {
37  
38      /** Serializable UID. */
39      private static final long serialVersionUID = 20131010L;
40  
41      /** Number of points to use in interpolation. */
42      private static final int INTERPOLATION_POINTS = 4;
43  
44      /**
45       * If this history has any EOP data.
46       *
47       * @see #hasDataFor(AbsoluteDate)
48       */
49      private final boolean hasData;
50  
51      /** EOP history entries. */
52      private final transient ImmutableTimeStampedCache<EOPEntry> cache;
53  
54      /** IERS conventions to which EOP refers. */
55      private final IERSConventions conventions;
56  
57      /** Correction to apply to EOP (may be null). */
58      private final transient TimeFunction<double[]> tidalCorrection;
59  
60      /** Simple constructor.
61       * @param conventions IERS conventions to which EOP refers
62       * @param data the EOP data to use
63       * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
64       * @exception OrekitException if tidal correction model cannot be loaded
65       */
66      protected EOPHistory(final IERSConventions conventions,
67                           final Collection<EOPEntry> data,
68                           final boolean simpleEOP)
69          throws OrekitException {
70          this.conventions = conventions;
71          tidalCorrection = simpleEOP ? null : conventions.getEOPTidalCorrection();
72          if (data.size() >= INTERPOLATION_POINTS) {
73              // enough data to interpolate
74              cache = new ImmutableTimeStampedCache<EOPEntry>(INTERPOLATION_POINTS, data);
75              hasData = true;
76          } else {
77              // not enough data to interpolate -> always use null correction
78              cache = ImmutableTimeStampedCache.emptyCache();
79              hasData = false;
80          }
81      }
82  
83      /** Get the IERS conventions to which these EOP apply.
84       * @return IERS conventions to which these EOP apply
85       */
86      public IERSConventions getConventions() {
87          return conventions;
88      }
89  
90      /** Get the date of the first available Earth Orientation Parameters.
91       * @return the start date of the available data
92       */
93      public AbsoluteDate getStartDate() {
94          return this.cache.getEarliest().getDate();
95      }
96  
97      /** Get the date of the last available Earth Orientation Parameters.
98       * @return the end date of the available data
99       */
100     public AbsoluteDate getEndDate() {
101         return this.cache.getLatest().getDate();
102     }
103 
104     /** Get the UT1-UTC value.
105      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
106      * @param date date at which the value is desired
107      * @return UT1-UTC in seconds (0 if date is outside covered range)
108      */
109     public double getUT1MinusUTC(final AbsoluteDate date) {
110         //check if there is data for date
111         if (!this.hasDataFor(date)) {
112             // no EOP data available for this date, we use a default 0.0 offset
113             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
114         }
115         //we have EOP data -> interpolate offset
116         try {
117             final List<EOPEntry> neighbors = getNeighbors(date);
118             final HermiteInterpolator interpolator = new HermiteInterpolator();
119             final double firstDUT = neighbors.get(0).getUT1MinusUTC();
120             boolean beforeLeap = true;
121             for (final EOPEntry neighbor : neighbors) {
122                 final double dut;
123                 if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
124                     // there was a leap second between the entries
125                     dut = neighbor.getUT1MinusUTC() - 1.0;
126                     if (neighbor.getDate().compareTo(date) <= 0) {
127                         beforeLeap = false;
128                     }
129                 } else {
130                     dut = neighbor.getUT1MinusUTC();
131                 }
132                 interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
133                                             new double[] {
134                                                 dut
135                                             });
136             }
137             double interpolated = interpolator.value(0)[0];
138             if (tidalCorrection != null) {
139                 interpolated += tidalCorrection.value(date)[2];
140             }
141             return beforeLeap ? interpolated : interpolated + 1.0;
142         } catch (TimeStampedCacheException tce) {
143             //this should not happen because of date check above
144             throw OrekitException.createInternalError(tce);
145         }
146     }
147 
148     /**
149      * Get the entries surrounding a central date.
150      * <p>
151      * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
152      * for {@code central} without throwing an exception.
153      *
154      * @param central central date
155      * @return array of cached entries surrounding specified date
156      * @exception TimeStampedCacheException if EOP data cannot be retrieved
157      */
158     protected List<EOPEntry> getNeighbors(final AbsoluteDate central) throws TimeStampedCacheException {
159         return cache.getNeighbors(central);
160     }
161 
162     /** Get the LoD (Length of Day) value.
163      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
164      * @param date date at which the value is desired
165      * @return LoD in seconds (0 if date is outside covered range)
166      */
167     public double getLOD(final AbsoluteDate date) {
168         //check if there is data for date
169         if (!this.hasDataFor(date)) {
170             // no EOP data available for this date, we use a default null correction
171             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
172         }
173         //we have EOP data for date -> interpolate correction
174         try {
175             final HermiteInterpolator interpolator = new HermiteInterpolator();
176             for (final EOPEntry entry : getNeighbors(date)) {
177                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
178                                             new double[] {
179                                                 entry.getLOD()
180                                             });
181             }
182             double interpolated = interpolator.value(0)[0];
183             if (tidalCorrection != null) {
184                 interpolated += tidalCorrection.value(date)[3];
185             }
186             return interpolated;
187         } catch (TimeStampedCacheException tce) {
188             // this should not happen because of date check above
189             throw OrekitException.createInternalError(tce);
190         }
191     }
192 
193     /** Get the pole IERS Reference Pole correction.
194      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
195      * @param date date at which the correction is desired
196      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
197      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
198      */
199     public PoleCorrection getPoleCorrection(final AbsoluteDate date) {
200         // check if there is data for date
201         if (!this.hasDataFor(date)) {
202             // no EOP data available for this date, we use a default null correction
203             if (tidalCorrection == null) {
204                 return PoleCorrection.NULL_CORRECTION;
205             } else {
206                 final double[] correction = tidalCorrection.value(date);
207                 return new PoleCorrection(correction[0], correction[1]);
208             }
209         }
210         //we have EOP data for date -> interpolate correction
211         try {
212             final HermiteInterpolator interpolator = new HermiteInterpolator();
213             for (final EOPEntry entry : getNeighbors(date)) {
214                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
215                                             new double[] {
216                                                 entry.getX(), entry.getY()
217                                             });
218             }
219             final double[] interpolated = interpolator.value(0);
220             if (tidalCorrection != null) {
221                 final double[] correction = tidalCorrection.value(date);
222                 interpolated[0] += correction[0];
223                 interpolated[1] += correction[1];
224             }
225             return new PoleCorrection(interpolated[0], interpolated[1]);
226         } catch (TimeStampedCacheException tce) {
227             // this should not happen because of date check above
228             throw OrekitException.createInternalError(tce);
229         }
230     }
231 
232     /** Get the correction to the nutation parameters for equinox-based paradigm.
233      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
234      * @param date date at which the correction is desired
235      * @return nutation correction in longitude &Delta;&Psi; and in obliquity &Delta;&epsilon;
236      * (zero if date is outside covered range)
237      */
238     public double[] getEquinoxNutationCorrection(final AbsoluteDate date) {
239         // check if there is data for date
240         if (!this.hasDataFor(date)) {
241             // no EOP data available for this date, we use a default null correction
242             return new double[2];
243         }
244         //we have EOP data for date -> interpolate correction
245         try {
246             final HermiteInterpolator interpolator = new HermiteInterpolator();
247             for (final EOPEntry entry : getNeighbors(date)) {
248                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
249                                             new double[] {
250                                                 entry.getDdPsi(), entry.getDdEps()
251                                             });
252             }
253             return interpolator.value(0);
254         } catch (TimeStampedCacheException tce) {
255             // this should not happen because of date check above
256             throw OrekitException.createInternalError(tce);
257         }
258     }
259 
260     /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
261      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
262      * @param date date at which the correction is desired
263      * @return nutation correction in Celestial Intermediat Pole coordinates
264      * &delta;X and &delta;Y (zero if date is outside covered range)
265      */
266     public double[] getNonRotatinOriginNutationCorrection(final AbsoluteDate date) {
267         // check if there is data for date
268         if (!this.hasDataFor(date)) {
269             // no EOP data available for this date, we use a default null correction
270             return new double[2];
271         }
272         //we have EOP data for date -> interpolate correction
273         try {
274             final HermiteInterpolator interpolator = new HermiteInterpolator();
275             for (final EOPEntry entry : getNeighbors(date)) {
276                 interpolator.addSamplePoint(entry.getDate().durationFrom(date),
277                                             new double[] {
278                                                 entry.getDx(), entry.getDy()
279                                             });
280             }
281             return interpolator.value(0);
282         } catch (TimeStampedCacheException tce) {
283             // this should not happen because of date check above
284             throw OrekitException.createInternalError(tce);
285         }
286     }
287 
288     /** Check Earth orientation parameters continuity.
289      * @param maxGap maximal allowed gap between entries (in seconds)
290      * @exception OrekitException if there are holes in the data sequence
291      */
292     public void checkEOPContinuity(final double maxGap) throws OrekitException {
293         TimeStamped preceding = null;
294         for (final TimeStamped current : this.cache.getAll()) {
295 
296             // compare the dates of preceding and current entries
297             if ((preceding != null) && ((current.getDate().durationFrom(preceding.getDate())) > maxGap)) {
298                 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES,
299                                           preceding.getDate(), current.getDate());
300             }
301 
302             // prepare next iteration
303             preceding = current;
304 
305         }
306     }
307 
308     /**
309      * Check if the cache has data for the given date using
310      * {@link #getStartDate()} and {@link #getEndDate()}.
311      *
312      * @param date the requested date
313      * @return true if the {@link #cache} has data for the requested date, false
314      *         otherwise.
315      */
316     protected boolean hasDataFor(final AbsoluteDate date) {
317         /*
318          * when there is no EOP data, short circuit getStartDate, which will
319          * throw an exception
320          */
321         return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
322                date.compareTo(this.getEndDate()) <= 0;
323     }
324 
325     /** Get a non-modifiable view of the EOP entries.
326      * @return non-modifiable view of the EOP entries
327      */
328     List<EOPEntry> getEntries() {
329         return cache.getAll();
330     }
331 
332     /** Replace the instance with a data transfer object for serialization.
333      * <p>
334      * This intermediate class serializes only the frame key.
335      * </p>
336      * @return data transfer object that will be serialized
337      */
338     private Object writeReplace() {
339         return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
340     }
341 
342     /** Internal class used only for serialization. */
343     private static class DataTransferObject implements Serializable {
344 
345         /** Serializable UID. */
346         private static final long serialVersionUID = 20131010L;
347 
348         /** IERS conventions. */
349         private final IERSConventions conventions;
350 
351         /** EOP entries. */
352         private final List<EOPEntry> entries;
353 
354         /** Indicator for simple interpolation without tidal effects. */
355         private final boolean simpleEOP;
356 
357         /** Simple constructor.
358          * @param conventions IERS conventions to which EOP refers
359          * @param entries the EOP data to use
360          * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
361          */
362         public DataTransferObject(final IERSConventions conventions,
363                                   final List<EOPEntry> entries,
364                                   final boolean simpleEOP) {
365             this.conventions = conventions;
366             this.entries     = entries;
367             this.simpleEOP   = simpleEOP;
368         }
369 
370         /** Replace the deserialized data transfer object with a {@link EOPHistory}.
371          * @return replacement {@link EOPHistory}
372          */
373         private Object readResolve() {
374             try {
375                 // retrieve a managed frame
376                 return new EOPHistory(conventions, entries, simpleEOP);
377             } catch (OrekitException oe) {
378                 throw OrekitException.createInternalError(oe);
379             }
380         }
381 
382     }
383 
384 }