EOPHistory.java

  1. /* Copyright 2002-2019 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.      */
  70.     protected EOPHistory(final IERSConventions conventions,
  71.                          final Collection<EOPEntry> data,
  72.                          final boolean simpleEOP) {
  73.         this(conventions, data, simpleEOP ? null : new CachedCorrection(conventions.getEOPTidalCorrection()));
  74.     }

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

  95.     /** Get non-interpolating version of the instance.
  96.      * @return non-interpolatig version of the instance
  97.      */
  98.     public EOPHistory getNonInterpolatingEOPHistory() {
  99.         return new EOPHistory(conventions, getEntries(), conventions.getEOPTidalCorrection());
  100.     }

  101.     /** Check if the instance uses interpolation on tidal corrections.
  102.      * @return true if the instance uses interpolation on tidal corrections
  103.      */
  104.     public boolean usesInterpolation() {
  105.         return tidalCorrection != null && tidalCorrection instanceof CachedCorrection;
  106.     }

  107.     /** Get the IERS conventions to which these EOP apply.
  108.      * @return IERS conventions to which these EOP apply
  109.      */
  110.     public IERSConventions getConventions() {
  111.         return conventions;
  112.     }

  113.     /** Get the date of the first available Earth Orientation Parameters.
  114.      * @return the start date of the available data
  115.      */
  116.     public AbsoluteDate getStartDate() {
  117.         return this.cache.getEarliest().getDate();
  118.     }

  119.     /** Get the date of the last available Earth Orientation Parameters.
  120.      * @return the end date of the available data
  121.      */
  122.     public AbsoluteDate getEndDate() {
  123.         return this.cache.getLatest().getDate();
  124.     }

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

  131.         //check if there is data for date
  132.         if (!this.hasDataFor(date)) {
  133.             // no EOP data available for this date, we use a default 0.0 offset
  134.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[2];
  135.         }

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

  149.     }

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

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

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

  177.     }

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

  180.         /** DUT at first entry. */
  181.         private double firstDUT;

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

  184.         /** Interpolator to use. */
  185.         private final HermiteInterpolator interpolator;

  186.         /** Interpolation date. */
  187.         private AbsoluteDate date;

  188.         /** Simple constructor.
  189.          * @param date interpolation date
  190.          */
  191.         DUT1Interpolator(final AbsoluteDate date) {
  192.             this.firstDUT     = Double.NaN;
  193.             this.beforeLeap   = true;
  194.             this.interpolator = new HermiteInterpolator();
  195.             this.date         = date;
  196.         }

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

  218.         /** Get the interpolated value.
  219.          * @return interpolated value
  220.          */
  221.         public double getInterpolated() {
  222.             final double interpolated = interpolator.value(0)[0];
  223.             return beforeLeap ? interpolated : interpolated + 1.0;
  224.         }

  225.     }

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

  228.         /** DUT at first entry. */
  229.         private double firstDUT;

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

  232.         /** Interpolator to use. */
  233.         private final FieldHermiteInterpolator<T> interpolator;

  234.         /** Interpolation date. */
  235.         private FieldAbsoluteDate<T> date;

  236.         /** Interpolation date. */
  237.         private AbsoluteDate absDate;

  238.         /** Simple constructor.
  239.          * @param date interpolation date
  240.          * @param absDate interpolation date
  241.          */
  242.         FieldDUT1Interpolator(final FieldAbsoluteDate<T> date, final AbsoluteDate absDate) {
  243.             this.firstDUT     = Double.NaN;
  244.             this.beforeLeap   = true;
  245.             this.interpolator = new FieldHermiteInterpolator<>();
  246.             this.date         = date;
  247.             this.absDate      = absDate;
  248.         }

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

  270.         /** Get the interpolated value.
  271.          * @return interpolated value
  272.          */
  273.         public T getInterpolated() {
  274.             final T interpolated = interpolator.value(date.getField().getZero())[0];
  275.             return beforeLeap ? interpolated : interpolated.add(1.0);
  276.         }

  277.     }

  278.     /**
  279.      * Get the entries surrounding a central date.
  280.      * <p>
  281.      * See {@link #hasDataFor(AbsoluteDate)} to determine if the cache has data
  282.      * for {@code central} without throwing an exception.
  283.      *
  284.      * @param central central date
  285.      * @return array of cached entries surrounding specified date
  286.      */
  287.     protected Stream<EOPEntry> getNeighbors(final AbsoluteDate central) {
  288.         return cache.getNeighbors(central);
  289.     }

  290.     /** Get the LoD (Length of Day) value.
  291.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  292.      * @param date date at which the value is desired
  293.      * @return LoD in seconds (0 if date is outside covered range)
  294.      */
  295.     public double getLOD(final AbsoluteDate date) {

  296.         // check if there is data for date
  297.         if (!this.hasDataFor(date)) {
  298.             // no EOP data available for this date, we use a default null correction
  299.             return (tidalCorrection == null) ? 0.0 : tidalCorrection.value(date)[3];
  300.         }

  301.         // we have EOP data for date -> interpolate correction
  302.         double interpolated = interpolate(date, entry -> entry.getLOD());
  303.         if (tidalCorrection != null) {
  304.             interpolated += tidalCorrection.value(date)[3];
  305.         }
  306.         return interpolated;

  307.     }

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

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

  317.         // check if there is data for date
  318.         if (!this.hasDataFor(aDate)) {
  319.             // no EOP data available for this date, we use a default null correction
  320.             return (tidalCorrection == null) ? date.getField().getZero() : tidalCorrection.value(date)[3];
  321.         }

  322.         // we have EOP data for date -> interpolate correction
  323.         T interpolated = interpolate(date, aDate, entry -> entry.getLOD());
  324.         if (tidalCorrection != null) {
  325.             interpolated = interpolated.add(tidalCorrection.value(date)[3]);
  326.         }

  327.         return interpolated;

  328.     }

  329.     /** Get the pole IERS Reference Pole correction.
  330.      * <p>The data provided comes from the IERS files. It is smoothed data.</p>
  331.      * @param date date at which the correction is desired
  332.      * @return pole correction ({@link PoleCorrection#NULL_CORRECTION
  333.      * PoleCorrection.NULL_CORRECTION} if date is outside covered range)
  334.      */
  335.     public PoleCorrection getPoleCorrection(final AbsoluteDate date) {

  336.         // check if there is data for date
  337.         if (!this.hasDataFor(date)) {
  338.             // no EOP data available for this date, we use a default null correction
  339.             if (tidalCorrection == null) {
  340.                 return PoleCorrection.NULL_CORRECTION;
  341.             } else {
  342.                 final double[] correction = tidalCorrection.value(date);
  343.                 return new PoleCorrection(correction[0], correction[1]);
  344.             }
  345.         }

  346.         // we have EOP data for date -> interpolate correction
  347.         final double[] interpolated = interpolate(date, entry -> entry.getX(), entry -> entry.getY());
  348.         if (tidalCorrection != null) {
  349.             final double[] correction = tidalCorrection.value(date);
  350.             interpolated[0] += correction[0];
  351.             interpolated[1] += correction[1];
  352.         }
  353.         return new PoleCorrection(interpolated[0], interpolated[1]);

  354.     }

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

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

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

  374.         // we have EOP data for date -> interpolate correction
  375.         final T[] interpolated = interpolate(date, aDate, entry -> entry.getX(), entry -> entry.getY());
  376.         if (tidalCorrection != null) {
  377.             final T[] correction = tidalCorrection.value(date);
  378.             interpolated[0] = interpolated[0].add(correction[0]);
  379.             interpolated[1] = interpolated[1].add(correction[1]);
  380.         }
  381.         return new FieldPoleCorrection<>(interpolated[0], interpolated[1]);

  382.     }

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

  390.         // check if there is data for date
  391.         if (!this.hasDataFor(date)) {
  392.             // no EOP data available for this date, we use a default null correction
  393.             return new double[2];
  394.         }

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

  397.     }

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

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

  407.         // check if there is data for date
  408.         if (!this.hasDataFor(aDate)) {
  409.             // no EOP data available for this date, we use a default null correction
  410.             return MathArrays.buildArray(date.getField(), 2);
  411.         }

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

  414.     }

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

  422.         // check if there is data for date
  423.         if (!this.hasDataFor(date)) {
  424.             // no EOP data available for this date, we use a default null correction
  425.             return new double[2];
  426.         }

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

  429.     }

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

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

  439.         // check if there is data for date
  440.         if (!this.hasDataFor(aDate)) {
  441.             // no EOP data available for this date, we use a default null correction
  442.             return MathArrays.buildArray(date.getField(), 2);
  443.         }

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

  446.     }

  447.     /** Get the ITRF version.
  448.      * @param date date at which the value is desired
  449.      * @return ITRF version of the EOP covering the specified date
  450.      * @since 9.2
  451.      */
  452.     public ITRFVersion getITRFVersion(final AbsoluteDate date) {

  453.         // check if there is data for date
  454.         if (!this.hasDataFor(date)) {
  455.             // no EOP data available for this date, we use a default ITRF 2014
  456.             return ITRFVersion.ITRF_2014;
  457.         }

  458.         try {
  459.             // we have EOP data for date
  460.             final Optional<EOPEntry> first = getNeighbors(date).findFirst();
  461.             return first.isPresent() ? first.get().getITRFType() : ITRFVersion.ITRF_2014;

  462.         } catch (TimeStampedCacheException tce) {
  463.             // this should not happen because of date check performed at start
  464.             throw new OrekitInternalError(tce);
  465.         }

  466.     }

  467.     /** Check Earth orientation parameters continuity.
  468.      * @param maxGap maximal allowed gap between entries (in seconds)
  469.      */
  470.     public void checkEOPContinuity(final double maxGap) {
  471.         TimeStamped preceding = null;
  472.         for (final TimeStamped current : this.cache.getAll()) {

  473.             // compare the dates of preceding and current entries
  474.             if ((preceding != null) && ((current.getDate().durationFrom(preceding.getDate())) > maxGap)) {
  475.                 throw new OrekitException(OrekitMessages.MISSING_EARTH_ORIENTATION_PARAMETERS_BETWEEN_DATES,
  476.                                           preceding.getDate(), current.getDate());
  477.             }

  478.             // prepare next iteration
  479.             preceding = current;

  480.         }
  481.     }

  482.     /**
  483.      * Check if the cache has data for the given date using
  484.      * {@link #getStartDate()} and {@link #getEndDate()}.
  485.      *
  486.      * @param date the requested date
  487.      * @return true if the {@link #cache} has data for the requested date, false
  488.      *         otherwise.
  489.      */
  490.     protected boolean hasDataFor(final AbsoluteDate date) {
  491.         /*
  492.          * when there is no EOP data, short circuit getStartDate, which will
  493.          * throw an exception
  494.          */
  495.         return this.hasData && this.getStartDate().compareTo(date) <= 0 &&
  496.                date.compareTo(this.getEndDate()) <= 0;
  497.     }

  498.     /** Get a non-modifiable view of the EOP entries.
  499.      * @return non-modifiable view of the EOP entries
  500.      */
  501.     public List<EOPEntry> getEntries() {
  502.         return cache.getAll();
  503.     }

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

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

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

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

  615.     /** Replace the instance with a data transfer object for serialization.
  616.      * <p>
  617.      * This intermediate class serializes only the frame key.
  618.      * </p>
  619.      * @return data transfer object that will be serialized
  620.      */
  621.     private Object writeReplace() {
  622.         return new DataTransferObject(conventions, getEntries(), tidalCorrection == null);
  623.     }

  624.     /** Internal class used only for serialization. */
  625.     private static class DataTransferObject implements Serializable {

  626.         /** Serializable UID. */
  627.         private static final long serialVersionUID = 20131010L;

  628.         /** IERS conventions. */
  629.         private final IERSConventions conventions;

  630.         /** EOP entries. */
  631.         private final List<EOPEntry> entries;

  632.         /** Indicator for simple interpolation without tidal effects. */
  633.         private final boolean simpleEOP;

  634.         /** Simple constructor.
  635.          * @param conventions IERS conventions to which EOP refers
  636.          * @param entries the EOP data to use
  637.          * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  638.          */
  639.         DataTransferObject(final IERSConventions conventions,
  640.                                   final List<EOPEntry> entries,
  641.                                   final boolean simpleEOP) {
  642.             this.conventions = conventions;
  643.             this.entries     = entries;
  644.             this.simpleEOP   = simpleEOP;
  645.         }

  646.         /** Replace the deserialized data transfer object with a {@link EOPHistory}.
  647.          * @return replacement {@link EOPHistory}
  648.          */
  649.         private Object readResolve() {
  650.             try {
  651.                 // retrieve a managed frame
  652.                 return new EOPHistory(conventions, entries, simpleEOP);
  653.             } catch (OrekitException oe) {
  654.                 throw new OrekitInternalError(oe);
  655.             }
  656.         }

  657.     }

  658.     /** Internal class for caching tidal correction. */
  659.     private static class TidalCorrectionEntry implements TimeStamped {

  660.         /** Entry date. */
  661.         private final AbsoluteDate date;

  662.         /** Correction. */
  663.         private final double[] correction;

  664.         /** Simple constructor.
  665.          * @param date entry date
  666.          * @param correction correction on the EOP parameters (xp, yp, ut1, lod)
  667.          */
  668.         TidalCorrectionEntry(final AbsoluteDate date, final double[] correction) {
  669.             this.date       = date;
  670.             this.correction = correction;
  671.         }

  672.         /** {@inheritDoc} */
  673.         @Override
  674.         public AbsoluteDate getDate() {
  675.             return date;
  676.         }

  677.     }

  678.     /** Local generator for thread-safe cache. */
  679.     private static class CachedCorrection
  680.         implements TimeVectorFunction, TimeStampedGenerator<TidalCorrectionEntry> {

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

  683.         /** Step between generated entries. */
  684.         private final double step;

  685.         /** Tidal corrections entries cache. */
  686.         private final TimeStampedCache<TidalCorrectionEntry> cache;

  687.         /** Simple constructor.
  688.          * @param tidalCorrection function computing the tidal correction
  689.          */
  690.         CachedCorrection(final TimeVectorFunction tidalCorrection) {
  691.             this.step            = 60 * 60;
  692.             this.tidalCorrection = tidalCorrection;
  693.             this.cache           =
  694.                 new GenericTimeStampedCache<TidalCorrectionEntry>(8,
  695.                                                                   OrekitConfiguration.getCacheSlotsNumber(),
  696.                                                                   Constants.JULIAN_DAY * 30,
  697.                                                                   Constants.JULIAN_DAY,
  698.                                                                   this);
  699.         }

  700.         /** {@inheritDoc} */
  701.         @Override
  702.         public double[] value(final AbsoluteDate date) {
  703.             try {
  704.                 // set up an interpolator
  705.                 final HermiteInterpolator interpolator = new HermiteInterpolator();
  706.                 cache.getNeighbors(date).forEach(entry -> interpolator.addSamplePoint(entry.date.durationFrom(date), entry.correction));

  707.                 // interpolate to specified date
  708.                 return interpolator.value(0.0);
  709.             } catch (TimeStampedCacheException tsce) {
  710.                 // this should never happen
  711.                 throw new OrekitInternalError(tsce);
  712.             }
  713.         }

  714.         /** {@inheritDoc} */
  715.         @Override
  716.         public <T extends RealFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
  717.             try {

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

  719.                 final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
  720.                 final T[] y = MathArrays.buildArray(date.getField(), 4);
  721.                 final T zero = date.getField().getZero();
  722.                 final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
  723.                                                                                            // for example removing derivatives
  724.                                                                                            // if T was DerivativeStructure
  725.                 cache.getNeighbors(aDate).forEach(entry -> {
  726.                     for (int i = 0; i < y.length; ++i) {
  727.                         y[i] = zero.add(entry.correction[i]);
  728.                     }
  729.                     interpolator.addSamplePoint(central.durationFrom(entry.getDate()).negate(), y);
  730.                 });

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

  733.             } catch (TimeStampedCacheException tsce) {
  734.                 // this should never happen
  735.                 throw new OrekitInternalError(tsce);
  736.             }
  737.         }

  738.         /** {@inheritDoc} */
  739.         @Override
  740.         public List<TidalCorrectionEntry> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {

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

  742.             if (existingDate == null) {

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

  748.             } else {

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

  751.                 AbsoluteDate t = existingDate;
  752.                 if (date.compareTo(t) > 0) {
  753.                     // forward generation
  754.                     do {
  755.                         t = t.shiftedBy(step);
  756.                         generated.add(new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  757.                     } while (t.compareTo(date) <= 0);
  758.                 } else {
  759.                     // backward generation
  760.                     do {
  761.                         t = t.shiftedBy(-step);
  762.                         generated.add(0, new TidalCorrectionEntry(t, tidalCorrection.value(t)));
  763.                     } while (t.compareTo(date) >= 0);
  764.                 }
  765.             }

  766.             // return the generated transforms
  767.             return generated;

  768.         }
  769.     }

  770. }