EOPHistory.java

  1. /* Copyright 2002-2018 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.Optional;
  23. import java.util.function.Consumer;
  24. import java.util.function.Function;
  25. import java.util.stream.Stream;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  155.     }

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

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

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

  183.     }

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

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

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

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

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

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

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

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

  231.     }

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

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

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

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

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

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

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

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

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

  283.     }

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

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

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

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

  314.     }

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

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

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

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

  334.         return interpolated;

  335.     }

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

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

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

  361.     }

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

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

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

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

  389.     }

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

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

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

  404.     }

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

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

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

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

  421.     }

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

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

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

  436.     }

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

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

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

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

  453.     }

  454.     /** Get the ITRF version.
  455.      * @param date date at which the value is desired
  456.      * @return ITRF version of the EOP covering the specified date
  457.      * @since 9.2
  458.      */
  459.     public ITRFVersion getITRFVersion(final AbsoluteDate date) {

  460.         // check if there is data for date
  461.         if (!this.hasDataFor(date)) {
  462.             // no EOP data available for this date, we use a default ITRF 2014
  463.             return ITRFVersion.ITRF_2014;
  464.         }

  465.         try {
  466.             // we have EOP data for date
  467.             final Optional<EOPEntry> first = getNeighbors(date).findFirst();
  468.             return first.isPresent() ? first.get().getITRFType() : ITRFVersion.ITRF_2014;

  469.         } catch (TimeStampedCacheException tce) {
  470.             // this should not happen because of date check performed at start
  471.             throw new OrekitInternalError(tce);
  472.         }

  473.     }

  474.     /** Check Earth orientation parameters continuity.
  475.      * @param maxGap maximal allowed gap between entries (in seconds)
  476.      * @exception OrekitException if there are holes in the data sequence
  477.      */
  478.     public void checkEOPContinuity(final double maxGap) throws OrekitException {
  479.         TimeStamped preceding = null;
  480.         for (final TimeStamped current : this.cache.getAll()) {

  481.             // compare the dates of preceding and current entries
  482.             if ((preceding != null) && ((current.getDate().durationFrom(preceding.getDate())) > maxGap)) {
  483.                 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES,
  484.                                           preceding.getDate(), current.getDate());
  485.             }

  486.             // prepare next iteration
  487.             preceding = current;

  488.         }
  489.     }

  490.     /**
  491.      * Check if the cache has data for the given date using
  492.      * {@link #getStartDate()} and {@link #getEndDate()}.
  493.      *
  494.      * @param date the requested date
  495.      * @return true if the {@link #cache} has data for the requested date, false
  496.      *         otherwise.
  497.      */
  498.     protected boolean hasDataFor(final AbsoluteDate date) {
  499.         /*
  500.          * when there is no EOP data, short circuit getStartDate, which will
  501.          * throw an exception
  502.          */
  503.         return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
  504.                date.compareTo(this.getEndDate()) <= 0;
  505.     }

  506.     /** Get a non-modifiable view of the EOP entries.
  507.      * @return non-modifiable view of the EOP entries
  508.      */
  509.     List<EOPEntry> getEntries() {
  510.         return cache.getAll();
  511.     }

  512.     /** Interpolate a single EOP component.
  513.      * <p>
  514.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  515.      * </p>
  516.      * @param date interpolation date
  517.      * @param selector selector for EOP entry component
  518.      * @return interpolated value
  519.      */
  520.     private double interpolate(final AbsoluteDate date, final Function<EOPEntry, Double> selector) {
  521.         try {
  522.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  523.             getNeighbors(date).forEach(entry ->
  524.                                        interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  525.                                                                    new double[] {
  526.                                                                        selector.apply(entry)
  527.                                                                    }));
  528.             return interpolator.value(0)[0];
  529.         } catch (TimeStampedCacheException tce) {
  530.             // this should not happen because of date check performed by caller
  531.             throw new OrekitInternalError(tce);
  532.         }
  533.     }

  534.     /** Interpolate a single EOP component.
  535.      * <p>
  536.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  537.      * </p>
  538.      * @param date interpolation date
  539.      * @param aDate interpolation date, as an {@link AbsoluteDate}
  540.      * @param selector selector for EOP entry component
  541.      * @param <T> type of the field elements
  542.      * @return interpolated value
  543.      */
  544.     private <T extends RealFieldElement<T>> T interpolate(final FieldAbsoluteDate<T> date,
  545.                                                           final AbsoluteDate aDate,
  546.                                                           final Function<EOPEntry, Double> selector) {
  547.         try {
  548.             final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  549.             final T[] y = MathArrays.buildArray(date.getField(), 1);
  550.             final T zero = date.getField().getZero();
  551.             final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  552.                                                                                        // for example removing derivatives
  553.                                                                                        // if T was DerivativeStructure
  554.             getNeighbors(aDate).forEach(entry -> {
  555.                 y[0] = zero.add(selector.apply(entry));
  556.                 interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  557.             });
  558.             return interpolator.value(date.durationFrom(central))[0]; // here, we introduce derivatives again (in DerivativeStructure case)
  559.         } catch (TimeStampedCacheException tce) {
  560.             // this should not happen because of date check performed by caller
  561.             throw new OrekitInternalError(tce);
  562.         }
  563.     }

  564.     /** Interpolate two EOP components.
  565.      * <p>
  566.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  567.      * </p>
  568.      * @param date interpolation date
  569.      * @param selector1 selector for first EOP entry component
  570.      * @param selector2 selector for second EOP entry component
  571.      * @return interpolated value
  572.      */
  573.     private double[] interpolate(final AbsoluteDate date,
  574.                                  final Function<EOPEntry, Double> selector1,
  575.                                  final Function<EOPEntry, Double> selector2) {
  576.         try {
  577.             final HermiteInterpolator interpolator = new HermiteInterpolator();
  578.             getNeighbors(date).forEach(entry ->
  579.                                        interpolator.addSamplePoint(entry.getDate().durationFrom(date),
  580.                                                                    new double[] {
  581.                                                                        selector1.apply(entry),
  582.                                                                        selector2.apply(entry)
  583.                                                                    }));
  584.             return interpolator.value(0);
  585.         } catch (TimeStampedCacheException tce) {
  586.             // this should not happen because of date check performed by caller
  587.             throw new OrekitInternalError(tce);
  588.         }
  589.     }

  590.     /** Interpolate two EOP components.
  591.      * <p>
  592.      * This method should be called <em>only</em> when {@link #hasDataFor(AbsoluteDate)} returns true.
  593.      * </p>
  594.      * @param date interpolation date
  595.      * @param aDate interpolation date, as an {@link AbsoluteDate}
  596.      * @param selector1 selector for first EOP entry component
  597.      * @param selector2 selector for second EOP entry component
  598.      * @param <T> type of the field elements
  599.      * @return interpolated value
  600.      */
  601.     private <T extends RealFieldElement<T>> T[] interpolate(final FieldAbsoluteDate<T> date,
  602.                                                             final AbsoluteDate aDate,
  603.                                                             final Function<EOPEntry, Double> selector1,
  604.                                                             final Function<EOPEntry, Double> selector2) {
  605.         try {
  606.             final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  607.             final T[] y = MathArrays.buildArray(date.getField(), 2);
  608.             final T zero = date.getField().getZero();
  609.             final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  610.                                                                                        // for example removing derivatives
  611.                                                                                        // if T was DerivativeStructure
  612.             getNeighbors(aDate).forEach(entry -> {
  613.                 y[0] = zero.add(selector1.apply(entry));
  614.                 y[1] = zero.add(selector2.apply(entry));
  615.                 interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  616.             });
  617.             return interpolator.value(date.durationFrom(central)); // here, we introduce derivatives again (in DerivativeStructure case)
  618.         } catch (TimeStampedCacheException tce) {
  619.             // this should not happen because of date check performed by caller
  620.             throw new OrekitInternalError(tce);
  621.         }
  622.     }

  623.     /** Replace the instance with a data transfer object for serialization.
  624.      * <p>
  625.      * This intermediate class serializes only the frame key.
  626.      * </p>
  627.      * @return data transfer object that will be serialized
  628.      */
  629.     private Object writeReplace() {
  630.         return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
  631.     }

  632.     /** Internal class used only for serialization. */
  633.     private static class DataTransferObject implements Serializable {

  634.         /** Serializable UID. */
  635.         private static final long serialVersionUID = 20131010L;

  636.         /** IERS conventions. */
  637.         private final IERSConventions conventions;

  638.         /** EOP entries. */
  639.         private final List<EOPEntry> entries;

  640.         /** Indicator for simple interpolation without tidal effects. */
  641.         private final boolean simpleEOP;

  642.         /** Simple constructor.
  643.          * @param conventions IERS conventions to which EOP refers
  644.          * @param entries the EOP data to use
  645.          * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  646.          */
  647.         DataTransferObject(final IERSConventions conventions,
  648.                                   final List<EOPEntry> entries,
  649.                                   final boolean simpleEOP) {
  650.             this.conventions = conventions;
  651.             this.entries     = entries;
  652.             this.simpleEOP   = simpleEOP;
  653.         }

  654.         /** Replace the deserialized data transfer object with a {@link EOPHistory}.
  655.          * @return replacement {@link EOPHistory}
  656.          */
  657.         private Object readResolve() {
  658.             try {
  659.                 // retrieve a managed frame
  660.                 return new EOPHistory(conventions, entries, simpleEOP);
  661.             } catch (OrekitException oe) {
  662.                 throw new OrekitInternalError(oe);
  663.             }
  664.         }

  665.     }

  666.     /** Internal class for caching tidal correction. */
  667.     private static class TidalCorrectionEntry implements TimeStamped {

  668.         /** Entry date. */
  669.         private final AbsoluteDate date;

  670.         /** Correction. */
  671.         private final double[] correction;

  672.         /** Simple constructor.
  673.          * @param date entry date
  674.          * @param correction correction on the EOP parameters (xp, yp, ut1, lod)
  675.          */
  676.         TidalCorrectionEntry(final AbsoluteDate date, final double[] correction) {
  677.             this.date       = date;
  678.             this.correction = correction;
  679.         }

  680.         /** {@inheritDoc} */
  681.         @Override
  682.         public AbsoluteDate getDate() {
  683.             return date;
  684.         }

  685.     }

  686.     /** Local generator for thread-safe cache. */
  687.     private static class CachedCorrection
  688.         implements TimeVectorFunction, TimeStampedGenerator<TidalCorrectionEntry> {

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

  691.         /** Step between generated entries. */
  692.         private final double step;

  693.         /** Tidal corrections entries cache. */
  694.         private final TimeStampedCache<TidalCorrectionEntry> cache;

  695.         /** Simple constructor.
  696.          * @param tidalCorrection function computing the tidal correction
  697.          */
  698.         CachedCorrection(final TimeVectorFunction tidalCorrection) {
  699.             this.step            = 60 * 60;
  700.             this.tidalCorrection = tidalCorrection;
  701.             this.cache           =
  702.                 new GenericTimeStampedCache<TidalCorrectionEntry>(8,
  703.                                                                   OrekitConfiguration.getCacheSlotsNumber(),
  704.                                                                   Constants.JULIAN_DAY * 30,
  705.                                                                   Constants.JULIAN_DAY,
  706.                                                                   this);
  707.         }

  708.         /** {@inheritDoc} */
  709.         @Override
  710.         public double[] value(final AbsoluteDate date) {
  711.             try {
  712.                 // set up an interpolator
  713.                 final HermiteInterpolator interpolator = new HermiteInterpolator();
  714.                 cache.getNeighbors(date).forEach(entry -> interpolator.addSamplePoint(entry.date.durationFrom(date), entry.correction));

  715.                 // interpolate to specified date
  716.                 return interpolator.value(0.0);
  717.             } catch (TimeStampedCacheException tsce) {
  718.                 // this should never happen
  719.                 throw new OrekitInternalError(tsce);
  720.             }
  721.         }

  722.         /** {@inheritDoc} */
  723.         @Override
  724.         public <T extends RealFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
  725.             try {

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

  727.                 final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  728.                 final T[] y = MathArrays.buildArray(date.getField(), 4);
  729.                 final T zero = date.getField().getZero();
  730.                 final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  731.                                                                                            // for example removing derivatives
  732.                                                                                            // if T was DerivativeStructure
  733.                 cache.getNeighbors(aDate).forEach(entry -> {
  734.                     for (int i = 0; i < y.length; ++i) {
  735.                         y[i] = zero.add(entry.correction[i]);
  736.                     }
  737.                     interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  738.                 });

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

  741.             } catch (TimeStampedCacheException tsce) {
  742.                 // this should never happen
  743.                 throw new OrekitInternalError(tsce);
  744.             }
  745.         }

  746.         /** {@inheritDoc} */
  747.         @Override
  748.         public List<TidalCorrectionEntry> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {

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

  750.             if (existingDate == null) {

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

  756.             } else {

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

  759.                 AbsoluteDate t = existingDate;
  760.                 if (date.compareTo(t) > 0) {
  761.                     // forward generation
  762.                     do {
  763.                         t = t.shiftedBy(step);
  764.                         generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  765.                     } while (t.compareTo(date) <= 0);
  766.                 } else {
  767.                     // backward generation
  768.                     do {
  769.                         t = t.shiftedBy(-step);
  770.                         generated.add(0, new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  771.                     } while (t.compareTo(date) >= 0);
  772.                 }
  773.             }

  774.             // return the generated transforms
  775.             return generated;

  776.         }
  777.     }

  778. }