EOPHistory.java

  1. /* Copyright 2002-2017 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. import java.io.Serializable;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.List;
  22. import java.util.function.Consumer;
  23. import java.util.function.Function;
  24. import java.util.stream.Stream;

  25. import org.hipparchus.RealFieldElement;
  26. import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
  27. import org.hipparchus.analysis.interpolation.HermiteInterpolator;
  28. import org.hipparchus.util.MathArrays;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitInternalError;
  31. import org.orekit.errors.OrekitMessages;
  32. import org.orekit.errors.TimeStampedCacheException;
  33. import org.orekit.time.AbsoluteDate;
  34. import org.orekit.time.FieldAbsoluteDate;
  35. import org.orekit.time.TimeStamped;
  36. import org.orekit.time.TimeVectorFunction;
  37. import org.orekit.utils.Constants;
  38. import org.orekit.utils.GenericTimeStampedCache;
  39. import org.orekit.utils.IERSConventions;
  40. import org.orekit.utils.ImmutableTimeStampedCache;
  41. import org.orekit.utils.OrekitConfiguration;
  42. import org.orekit.utils.TimeStampedCache;
  43. import org.orekit.utils.TimeStampedGenerator;

  44. /** This class loads any kind of Earth Orientation Parameter data throughout a large time range.
  45.  * @author Pascal Parraud
  46.  */
  47. public class EOPHistory implements Serializable {

  48.     /** Serializable UID. */
  49.     private static final long serialVersionUID = 20131010L;

  50.     /** Number of points to use in interpolation. */
  51.     private static final int INTERPOLATION_POINTS = 4;

  52.     /**
  53.      * If this history has any EOP data.
  54.      *
  55.      * @see #hasDataFor(AbsoluteDate)
  56.      */
  57.     private final boolean hasData;

  58.     /** EOP history entries. */
  59.     private final transient ImmutableTimeStampedCache<EOPEntry> cache;

  60.     /** IERS conventions to which EOP refers. */
  61.     private final IERSConventions conventions;

  62.     /** Correction to apply to EOP (may be null). */
  63.     private final transient TimeVectorFunction tidalCorrection;

  64.     /** Simple constructor.
  65.      * @param conventions IERS conventions to which EOP refers
  66.      * @param data the EOP data to use
  67.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  68.      * @exception OrekitException if tidal correction model cannot be loaded
  69.      */
  70.     protected EOPHistory(final IERSConventions conventions,
  71.                          final Collection<EOPEntry> data,
  72.                          final boolean simpleEOP)
  73.         throws OrekitException {
  74.         this(conventions, data, simpleEOP ? null : new CachedCorrection(conventions.getEOPTidalCorrection()));
  75.     }

  76.     /** Simple constructor.
  77.      * @param conventions IERS conventions to which EOP refers
  78.      * @param data the EOP data to use
  79.      * @param tidalCorrection correction to apply to EOP
  80.      * @exception OrekitException if tidal correction model cannot be loaded
  81.      */
  82.     private EOPHistory(final IERSConventions conventions,
  83.                          final Collection<EOPEntry> data,
  84.                          final TimeVectorFunction tidalCorrection)
  85.         throws OrekitException {
  86.         this.conventions      = conventions;
  87.         this.tidalCorrection  = tidalCorrection;
  88.         if (data.size() >= INTERPOLATION_POINTS) {
  89.             // enough data to interpolate
  90.             cache = new ImmutableTimeStampedCache<EOPEntry>(INTERPOLATION_POINTS, data);
  91.             hasData = true;
  92.         } else {
  93.             // not enough data to interpolate -> always use null correction
  94.             cache = ImmutableTimeStampedCache.emptyCache();
  95.             hasData = false;
  96.         }
  97.     }

  98.     /** Get non-interpolating version of the instance.
  99.      * @return non-interpolatig version of the instance
  100.      * @exception OrekitException if tidal correction model cannot be loaded
  101.      */
  102.     public EOPHistory getNonInterpolatingEOPHistory()
  103.         throws OrekitException {
  104.         return new EOPHistory(conventions, getEntries(), conventions.getEOPTidalCorrection());
  105.     }

  106.     /** Check if the instance uses interpolation on tidal corrections.
  107.      * @return true if the instance uses interpolation on tidal corrections
  108.      */
  109.     public boolean usesInterpolation() {
  110.         return tidalCorrection != null && tidalCorrection instanceof CachedCorrection;
  111.     }

  112.     /** Get the IERS conventions to which these EOP apply.
  113.      * @return IERS conventions to which these EOP apply
  114.      */
  115.     public IERSConventions getConventions() {
  116.         return conventions;
  117.     }

  118.     /** Get the date of the first available Earth Orientation Parameters.
  119.      * @return the start date of the available data
  120.      */
  121.     public AbsoluteDate getStartDate() {
  122.         return this.cache.getEarliest().getDate();
  123.     }

  124.     /** Get the date of the last available Earth Orientation Parameters.
  125.      * @return the end date of the available data
  126.      */
  127.     public AbsoluteDate getEndDate() {
  128.         return this.cache.getLatest().getDate();
  129.     }

  130.     /** Get the UT1-UTC value.
  131.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  132.      * @param date date at which the value is desired
  133.      * @return UT1-UTC in seconds (0 if date is outside covered range)
  134.      */
  135.     public double getUT1MinusUTC(final AbsoluteDate date) {

  136.         //check if there is data for date
  137.         if (!this.hasDataFor(date)) {
  138.             // no EOP data available for this date, we use a default 0.0 offset
  139.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
  140.         }

  141.         // we have EOP data -> interpolate offset
  142.         try {
  143.             final DUT1Interpolator interpolator = new DUT1Interpolator(date);
  144.             getNeighbors(date).forEach(interpolator);
  145.             double interpolated = interpolator.getInterpolated();
  146.             if (tidalCorrection != null) {
  147.                 interpolated += tidalCorrection.value(date)[2];
  148.             }
  149.             return interpolated;
  150.         } catch (TimeStampedCacheException tce) {
  151.             //this should not happen because of date check above
  152.             throw new OrekitInternalError(tce);
  153.         }

  154.     }

  155.     /** Get the UT1-UTC value.
  156.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  157.      * @param date date at which the value is desired
  158.      * @param <T> type of the field elements
  159.      * @return UT1-UTC in seconds (0 if date is outside covered range)
  160.      * @since 9.0
  161.      */
  162.     public <T extends RealFieldElement<T>> T getUT1MinusUTC(final FieldAbsoluteDate<T> date) {

  163.         //check if there is data for date
  164.         final AbsoluteDate absDate = date.toAbsoluteDate();
  165.         if (!this.hasDataFor(absDate)) {
  166.             // no EOP data available for this date, we use a default 0.0 offset
  167.             return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[2];
  168.         }

  169.         // we have EOP data -> interpolate offset
  170.         try {
  171.             final FieldDUT1Interpolator<T> interpolator = new FieldDUT1Interpolator<>(date, absDate);
  172.             getNeighbors(absDate).forEach(interpolator);
  173.             T interpolated = interpolator.getInterpolated();
  174.             if (tidalCorrection != null) {
  175.                 interpolated = interpolated.add(tidalCorrection.value(date)[2]);
  176.             }
  177.             return interpolated;
  178.         } catch (TimeStampedCacheException tce) {
  179.             //this should not happen because of date check above
  180.             throw new OrekitInternalError(tce);
  181.         }

  182.     }

  183.     /** Local class for DUT1 interpolation, crossing leaps safely. */
  184.     private static class DUT1Interpolator implements Consumer<EOPEntry> {

  185.         /** DUT at first entry. */
  186.         private double firstDUT;

  187.         /** Indicator for dates just before a leap occurring during the interpolation sample. */
  188.         private boolean beforeLeap;

  189.         /** Interpolator to use. */
  190.         private final HermiteInterpolator interpolator;

  191.         /** Interpolation date. */
  192.         private AbsoluteDate date;

  193.         /** Simple constructor.
  194.          * @param date interpolation date
  195.          */
  196.         DUT1Interpolator(final AbsoluteDate date) {
  197.             this.firstDUT     = Double.NaN;
  198.             this.beforeLeap   = true;
  199.             this.interpolator = new HermiteInterpolator();
  200.             this.date         = date;
  201.         }

  202.         /** {@inheritDoc} */
  203.         @Override
  204.         public void accept(final EOPEntry neighbor) {
  205.             if (Double.isNaN(firstDUT)) {
  206.                 firstDUT = neighbor.getUT1MinusUTC();
  207.             }
  208.             final double dut;
  209.             if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
  210.                 // there was a leap second between the entries
  211.                 dut = neighbor.getUT1MinusUTC() - 1.0;
  212.                 if (neighbor.getDate().compareTo(date) <= 0) {
  213.                     beforeLeap = false;
  214.                 }
  215.             } else {
  216.                 dut = neighbor.getUT1MinusUTC();
  217.             }
  218.             interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
  219.                                         new double[] {
  220.                                             dut
  221.                                         });
  222.         }

  223.         /** Get the interpolated value.
  224.          * @return interpolated value
  225.          */
  226.         public double getInterpolated() {
  227.             final double interpolated = interpolator.value(0)[0];
  228.             return beforeLeap ? interpolated : interpolated + 1.0;
  229.         }

  230.     }

  231.     /** Local class for DUT1 interpolation, crossing leaps safely. */
  232.     private static class FieldDUT1Interpolator<T extends RealFieldElement<T>> implements Consumer<EOPEntry> {

  233.         /** DUT at first entry. */
  234.         private double firstDUT;

  235.         /** Indicator for dates just before a leap occurring during the interpolation sample. */
  236.         private boolean beforeLeap;

  237.         /** Interpolator to use. */
  238.         private final FieldHermiteInterpolator<T> interpolator;

  239.         /** Interpolation date. */
  240.         private FieldAbsoluteDate<T> date;

  241.         /** Interpolation date. */
  242.         private AbsoluteDate absDate;

  243.         /** Simple constructor.
  244.          * @param date interpolation date
  245.          * @param absDate interpolation date
  246.          */
  247.         FieldDUT1Interpolator(final FieldAbsoluteDate<T> date, final AbsoluteDate absDate) {
  248.             this.firstDUT     = Double.NaN;
  249.             this.beforeLeap   = true;
  250.             this.interpolator = new FieldHermiteInterpolator<>();
  251.             this.date         = date;
  252.             this.absDate      = absDate;
  253.         }

  254.         /** {@inheritDoc} */
  255.         @Override
  256.         public void accept(final EOPEntry neighbor) {
  257.             if (Double.isNaN(firstDUT)) {
  258.                 firstDUT = neighbor.getUT1MinusUTC();
  259.             }
  260.             final double dut;
  261.             if (neighbor.getUT1MinusUTC() - firstDUT > 0.9) {
  262.                 // there was a leap second between the entries
  263.                 dut = neighbor.getUT1MinusUTC() - 1.0;
  264.                 if (neighbor.getDate().compareTo(absDate) <= 0) {
  265.                     beforeLeap = false;
  266.                 }
  267.             } else {
  268.                 dut = neighbor.getUT1MinusUTC();
  269.             }
  270.             final T[] array = MathArrays.buildArray(date.getField(), 1);
  271.             array[0] = date.getField().getZero().add(dut);
  272.             interpolator.addSamplePoint(date.durationFrom(neighbor.getDate()).negate(),
  273.                                         array);
  274.         }

  275.         /** Get the interpolated value.
  276.          * @return interpolated value
  277.          */
  278.         public T getInterpolated() {
  279.             final T interpolated = interpolator.value(date.getField().getZero())[0];
  280.             return beforeLeap ? interpolated : interpolated.add(1.0);
  281.         }

  282.     }

  283.     /**
  284.      * Get the entries surrounding a central date.
  285.      * <p>
  286.      * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
  287.      * for {@code central} without throwing an exception.
  288.      *
  289.      * @param central central date
  290.      * @return array of cached entries surrounding specified date
  291.      * @exception TimeStampedCacheException if EOP data cannot be retrieved
  292.      */
  293.     protected Stream<EOPEntry> getNeighbors(final AbsoluteDate central) throws TimeStampedCacheException {
  294.         return cache.getNeighbors(central);
  295.     }

  296.     /** Get the LoD (Length of Day) value.
  297.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  298.      * @param date date at which the value is desired
  299.      * @return LoD in seconds (0 if date is outside covered range)
  300.      */
  301.     public double getLOD(final AbsoluteDate date) {

  302.         // check if there is data for date
  303.         if (!this.hasDataFor(date)) {
  304.             // no EOP data available for this date, we use a default null correction
  305.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
  306.         }

  307.         // we have EOP data for date -> interpolate correction
  308.         double interpolated = interpolate(date, entry -> entry.getLOD());
  309.         if (tidalCorrection != null) {
  310.             interpolated += tidalCorrection.value(date)[3];
  311.         }
  312.         return interpolated;

  313.     }

  314.     /** Get the LoD (Length of Day) value.
  315.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  316.      * @param date date at which the value is desired
  317.      * @param <T> type of the filed elements
  318.      * @return LoD in seconds (0 if date is outside covered range)
  319.      * @since 9.0
  320.      */
  321.     public <T extends RealFieldElement<T>> T getLOD(final FieldAbsoluteDate<T> date) {

  322.         final AbsoluteDate aDate = date.toAbsoluteDate();

  323.         // check if there is data for date
  324.         if (!this.hasDataFor(aDate)) {
  325.             // no EOP data available for this date, we use a default null correction
  326.             return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[3];
  327.         }

  328.         // we have EOP data for date -> interpolate correction
  329.         T interpolated = interpolate(date, aDate, entry -> entry.getLOD());
  330.         if (tidalCorrection != null) {
  331.             interpolated = interpolated.add(tidalCorrection.value(date)[3]);
  332.         }

  333.         return interpolated;

  334.     }

  335.     /** Get the pole IERS Reference Pole correction.
  336.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  337.      * @param date date at which the correction is desired
  338.      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
  339.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  340.      */
  341.     public PoleCorrection getPoleCorrection(final AbsoluteDate date) {

  342.         // check if there is data for date
  343.         if (!this.hasDataFor(date)) {
  344.             // no EOP data available for this date, we use a default null correction
  345.             if (tidalCorrection == null) {
  346.                 return PoleCorrection.NULL_CORRECTION;
  347.             } else {
  348.                 final double[] correction = tidalCorrection.value(date);
  349.                 return new PoleCorrection(correction[0], correction[1]);
  350.             }
  351.         }

  352.         // we have EOP data for date -> interpolate correction
  353.         final double[] interpolated = interpolate(date, entry -> entry.getX(), entry -> entry.getY());
  354.         if (tidalCorrection != null) {
  355.             final double[] correction = tidalCorrection.value(date);
  356.             interpolated[0] += correction[0];
  357.             interpolated[1] += correction[1];
  358.         }
  359.         return new PoleCorrection(interpolated[0], interpolated[1]);

  360.     }

  361.     /** Get the pole IERS Reference Pole correction.
  362.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  363.      * @param date date at which the correction is desired
  364.      * @param <T> type of the field elements
  365.      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
  366.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  367.      */
  368.     public <T extends RealFieldElement<T>> FieldPoleCorrection<T> getPoleCorrection(final FieldAbsoluteDate<T> date) {

  369.         final AbsoluteDate aDate = date.toAbsoluteDate();

  370.         // check if there is data for date
  371.         if (!this.hasDataFor(aDate)) {
  372.             // no EOP data available for this date, we use a default null correction
  373.             if (tidalCorrection == null) {
  374.                 return new FieldPoleCorrection<>(date.getField().getZero(), date.getField().getZero());
  375.             } else {
  376.                 final T[] correction = tidalCorrection.value(date);
  377.                 return new FieldPoleCorrection<>(correction[0], correction[1]);
  378.             }
  379.         }

  380.         // we have EOP data for date -> interpolate correction
  381.         final T[] interpolated = interpolate(date, aDate, entry -> entry.getX(), entry -> entry.getY());
  382.         if (tidalCorrection != null) {
  383.             final T[] correction = tidalCorrection.value(date);
  384.             interpolated[0] = interpolated[0].add(correction[0]);
  385.             interpolated[1] = interpolated[1].add(correction[1]);
  386.         }
  387.         return new FieldPoleCorrection<>(interpolated[0], interpolated[1]);

  388.     }

  389.     /** Get the correction to the nutation parameters for equinox-based paradigm.
  390.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  391.      * @param date date at which the correction is desired
  392.      * @return nutation correction in longitude ΔΨ and in obliquity Δε
  393.      * (zero if date is outside covered range)
  394.      */
  395.     public double[] getEquinoxNutationCorrection(final AbsoluteDate date) {

  396.         // check if there is data for date
  397.         if (!this.hasDataFor(date)) {
  398.             // no EOP data available for this date, we use a default null correction
  399.             return new double[2];
  400.         }

  401.         // we have EOP data for date -> interpolate correction
  402.         return interpolate(date, entry -> entry.getDdPsi(), entry -> entry.getDdEps());

  403.     }

  404.     /** Get the correction to the nutation parameters for equinox-based paradigm.
  405.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  406.      * @param date date at which the correction is desired
  407.      * @param <T> type of the field elements
  408.      * @return nutation correction in longitude ΔΨ and in obliquity Δε
  409.      * (zero if date is outside covered range)
  410.      */
  411.     public <T extends RealFieldElement<T>> T[] getEquinoxNutationCorrection(final FieldAbsoluteDate<T> date) {

  412.         final AbsoluteDate aDate = date.toAbsoluteDate();

  413.         // check if there is data for date
  414.         if (!this.hasDataFor(aDate)) {
  415.             // no EOP data available for this date, we use a default null correction
  416.             return MathArrays.buildArray(date.getField(), 2);
  417.         }

  418.         // we have EOP data for date -> interpolate correction
  419.         return interpolate(date, aDate, entry -> entry.getDdPsi(), entry -> entry.getDdEps());

  420.     }

  421.     /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
  422.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  423.      * @param date date at which the correction is desired
  424.      * @return nutation correction in Celestial Intermediat Pole coordinates
  425.      * δX and δY (zero if date is outside covered range)
  426.      */
  427.     public double[] getNonRotatinOriginNutationCorrection(final AbsoluteDate date) {

  428.         // check if there is data for date
  429.         if (!this.hasDataFor(date)) {
  430.             // no EOP data available for this date, we use a default null correction
  431.             return new double[2];
  432.         }

  433.         // we have EOP data for date -> interpolate correction
  434.         return interpolate(date, entry -> entry.getDx(), entry -> entry.getDy());

  435.     }

  436.     /** Get the correction to the nutation parameters for Non-Rotating Origin paradigm.
  437.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  438.      * @param date date at which the correction is desired
  439.      * @param <T> type of the filed elements
  440.      * @return nutation correction in Celestial Intermediat Pole coordinates
  441.      * δX and δY (zero if date is outside covered range)
  442.      */
  443.     public <T extends RealFieldElement<T>> T[] getNonRotatinOriginNutationCorrection(final FieldAbsoluteDate<T> date) {

  444.         final AbsoluteDate aDate = date.toAbsoluteDate();

  445.         // check if there is data for date
  446.         if (!this.hasDataFor(aDate)) {
  447.             // no EOP data available for this date, we use a default null correction
  448.             return MathArrays.buildArray(date.getField(), 2);
  449.         }

  450.         // we have EOP data for date -> interpolate correction
  451.         return interpolate(date, aDate, entry -> entry.getDx(), entry -> entry.getDy());

  452.     }

  453.     /** Check Earth orientation parameters continuity.
  454.      * @param maxGap maximal allowed gap between entries (in seconds)
  455.      * @exception OrekitException if there are holes in the data sequence
  456.      */
  457.     public void checkEOPContinuity(final double maxGap) throws OrekitException {
  458.         TimeStamped preceding = null;
  459.         for (final TimeStamped current : this.cache.getAll()) {

  460.             // compare the dates of preceding and current entries
  461.             if ((preceding != null) && ((current.getDate().durationFrom(preceding.getDate())) > maxGap)) {
  462.                 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES,
  463.                                           preceding.getDate(), current.getDate());
  464.             }

  465.             // prepare next iteration
  466.             preceding = current;

  467.         }
  468.     }

  469.     /**
  470.      * Check if the cache has data for the given date using
  471.      * {@link #getStartDate()} and {@link #getEndDate()}.
  472.      *
  473.      * @param date the requested date
  474.      * @return true if the {@link #cache} has data for the requested date, false
  475.      *         otherwise.
  476.      */
  477.     protected boolean hasDataFor(final AbsoluteDate date) {
  478.         /*
  479.          * when there is no EOP data, short circuit getStartDate, which will
  480.          * throw an exception
  481.          */
  482.         return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
  483.                date.compareTo(this.getEndDate()) <= 0;
  484.     }

  485.     /** Get a non-modifiable view of the EOP entries.
  486.      * @return non-modifiable view of the EOP entries
  487.      */
  488.     List<EOPEntry> getEntries() {
  489.         return cache.getAll();
  490.     }

  491.     /** Interpolate a single EOP component.
  492.      * <p>
  493.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  494.      * </p>
  495.      * @param date interpolation date
  496.      * @param selector selector for EOP entry component
  497.      * @return interpolated value
  498.      */
  499.     private double interpolate(final AbsoluteDate date, final Function<EOPEntry, Double> selector) {
  500.         try {
  501.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  502.             getNeighbors(date).forEach(entry ->
  503.                                        interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  504.                                                                    new double[] {
  505.                                                                        selector.apply(entry)
  506.                                                                    }));
  507.             return interpolator.value(0)[0];
  508.         } catch (TimeStampedCacheException tce) {
  509.             // this should not happen because of date check performed by caller
  510.             throw new OrekitInternalError(tce);
  511.         }
  512.     }

  513.     /** Interpolate a single EOP component.
  514.      * <p>
  515.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  516.      * </p>
  517.      * @param date interpolation date
  518.      * @param aDate interpolation date, as an {@link AbsoluteDate}
  519.      * @param selector selector for EOP entry component
  520.      * @param <T> type of the field elements
  521.      * @return interpolated value
  522.      */
  523.     private <T extends RealFieldElement<T>> T interpolate(final FieldAbsoluteDate<T> date,
  524.                                                           final AbsoluteDate aDate,
  525.                                                           final Function<EOPEntry, Double> selector) {
  526.         try {
  527.             final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  528.             final T[] y = MathArrays.buildArray(date.getField(), 1);
  529.             final T zero = date.getField().getZero();
  530.             final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  531.                                                                                        // for example removing derivatives
  532.                                                                                        // if T was DerivativeStructure
  533.             getNeighbors(aDate).forEach(entry -> {
  534.                 y[0] = zero.add(selector.apply(entry));
  535.                 interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  536.             });
  537.             return interpolator.value(date.durationFrom(central))[0]; // here, we introduce derivatives again (in DerivativeStructure case)
  538.         } catch (TimeStampedCacheException tce) {
  539.             // this should not happen because of date check performed by caller
  540.             throw new OrekitInternalError(tce);
  541.         }
  542.     }

  543.     /** Interpolate two EOP components.
  544.      * <p>
  545.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  546.      * </p>
  547.      * @param date interpolation date
  548.      * @param selector1 selector for first EOP entry component
  549.      * @param selector2 selector for second EOP entry component
  550.      * @return interpolated value
  551.      */
  552.     private double[] interpolate(final AbsoluteDate date,
  553.                                  final Function<EOPEntry, Double> selector1,
  554.                                  final Function<EOPEntry, Double> selector2) {
  555.         try {
  556.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  557.             getNeighbors(date).forEach(entry ->
  558.                                        interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  559.                                                                    new double[] {
  560.                                                                        selector1.apply(entry),
  561.                                                                        selector2.apply(entry)
  562.                                                                    }));
  563.             return interpolator.value(0);
  564.         } catch (TimeStampedCacheException tce) {
  565.             // this should not happen because of date check performed by caller
  566.             throw new OrekitInternalError(tce);
  567.         }
  568.     }

  569.     /** Interpolate two EOP components.
  570.      * <p>
  571.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  572.      * </p>
  573.      * @param date interpolation date
  574.      * @param aDate interpolation date, as an {@link AbsoluteDate}
  575.      * @param selector1 selector for first EOP entry component
  576.      * @param selector2 selector for second EOP entry component
  577.      * @param <T> type of the field elements
  578.      * @return interpolated value
  579.      */
  580.     private <T extends RealFieldElement<T>> T[] interpolate(final FieldAbsoluteDate<T> date,
  581.                                                             final AbsoluteDate aDate,
  582.                                                             final Function<EOPEntry, Double> selector1,
  583.                                                             final Function<EOPEntry, Double> selector2) {
  584.         try {
  585.             final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  586.             final T[] y = MathArrays.buildArray(date.getField(), 2);
  587.             final T zero = date.getField().getZero();
  588.             final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  589.                                                                                        // for example removing derivatives
  590.                                                                                        // if T was DerivativeStructure
  591.             getNeighbors(aDate).forEach(entry -> {
  592.                 y[0] = zero.add(selector1.apply(entry));
  593.                 y[1] = zero.add(selector2.apply(entry));
  594.                 interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  595.             });
  596.             return interpolator.value(date.durationFrom(central)); // here, we introduce derivatives again (in DerivativeStructure case)
  597.         } catch (TimeStampedCacheException tce) {
  598.             // this should not happen because of date check performed by caller
  599.             throw new OrekitInternalError(tce);
  600.         }
  601.     }

  602.     /** Replace the instance with a data transfer object for serialization.
  603.      * <p>
  604.      * This intermediate class serializes only the frame key.
  605.      * </p>
  606.      * @return data transfer object that will be serialized
  607.      */
  608.     private Object writeReplace() {
  609.         return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
  610.     }

  611.     /** Internal class used only for serialization. */
  612.     private static class DataTransferObject implements Serializable {

  613.         /** Serializable UID. */
  614.         private static final long serialVersionUID = 20131010L;

  615.         /** IERS conventions. */
  616.         private final IERSConventions conventions;

  617.         /** EOP entries. */
  618.         private final List<EOPEntry> entries;

  619.         /** Indicator for simple interpolation without tidal effects. */
  620.         private final boolean simpleEOP;

  621.         /** Simple constructor.
  622.          * @param conventions IERS conventions to which EOP refers
  623.          * @param entries the EOP data to use
  624.          * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  625.          */
  626.         DataTransferObject(final IERSConventions conventions,
  627.                                   final List<EOPEntry> entries,
  628.                                   final boolean simpleEOP) {
  629.             this.conventions = conventions;
  630.             this.entries     = entries;
  631.             this.simpleEOP   = simpleEOP;
  632.         }

  633.         /** Replace the deserialized data transfer object with a {@link EOPHistory}.
  634.          * @return replacement {@link EOPHistory}
  635.          */
  636.         private Object readResolve() {
  637.             try {
  638.                 // retrieve a managed frame
  639.                 return new EOPHistory(conventions, entries, simpleEOP);
  640.             } catch (OrekitException oe) {
  641.                 throw new OrekitInternalError(oe);
  642.             }
  643.         }

  644.     }

  645.     /** Internal class for caching tidal correction. */
  646.     private static class TidalCorrectionEntry implements TimeStamped {

  647.         /** Entry date. */
  648.         private final AbsoluteDate date;

  649.         /** Correction. */
  650.         private final double[] correction;

  651.         /** Simple constructor.
  652.          * @param date entry date
  653.          * @param correction correction on the EOP parameters (xp, yp, ut1, lod)
  654.          */
  655.         TidalCorrectionEntry(final AbsoluteDate date, final double[] correction) {
  656.             this.date       = date;
  657.             this.correction = correction;
  658.         }

  659.         /** {@inheritDoc} */
  660.         @Override
  661.         public AbsoluteDate getDate() {
  662.             return date;
  663.         }

  664.     }

  665.     /** Local generator for thread-safe cache. */
  666.     private static class CachedCorrection
  667.         implements TimeVectorFunction, TimeStampedGenerator<TidalCorrectionEntry> {

  668.         /** Correction to apply to EOP (may be null). */
  669.         private final TimeVectorFunction tidalCorrection;

  670.         /** Step between generated entries. */
  671.         private final double step;

  672.         /** Tidal corrections entries cache. */
  673.         private final TimeStampedCache<TidalCorrectionEntry> cache;

  674.         /** Simple constructor.
  675.          * @param tidalCorrection function computing the tidal correction
  676.          */
  677.         CachedCorrection(final TimeVectorFunction tidalCorrection) {
  678.             this.step            = 60 * 60;
  679.             this.tidalCorrection = tidalCorrection;
  680.             this.cache           =
  681.                 new GenericTimeStampedCache<TidalCorrectionEntry>(8,
  682.                                                                   OrekitConfiguration.getCacheSlotsNumber(),
  683.                                                                   Constants.JULIAN_DAY * 30,
  684.                                                                   Constants.JULIAN_DAY,
  685.                                                                   this);
  686.         }

  687.         /** {@inheritDoc} */
  688.         @Override
  689.         public double[] value(final AbsoluteDate date) {
  690.             try {
  691.                 // set up an interpolator
  692.                 final HermiteInterpolator interpolator = new HermiteInterpolator();
  693.                 cache.getNeighbors(date).forEach(entry -> interpolator.addSamplePoint(entry.date.durationFrom(date), entry.correction));

  694.                 // interpolate to specified date
  695.                 return interpolator.value(0.0);
  696.             } catch (TimeStampedCacheException tsce) {
  697.                 // this should never happen
  698.                 throw new OrekitInternalError(tsce);
  699.             }
  700.         }

  701.         /** {@inheritDoc} */
  702.         @Override
  703.         public <T extends RealFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
  704.             try {

  705.                 final AbsoluteDate aDate = date.toAbsoluteDate();

  706.                 final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  707.                 final T[] y = MathArrays.buildArray(date.getField(), 4);
  708.                 final T zero = date.getField().getZero();
  709.                 final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  710.                                                                                            // for example removing derivatives
  711.                                                                                            // if T was DerivativeStructure
  712.                 cache.getNeighbors(aDate).forEach(entry -> {
  713.                     for (int i = 0; i < y.length; ++i) {
  714.                         y[i] = zero.add(entry.correction[i]);
  715.                     }
  716.                     interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  717.                 });

  718.                 // interpolate to specified date
  719.                 return interpolator.value(date.durationFrom(central)); // here, we introduce derivatives again (in DerivativeStructure case)

  720.             } catch (TimeStampedCacheException tsce) {
  721.                 // this should never happen
  722.                 throw new OrekitInternalError(tsce);
  723.             }
  724.         }

  725.         /** {@inheritDoc} */
  726.         @Override
  727.         public List<TidalCorrectionEntry> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {

  728.             final List<TidalCorrectionEntry> generated = new ArrayList<TidalCorrectionEntry>();

  729.             if (existingDate == null) {

  730.                 // no prior existing entries, just generate a first set
  731.                 for (int i = -cache.getNeighborsSize() / 2; generated.size() < cache.getNeighborsSize(); ++i) {
  732.                     final AbsoluteDate t = date.shiftedBy(i * step);
  733.                     generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  734.                 }

  735.             } else {

  736.                 // some entries have already been generated
  737.                 // add the missing ones up to specified date

  738.                 AbsoluteDate t = existingDate;
  739.                 if (date.compareTo(t) > 0) {
  740.                     // forward generation
  741.                     do {
  742.                         t = t.shiftedBy(step);
  743.                         generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  744.                     } while (t.compareTo(date) <= 0);
  745.                 } else {
  746.                     // backward generation
  747.                     do {
  748.                         t = t.shiftedBy(-step);
  749.                         generated.add(0, new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  750.                     } while (t.compareTo(date) >= 0);
  751.                 }
  752.             }

  753.             // return the generated transforms
  754.             return generated;

  755.         }
  756.     }

  757. }